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" 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." 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" 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 publish = false
[[bin]] [[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 recognized outputs (with recovered derivation paths) in a **database**, and
serve balance / unspent-outputs / broadcast / height fast from that store. 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 It is the Grin analogue of monero-lws: a wallet backend proxies to it exactly the
the way `smirk-backend-core`'s `xmr_wow.rs` proxies to the Monero LWS via way a backend proxies to the Monero LWS — forward a view credential, serve the
`infra/lws/client.rs`. stored results back to the wallet.
**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the **Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the
scanner loop *structure* are real and build. The two chain-crypto seams — block 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) ## Why this exists (and why it needs a DB)
`smirk-backend-core`'s current `POST /wallet/grin/scan` is an **on-demand, A common wallet-backend approach is an **on-demand, stateless** grin scan: each
stateless proxy**: each request forwards a `rewind_hash` to grin-wallet's request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
`scan_rewind_hash`, which rescans the chain and returns matches, **storing rescans the chain and returns matches, **storing nothing**. That works for
nothing**. That works for Grin's small UTXO set, but it is *not* a Grin's small UTXO set, but it is *not* a light-wallet-server — every read
light-wallet-server — every read re-pays the scan cost, and it has no output re-pays the scan cost, and it has no output store to recover derivation paths
store to recover derivation paths from. from.
A real LWS — like monero-lws — keeps a **per-account output store** maintained by 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 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 wallet ──JWT──▶ wallet backend ──private──▶ grin-lws ──▶ grin node
/api/v1/wallet/grin/scan /get_unspent_outs (Foreign API v2) /wallet/grin/scan /get_unspent_outs (Foreign API v2)
``` ```
1. Add `GrinLwsConfig { lws_url, admin_url?, admin_key? }` in `config.rs`, next 1. Add a grin-lws client config (`lws_url`, optional `admin_url` + `admin_key`)
to `LwsConfig`. alongside your existing Monero-LWS config.
2. Add a `GrinLwsClient` in `infra/` mirroring `infra/lws/client.rs` (shared 2. Add an HTTP client mirroring your Monero-LWS client (shared `reqwest::Client`,
`reqwest::Client`, timeouts, size-capped hostile-response reads, `Secret`- timeouts, size-capped hostile-response reads, a `Secret`-wrapped admin key).
wrapped admin key). 3. Behind a feature flag, have your grin scan endpoint forward the `rewind_hash`
3. Behind a `features.chains.grin_lws` flag, `POST /wallet/grin/scan` forwards to grin-lws `/get_unspent_outs` (returning outputs **with paths**) instead of
the `rewind_hash` to grin-lws `/get_unspent_outs` (returning outputs **with driving grin-wallet `scan_rewind_hash` on-demand.
paths**) instead of driving grin-wallet `scan_rewind_hash` on-demand.
4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws 4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws
`/register` (idempotent), exactly as `fetchLwsBalance` awaits `registerLws` `/register` (idempotent), exactly as you await LWS registration for Monero.
for XMR/WOW.
**Non-custodial guarantee preserved end to end:** the backend forwards only the **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 `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. 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 — Once grin-lws is wired in, the wallet client can **drop** any client-side
`/get_unspent_outs` returns paths directly. 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 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 readiness probe on `/ready`. Everything is env-configured — no code changes to
deploy. 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 //! HTTP surface — mirrors the monero-lws user API so a wallet backend can proxy
//! proxy to it exactly as it proxies the XMR/WOW LWS (see `infra/lws/client.rs`). //! to it exactly as it proxies a Monero LWS.
//! //!
//! AUTH: the user API is unauthenticated at THIS layer — grin-lws binds to //! 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 //! loopback / a private network and trusts the proxying wallet backend (which
//! JWT auth) as its only caller. The admin API (`/list_accounts`, `/rescan`) is //! does the JWT auth) as its only caller. The admin API (`/list_accounts`,
//! gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled when unset. //! `/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 //! 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 //! 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. //! Error type mapped to HTTP responses.
//! //!
//! Hostile-upstream hygiene (carried over from smirk-backend-core's LWS/grin //! Hostile-upstream hygiene: a node/DB error NEVER interpolates an untrusted
//! clients): a node/DB error NEVER interpolates an untrusted upstream response //! upstream response body into the client-facing message.
//! body into the client-facing message.
use axum::{http::StatusCode, response::IntoResponse, Json}; 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 //! block outputs (commit + rangeproof), and relay a finalized transaction. This
//! is the ONLY component that reaches the chain; everything else reads the DB. //! is the ONLY component that reaches the chain; everything else reads the DB.
//! //!
//! Hostile-upstream hygiene (carried from smirk-backend-core's node client): //! Hostile-upstream hygiene: per-request + connect timeouts, a streaming
//! per-request + connect timeouts, a streaming body-size cap, and errors that //! body-size cap, and errors that never interpolate an untrusted node body.
//! never interpolate an untrusted node body.
//! //!
//! REWIND (the hard, deferred part): recognizing which of a block's outputs //! REWIND (the hard, deferred part): recognizing which of a block's outputs
//! belong to a registered account means rewinding each output's Bulletproof //! belong to a registered account means rewinding each output's Bulletproof
+17 -18
View File
@@ -10,25 +10,24 @@
//! //!
//! WHY A DB //! WHY A DB
//! -------- //! --------
//! smirk-backend-core's current `POST /wallet/grin/scan` is an ON-DEMAND, //! A typical wallet backend exposes an ON-DEMAND, STATELESS grin scan: each
//! STATELESS proxy: each request forwards a `rewind_hash` to grin-wallet's //! request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
//! `scan_rewind_hash`, which rescans and returns matches, storing NOTHING. That //! rescans and returns matches, storing NOTHING. That is fine for grin's small
//! is fine for grin's small UTXO set but it is NOT a light-wallet-server. A real //! UTXO set but it is NOT a light-wallet-server. A real LWS — like monero-lws —
//! LWS — like monero-lws — keeps a per-account output store maintained by a //! keeps a per-account output store maintained by a background scanner, so reads
//! background scanner, so reads are O(rows) not O(chain). Hence this service //! are O(rows) not O(chain). Hence this service needs a database (the 3 tables in
//! needs a database (the 3 tables in `migrations/`). //! `migrations/`).
//! //!
//! 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`): the backend adds //! Identical wiring to a Monero LWS client: the backend adds a grin-lws client
//! a `GrinLwsConfig { lws_url, admin_url?, admin_key? }` and a `GrinLwsClient`; //! (`lws_url`, optional `admin_url` + `admin_key`); behind a feature flag, its
//! when the `grin_lws` feature is on, `POST /wallet/grin/scan` forwards the //! grin scan endpoint forwards the `rewind_hash` to this service's
//! `rewind_hash` to this service's `/get_unspent_outs` (and registers via //! `/get_unspent_outs` (and registers via `/register` at balance-fetch time,
//! `/register` at balance-fetch time, idempotently, exactly as `fetchLwsBalance` //! idempotently, exactly as it awaits LWS registration for Monero). Non-custodial
//! awaits `registerLws` for XMR/WOW). Non-custodial guarantee preserved: the //! guarantee preserved: the backend forwards only the `rewind_hash` and stores
//! backend forwards only the `rewind_hash` and stores nothing; grin-lws holds //! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws
//! the `rewind_hash` for scanning just as monero-lws holds the view key. NEVER a //! holds the view key. NEVER a spend key.
//! spend key.
//! //!
//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop //! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop
//! structure are real; the two chain-crypto seams — block parsing //! 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 //! 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)] #[derive(Clone)]
pub struct Secret(String); pub struct Secret(String);