Prepare for public release: generic wording, MIT LICENSE

Make the scaffold standalone and publish-ready:
- Rewrite code + README references to the originating backend as a generic
  "wallet backend" so the service reads as a reusable, run-anywhere LWS.
- Add the MIT LICENSE file (matches Cargo.toml) and a short provenance note.
- Add repository/readme/keywords/categories metadata to Cargo.toml.
This commit is contained in:
jwinterm
2026-07-09 07:11:10 -04:00
parent 1dabd1513c
commit 255c13e27e
8 changed files with 87 additions and 55 deletions
+5
View File
@@ -5,6 +5,11 @@ edition = "2021"
rust-version = "1.75"
description = "Grin light-wallet-server: register a view credential, scan the chain in the background, store recognized outputs in a DB, and serve balance/outputs/broadcast/height."
license = "MIT"
repository = "https://github.com/Such-Software/grin-lws"
readme = "README.md"
keywords = ["grin", "light-wallet-server", "lws", "mimblewimble", "wallet"]
categories = ["cryptography::cryptocurrencies"]
# crates.io publish is gated until the rangeproof-rewind crypto lands (scaffold).
publish = false
[[bin]]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Such Software
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+33 -25
View File
@@ -5,9 +5,9 @@ view credential, run a **background scanner** over the Grin chain, **store**
recognized outputs (with recovered derivation paths) in a **database**, and
serve balance / unspent-outputs / broadcast / height fast from that store.
It is the Grin analogue of monero-lws, and lets any backend proxy to it exactly
the way `smirk-backend-core`'s `xmr_wow.rs` proxies to the Monero LWS via
`infra/lws/client.rs`.
It is the Grin analogue of monero-lws: a wallet backend proxies to it exactly the
way a backend proxies to the Monero LWS — forward a view credential, serve the
stored results back to the wallet.
**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the
scanner loop *structure* are real and build. The two chain-crypto seams — block
@@ -20,12 +20,12 @@ milestone (see [Build order](#build-order)).
## Why this exists (and why it needs a DB)
`smirk-backend-core`'s current `POST /wallet/grin/scan` is an **on-demand,
stateless proxy**: each request forwards a `rewind_hash` to grin-wallet's
`scan_rewind_hash`, which rescans the chain and returns matches, **storing
nothing**. That works for Grin's small UTXO set, but it is *not* a
light-wallet-server — every read re-pays the scan cost, and it has no output
store to recover derivation paths from.
A common wallet-backend approach is an **on-demand, stateless** grin scan: each
request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
rescans the chain and returns matches, **storing nothing**. That works for
Grin's small UTXO set, but it is *not* a light-wallet-server — every read
re-pays the scan cost, and it has no output store to recover derivation paths
from.
A real LWS — like monero-lws — keeps a **per-account output store** maintained by
a **background scanner**, so client reads are O(rows) not O(chain), and it can
@@ -145,33 +145,32 @@ chain_cursor(id=1 PK, height, block_hash) -- reorg-safe resume
---
## How `smirk-backend-core` proxies to it
## How a wallet backend proxies to it
Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`):
Identical wiring to a Monero LWS client — forward the view credential, serve the
stored results:
```
wallet ──JWT──▶ smirk-backend-core ──private──▶ grin-lws ──▶ grin node
/api/v1/wallet/grin/scan /get_unspent_outs (Foreign API v2)
wallet ──JWT──▶ wallet backend ──private──▶ grin-lws ──▶ grin node
/wallet/grin/scan /get_unspent_outs (Foreign API v2)
```
1. Add `GrinLwsConfig { lws_url, admin_url?, admin_key? }` in `config.rs`, next
to `LwsConfig`.
2. Add a `GrinLwsClient` in `infra/` mirroring `infra/lws/client.rs` (shared
`reqwest::Client`, timeouts, size-capped hostile-response reads, `Secret`-
wrapped admin key).
3. Behind a `features.chains.grin_lws` flag, `POST /wallet/grin/scan` forwards
the `rewind_hash` to grin-lws `/get_unspent_outs` (returning outputs **with
paths**) instead of driving grin-wallet `scan_rewind_hash` on-demand.
1. Add a grin-lws client config (`lws_url`, optional `admin_url` + `admin_key`)
alongside your existing Monero-LWS config.
2. Add an HTTP client mirroring your Monero-LWS client (shared `reqwest::Client`,
timeouts, size-capped hostile-response reads, a `Secret`-wrapped admin key).
3. Behind a feature flag, have your grin scan endpoint forward the `rewind_hash`
to grin-lws `/get_unspent_outs` (returning outputs **with paths**) instead of
driving grin-wallet `scan_rewind_hash` on-demand.
4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws
`/register` (idempotent), exactly as `fetchLwsBalance` awaits `registerLws`
for XMR/WOW.
`/register` (idempotent), exactly as you await LWS registration for Monero.
**Non-custodial guarantee preserved end to end:** the backend forwards only the
`rewind_hash` and stores nothing; grin-lws holds the `rewind_hash` for scanning
just as monero-lws holds the view key. No spend key ever leaves the wallet.
When grin-lws ships, the client can **drop** its `grin_identify_output` helper —
`/get_unspent_outs` returns paths directly.
Once grin-lws is wired in, the wallet client can **drop** any client-side
output-identify helper — `/get_unspent_outs` returns paths directly.
---
@@ -228,3 +227,12 @@ Run as a private service alongside a grin node and its database. Bind to
loopback / a private network; expose it only to the proxying backend. Add a
readiness probe on `/ready`. Everything is env-configured — no code changes to
deploy.
---
## License & provenance
MIT — see [`LICENSE`](LICENSE).
grin-lws was extracted from the [Smirk](https://smirk.cash) wallet's backend and
generalized into a standalone service that any wallet backend can run.
+6 -5
View File
@@ -1,10 +1,11 @@
//! HTTP surface — mirrors the monero-lws user API so smirk-backend-core can
//! proxy to it exactly as it proxies the XMR/WOW LWS (see `infra/lws/client.rs`).
//! HTTP surface — mirrors the monero-lws user API so a wallet backend can proxy
//! to it exactly as it proxies a Monero LWS.
//!
//! AUTH: the user API is unauthenticated at THIS layer — grin-lws binds to
//! loopback / a private network and trusts smirk-backend-core (which does the
//! JWT auth) as its only caller. The admin API (`/list_accounts`, `/rescan`) is
//! gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled when unset.
//! loopback / a private network and trusts the proxying wallet backend (which
//! does the JWT auth) as its only caller. The admin API (`/list_accounts`,
//! `/rescan`) is gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled
//! when unset.
//!
//! The `rewind_hash` in every user request is a VIEW credential (public-key
//! derived — it cannot spend). Its request DTOs omit `Debug` so it is never
+2 -3
View File
@@ -1,8 +1,7 @@
//! Error type mapped to HTTP responses.
//!
//! Hostile-upstream hygiene (carried over from smirk-backend-core's LWS/grin
//! clients): a node/DB error NEVER interpolates an untrusted upstream response
//! body into the client-facing message.
//! Hostile-upstream hygiene: a node/DB error NEVER interpolates an untrusted
//! upstream response body into the client-facing message.
use axum::{http::StatusCode, response::IntoResponse, Json};
+2 -3
View File
@@ -4,9 +4,8 @@
//! block outputs (commit + rangeproof), and relay a finalized transaction. This
//! is the ONLY component that reaches the chain; everything else reads the DB.
//!
//! Hostile-upstream hygiene (carried from smirk-backend-core's node client):
//! per-request + connect timeouts, a streaming body-size cap, and errors that
//! never interpolate an untrusted node body.
//! Hostile-upstream hygiene: per-request + connect timeouts, a streaming
//! body-size cap, and errors that never interpolate an untrusted node body.
//!
//! REWIND (the hard, deferred part): recognizing which of a block's outputs
//! belong to a registered account means rewinding each output's Bulletproof
+17 -18
View File
@@ -10,25 +10,24 @@
//!
//! WHY A DB
//! --------
//! smirk-backend-core's current `POST /wallet/grin/scan` is an ON-DEMAND,
//! STATELESS proxy: each request forwards a `rewind_hash` to grin-wallet's
//! `scan_rewind_hash`, which rescans and returns matches, storing NOTHING. That
//! is fine for grin's small UTXO set but it is NOT a light-wallet-server. A real
//! LWS — like monero-lws — keeps a per-account output store maintained by a
//! background scanner, so reads are O(rows) not O(chain). Hence this service
//! needs a database (the 3 tables in `migrations/`).
//! A typical wallet backend exposes an ON-DEMAND, STATELESS grin scan: each
//! request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
//! rescans and returns matches, storing NOTHING. That is fine for grin's small
//! UTXO set but it is NOT a light-wallet-server. A real LWS — like monero-lws —
//! keeps a per-account output store maintained by a background scanner, so reads
//! are O(rows) not O(chain). Hence this service needs a database (the 3 tables in
//! `migrations/`).
//!
//! HOW smirk-backend-core PROXIES TO IT
//! ------------------------------------
//! Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`): the backend adds
//! a `GrinLwsConfig { lws_url, admin_url?, admin_key? }` and a `GrinLwsClient`;
//! when the `grin_lws` feature is on, `POST /wallet/grin/scan` forwards the
//! `rewind_hash` to this service's `/get_unspent_outs` (and registers via
//! `/register` at balance-fetch time, idempotently, exactly as `fetchLwsBalance`
//! awaits `registerLws` for XMR/WOW). Non-custodial guarantee preserved: the
//! backend forwards only the `rewind_hash` and stores nothing; grin-lws holds
//! the `rewind_hash` for scanning just as monero-lws holds the view key. NEVER a
//! spend key.
//! HOW A WALLET BACKEND PROXIES TO IT
//! ----------------------------------
//! Identical wiring to a Monero LWS client: the backend adds a grin-lws client
//! (`lws_url`, optional `admin_url` + `admin_key`); behind a feature flag, its
//! grin scan endpoint forwards the `rewind_hash` to this service's
//! `/get_unspent_outs` (and registers via `/register` at balance-fetch time,
//! idempotently, exactly as it awaits LWS registration for Monero). Non-custodial
//! guarantee preserved: the backend forwards only the `rewind_hash` and stores
//! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws
//! holds the view key. NEVER a spend key.
//!
//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop
//! structure are real; the two chain-crypto seams — block parsing
+1 -1
View File
@@ -1,5 +1,5 @@
//! A tiny redacting wrapper so secrets (DB URL, node auth, admin key) never leak
//! into `Debug` output or tracing spans. Mirrors smirk-backend-core's `Secret`.
//! into `Debug` output or tracing spans.
#[derive(Clone)]
pub struct Secret(String);