diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ad59aaf2b..a5f1ad647 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -28,6 +28,8 @@ package may import from a higher layer. | [`lib/recovery`](lib/recovery/) | Immutable recovery proof graph, session state machine, TLV codec for unilateral exit | | [`unrollplan`](unrollplan/) | Pure dependency-resolution planner driving unilateral-exit broadcast/sweep ordering | | [`vhtlcrecovery`](vhtlcrecovery/) | Durable control-plane types for vHTLC on-chain recovery jobs (action, state, script parameters, swap linkage) | +| [`coinselect`](coinselect/) | Shared coin-type-agnostic largest-first coin-selection algorithm used by `vtxo` (reservations) and `swapwallet` (send preview) | +| [`credit`](credit/) | Durable protofsm-actor subsystem driving server-side credit account pay/receive/redeem operations, reconciling Ark top-ups and wallet-owned auto-redeem | ### Layer 2: Infrastructure (Chain, Storage, Messaging) @@ -36,6 +38,8 @@ package may import from a higher layer. | [`baselib`](baselib/) | Actor framework (`baselib/actor`) and protofsm state machine engine (`baselib/protofsm`) | | [`chainsource`](chainsource/) | `ChainBackend` interface: fee estimation, block/conf/spend notifications | | [`chainbackends`](chainbackends/) | LND-backed `ChainBackend` implementation plus lndclient adapters (`TxBroadcaster`, `PackageSubmitter`) | +| [`chainbackends/lndsubmitter`](chainbackends/lndsubmitter/) | `PackageSubmitter` implementation relaying v3/TRUC CPFP packages via lnd's `WalletKit.SubmitPackage` RPC (lnd-wallet alternative to `chainbackends/bitcoindrpc`) | +| [`chainfees`](chainfees/) | `chainfee.Estimator` implementations (WalletKit-backed, mempool.space) and a min-selecting combinator | | [`chain`](chain/) | Bitcoind RPC utilities (package relay, `SubmitPackage`) | | [`txconfirm`](txconfirm/) | Generic "broadcast + CPFP fee-bump + notify on confirm" actor with per-parent fee-input reservations and BIP-125 Rule 3/4 enforcement | | [`unroll`](unroll/) | Durable per-target unilateral-exit actor + thin registry: owns proof assembly, materialization, CSV maturity, final sweep build, persist-before-broadcast, and control-plane record persistence | @@ -48,6 +52,7 @@ package may import from a higher layer. | [`vhtlcrecovery/coordinator`](vhtlcrecovery/coordinator/) | Runtime coordinator for durable vHTLC recovery jobs: arms, escalates into unroll, cancels, and reconciles after restart | | [`vhtlcrecovery/unrollpolicy`](vhtlcrecovery/unrollpolicy/) | Adapter that resolves `(exit_policy_kind, recovery_id)` into a concrete `unroll.ExitSpendPolicy` for vHTLC claim and refund exits | | [`db`](db/) | SQLite/PostgreSQL persistence: boarding, rounds, VTXOs, OOR artifacts, fee ledger | +| [`internal/sqlbase`](internal/sqlbase/) | `walletdb.DB` implementation emulating bbolt's nested bucket/key-value model over `database/sql`, gated to `js && wasm` builds; backs `lwwallet`'s browser OPFS SQLite store | | [`mailbox`](mailbox/) | Mailbox protocol primitives across three sub-packages (pb, rpc, conn) | | [`serverconn`](serverconn/) | Unified server connector: durable egress, ingress polling, unary RPC facade | | [`serverconn/mailboxpull`](serverconn/mailboxpull/) | Shared exponential-backoff retry primitives for mailbox pull loops (used by serverconn ingress and SDK swap consumers) | @@ -61,10 +66,13 @@ package may import from a higher layer. | [`sdk/ark`](sdk/ark/) | Consumer-facing Go SDK facade: remote or embedded daemon access with typed models | | [`sdk/swaps`](sdk/swaps/) | Lightning-to-Ark / Ark-to-Lightning atomic swap SDK with durable FSM flows | | [`sdk/walletdk`](sdk/walletdk/) | Wallet-shaped SDK facade for host apps: embeds the daemon in-process, dials it over a private bufconn transport, exposes typed methods for the seven core wallet verbs (create, unlock, send, recv, list, balance, exit). The highest-level layer in the stack; wraps `walletdkrpc.WalletService`. Wallet RPC methods gated behind `walletdkrpc` (which transitively requires `swapruntime`) | +| [`sdk/walletdk/mobile`](sdk/walletdk/mobile/) | Gomobile-safe JSON facade over `sdk/walletdk` for iOS/Android host bindings, built via `gen_bindings.sh` | | [`swapwallet`](swapwallet/) | Optional daemon-side `walletdkrpc.WalletService` implementation (build tags `walletdkrpc swapruntime`): composes the swap subsystem, cooperative leave, boarding, ledger, and unilateral-exit registry behind one flat, swap-vocabulary-free wallet API | | [`swapclientserver`](swapclientserver/) | Optional daemon-side swap subserver (build tag `swapruntime`): translates `swapclientrpc` RPCs into `sdk/swaps` operations and manages the daemon-local worker registry | | [`cmd/darepod`](cmd/darepod/) | Daemon entry point | | [`cmd/darepocli`](cmd/darepocli/) | CLI client | +| [`cmd/walletdk-wasm`](cmd/walletdk-wasm/) | Browser js/wasm bridge exposing `sdk/walletdk/mobile`'s JSON facade to JavaScript via a single `walletdkCall` Promise dispatcher | +| [`rpcauth`](rpcauth/) | Shared TLS-cert and macaroon-credential helpers used by `darepod`'s server and clients (`darepocli`, wallet SDK) to secure and dial the gRPC/REST surface | | [`timeout`](timeout/) | Generic timeout scheduling actor | | [`indexer`](indexer/) | Server indexing client for receive script registration | | [`arkrpc`](arkrpc/) | Server-side gRPC service definitions (ArkService, IndexerService) | diff --git a/chainbackends/AGENTS.md b/chainbackends/AGENTS.md index 3ea8f2727..9cc79eed5 100644 --- a/chainbackends/AGENTS.md +++ b/chainbackends/AGENTS.md @@ -14,9 +14,11 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. - `TxBroadcaster` — Interface over transaction broadcasting (wraps lndclient.WalletKitClient or in-process lnd). - `PackageSubmitter` — Optional interface for v3 package relay: - `SubmitPackage(ctx, parents, child, maxFeeRate)`. Used by backends that need - a direct bitcoind path for atomic parent+child submission; absent in - environments that do not support package relay. + `SubmitPackage(ctx, parents, child, maxFeeRate)`. Pluggable: implementations + exist for a direct bitcoind path (`chainbackends/bitcoindrpc`) and for + relaying through lnd's own `WalletKit.SubmitPackage` RPC + (`chainbackends/lndsubmitter`); absent in environments that do not support + package relay. - `LndClientTxBroadcaster` — Implements `TxBroadcaster` using `lndclient.WalletKitClient`. - `LndClientFeeEstimator` — Type alias for @@ -48,20 +50,32 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. - **Depends on**: `chainsource` (implements `ChainBackend` interface). - **Depended on by**: `darepod` (instantiates backend and wires a - `PackageSubmitter` from operator config: production uses - `chainbackends/bitcoindrpc.PackageSubmitter` directly, itests inject the - same submitter from the harness). + `PackageSubmitter`: an explicitly configured submitter — production's + `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are set, or + the itest harness's injected submitter via `darepod.Config.PackageSubmitter` + — takes precedence; otherwise `darepod` falls back to + `chainbackends/lndsubmitter.New(lndSvc.WalletKit)` so an lnd-wallet-backed + daemon can relay packages through lnd's own chain connection with no + separate bitcoind RPC or Esplora endpoint); `chainbackends/lndsubmitter` + (sibling sub-package that implements `PackageSubmitter` against lnd's + `WalletKit.SubmitPackage` RPC). ## Invariants - `LNDBackend` requires an lnd instance (local or remote via lndclient). - Provides real-time notifications via lnd's chainntnfs package. -- `PackageSubmitter` is optional; package-capable backends return an error - from `SubmitPackage` when no submitter is set. In production `cmd/darepod` - injects - `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are - configured; the itest harness injects the same type via - `darepod.Config.PackageSubmitter`. +- `PackageSubmitter` is optional at the `LNDBackend` level; + `SubmitPackage` returns an error when no submitter is set. In practice + `cmd/darepod` always wires one for the lnd wallet backend: an explicit + submitter (bitcoind flags / itest harness) takes precedence, otherwise it + defaults to `chainbackends/lndsubmitter` backed by lnd's own WalletKit. +- When the configured submitter reports lnd's neutrino + `"broadcast-unverified"` sentinel (`lndNeutrinoBroadcastMsg`) with no + per-tx errors, `LNDBackend.SubmitPackage` treats it as a successful + best-effort broadcast rather than a rejection — a light client has no + mempool and cannot return a real package-accept verdict, so it broadcasts + each tx individually over P2P and relies on peer relay/confirmation to + decide the outcome. - `LndClientChainNotifier` enforces a 15-second timeout on registration to prevent hanging under LND block load. - Log messages use canonical txid strings (not reversed byte slices). diff --git a/chainbackends/CLAUDE.md b/chainbackends/CLAUDE.md index 3ea8f2727..9cc79eed5 100644 --- a/chainbackends/CLAUDE.md +++ b/chainbackends/CLAUDE.md @@ -14,9 +14,11 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. - `TxBroadcaster` — Interface over transaction broadcasting (wraps lndclient.WalletKitClient or in-process lnd). - `PackageSubmitter` — Optional interface for v3 package relay: - `SubmitPackage(ctx, parents, child, maxFeeRate)`. Used by backends that need - a direct bitcoind path for atomic parent+child submission; absent in - environments that do not support package relay. + `SubmitPackage(ctx, parents, child, maxFeeRate)`. Pluggable: implementations + exist for a direct bitcoind path (`chainbackends/bitcoindrpc`) and for + relaying through lnd's own `WalletKit.SubmitPackage` RPC + (`chainbackends/lndsubmitter`); absent in environments that do not support + package relay. - `LndClientTxBroadcaster` — Implements `TxBroadcaster` using `lndclient.WalletKitClient`. - `LndClientFeeEstimator` — Type alias for @@ -48,20 +50,32 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. - **Depends on**: `chainsource` (implements `ChainBackend` interface). - **Depended on by**: `darepod` (instantiates backend and wires a - `PackageSubmitter` from operator config: production uses - `chainbackends/bitcoindrpc.PackageSubmitter` directly, itests inject the - same submitter from the harness). + `PackageSubmitter`: an explicitly configured submitter — production's + `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are set, or + the itest harness's injected submitter via `darepod.Config.PackageSubmitter` + — takes precedence; otherwise `darepod` falls back to + `chainbackends/lndsubmitter.New(lndSvc.WalletKit)` so an lnd-wallet-backed + daemon can relay packages through lnd's own chain connection with no + separate bitcoind RPC or Esplora endpoint); `chainbackends/lndsubmitter` + (sibling sub-package that implements `PackageSubmitter` against lnd's + `WalletKit.SubmitPackage` RPC). ## Invariants - `LNDBackend` requires an lnd instance (local or remote via lndclient). - Provides real-time notifications via lnd's chainntnfs package. -- `PackageSubmitter` is optional; package-capable backends return an error - from `SubmitPackage` when no submitter is set. In production `cmd/darepod` - injects - `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are - configured; the itest harness injects the same type via - `darepod.Config.PackageSubmitter`. +- `PackageSubmitter` is optional at the `LNDBackend` level; + `SubmitPackage` returns an error when no submitter is set. In practice + `cmd/darepod` always wires one for the lnd wallet backend: an explicit + submitter (bitcoind flags / itest harness) takes precedence, otherwise it + defaults to `chainbackends/lndsubmitter` backed by lnd's own WalletKit. +- When the configured submitter reports lnd's neutrino + `"broadcast-unverified"` sentinel (`lndNeutrinoBroadcastMsg`) with no + per-tx errors, `LNDBackend.SubmitPackage` treats it as a successful + best-effort broadcast rather than a rejection — a light client has no + mempool and cannot return a real package-accept verdict, so it broadcasts + each tx individually over P2P and relies on peer relay/confirmation to + decide the outcome. - `LndClientChainNotifier` enforces a 15-second timeout on registration to prevent hanging under LND block load. - Log messages use canonical txid strings (not reversed byte slices). diff --git a/chainbackends/lndsubmitter/AGENTS.md b/chainbackends/lndsubmitter/AGENTS.md new file mode 100644 index 000000000..ff583d1d6 --- /dev/null +++ b/chainbackends/lndsubmitter/AGENTS.md @@ -0,0 +1,46 @@ +# lndsubmitter + +## Purpose + +Implements `chainbackends.PackageSubmitter` by relaying v3/TRUC CPFP packages +through lnd's own `WalletKit.SubmitPackage` RPC. Lets a darepod running the +lnd wallet backend broadcast zero-fee unilateral-exit packages without a +separate bitcoind RPC or Esplora endpoint. + +## Key Types + +- `Submitter` — Relays a parents-first, child-last package to lnd's + `WalletKit.SubmitPackage` RPC and maps the lndclient-native result back to + `btcjson.SubmitPackageResult`. Constructed via `New(walletKit)`. +- `walletKitSubmitter` — Unexported interface narrowing + `lndclient.WalletKitClient` down to just `SubmitPackage`, so tests can fake + it without a full lndclient mock. + +## Relationships + +- **Depends on**: `lndclient` (WalletKit RPC client and + `SubmitPackageResult`), `lnd/lnwallet/chainfee` (`SatPerVByte` for the + max-fee-rate ceiling), `btcd/btcjson` (result type returned to callers), + `btcd/wire` (`MsgTx`). +- **Depended on by**: `darepod` (`darepod/server.go` constructs + `lndsubmitter.New(lndSvc.WalletKit)` and wires it in as the + `chainbackends.PackageSubmitter` when the daemon runs the lnd wallet + backend, instead of the bitcoind-direct `chainbackends/bitcoindrpc` + submitter). + +## Invariants + +- `SubmitPackage` rejects a nil child or any nil parent up front with a typed + error, rather than letting lndclient/wire serialization panic on a nil + pointer deep in the call stack. +- The optional `maxFeeRate` ceiling arrives as BTC/kvB (bitcoind's + `maxfeerate` shape, per the `chainbackends.PackageSubmitter` contract) and + must be converted to sat/vByte for lnd's RPC by rounding to the nearest + integer, not truncating — truncation would silently make the ceiling + stricter than the caller asked for (e.g. 12.5 sat/vByte → 12). +- `mapResult` only sets a `TxResults` entry's `Error` field when lnd reported + a non-empty rejection reason; an empty string means the tx was accepted. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/chainbackends/lndsubmitter/CLAUDE.md b/chainbackends/lndsubmitter/CLAUDE.md new file mode 100644 index 000000000..ff583d1d6 --- /dev/null +++ b/chainbackends/lndsubmitter/CLAUDE.md @@ -0,0 +1,46 @@ +# lndsubmitter + +## Purpose + +Implements `chainbackends.PackageSubmitter` by relaying v3/TRUC CPFP packages +through lnd's own `WalletKit.SubmitPackage` RPC. Lets a darepod running the +lnd wallet backend broadcast zero-fee unilateral-exit packages without a +separate bitcoind RPC or Esplora endpoint. + +## Key Types + +- `Submitter` — Relays a parents-first, child-last package to lnd's + `WalletKit.SubmitPackage` RPC and maps the lndclient-native result back to + `btcjson.SubmitPackageResult`. Constructed via `New(walletKit)`. +- `walletKitSubmitter` — Unexported interface narrowing + `lndclient.WalletKitClient` down to just `SubmitPackage`, so tests can fake + it without a full lndclient mock. + +## Relationships + +- **Depends on**: `lndclient` (WalletKit RPC client and + `SubmitPackageResult`), `lnd/lnwallet/chainfee` (`SatPerVByte` for the + max-fee-rate ceiling), `btcd/btcjson` (result type returned to callers), + `btcd/wire` (`MsgTx`). +- **Depended on by**: `darepod` (`darepod/server.go` constructs + `lndsubmitter.New(lndSvc.WalletKit)` and wires it in as the + `chainbackends.PackageSubmitter` when the daemon runs the lnd wallet + backend, instead of the bitcoind-direct `chainbackends/bitcoindrpc` + submitter). + +## Invariants + +- `SubmitPackage` rejects a nil child or any nil parent up front with a typed + error, rather than letting lndclient/wire serialization panic on a nil + pointer deep in the call stack. +- The optional `maxFeeRate` ceiling arrives as BTC/kvB (bitcoind's + `maxfeerate` shape, per the `chainbackends.PackageSubmitter` contract) and + must be converted to sat/vByte for lnd's RPC by rounding to the nearest + integer, not truncating — truncation would silently make the ceiling + stricter than the caller asked for (e.g. 12.5 sat/vByte → 12). +- `mapResult` only sets a `TxResults` entry's `Error` field when lnd reported + a non-empty rejection reason; an empty string means the tx was accepted. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/chainfees/AGENTS.md b/chainfees/AGENTS.md new file mode 100644 index 000000000..ec225bc3c --- /dev/null +++ b/chainfees/AGENTS.md @@ -0,0 +1,62 @@ +# chainfees + +## Purpose + +Reusable `chainfee.Estimator` implementations and combinators for wallet and +daemon chain backends: an lnd `WalletKit`-backed estimator, a mempool.space +HTTP-backed estimator, and a `MinEstimator` selector that composes several +providers and picks the lowest live rate. + +## Key Types + +- `WalletKitEstimator` — Proxies `EstimateFeePerKW` to an + `lndclient.WalletKitClient`. Fail-fast by default (`NewWalletKitEstimator`); + `NewFallbackWalletKitEstimator` instead serves the last successful rate (or + fails closed before any success) rather than propagating errors. +- `MempoolSpaceEstimator` — Queries mempool.space's recommended-fee HTTP + endpoint, mapping `fastestFee`/`halfHourFee`/`hourFee`/`economyFee`/ + `minimumFee` buckets onto confirmation targets. Caches the response for + `CacheTTL` (default 30s) and rejects non-loopback plaintext HTTP endpoints. +- `MinEstimator` — Wraps one or more `NamedEstimator` children and returns the + minimum successful estimate per call; falls back to the last selected rate + (or the relay floor) only when every child fails. +- `NamedEstimator` — Pairs a `chainfee.Estimator` child with a stable `Name` + for logging inside `MinEstimator`. +- `DefaultMempoolSpaceURL(params)` — Resolves the network-specific + mempool.space recommended-fee URL (mainnet/testnet3/testnet4/signet). + +## Relationships + +- **Depends on**: `lndclient` (`WalletKitClient` for `WalletKitEstimator`), + `lnd/lnwallet/chainfee` (`Estimator` interface, `SatPerKWeight`, + `FeePerKwFloor`), `btcd/chaincfg` and `btcd/wire` (network selection in + `DefaultMempoolSpaceURL`), `btclog` (structured logging). +- **Depended on by**: `chainbackends` (`chainbackends/lndclient_adapters.go` + aliases `LndClientFeeEstimator = chainfees.WalletKitEstimator` and builds + the default lnd fee estimator via `NewFallbackWalletKitEstimator`), + `darepod` (`darepod/server.go`'s `lndFeeEstimator` composes a fail-fast + `WalletKitEstimator` and a `MempoolSpaceEstimator` under a `MinEstimator` + when the mempool.space provider is enabled; `darepod/logging.go` registers + `chainfees.Subsystem` as a log subsystem). + +## Invariants + +- A child estimator composed inside `MinEstimator` (or `WalletKitEstimator` + used there) must be fail-fast, not fallback-on-error: a stale fallback rate + could otherwise beat another provider's live estimate and win the minimum. + Never pass a `NewFallbackWalletKitEstimator` into `NewMinEstimator`. +- Every estimator clamps successful rates up to `chainfee.FeePerKwFloor` + before returning or caching them, so a cached value below the floor is the + sentinel for "no successful estimate yet" (see `WalletKitEstimator. + cachedRate`). +- `MempoolSpaceEstimator` requires an absolute `https` URL; plaintext `http` + is only accepted for a loopback host, and the HTTP response body is capped + at 64 KiB to bound memory from a misbehaving endpoint. +- `NewMinEstimator` and `NewWalletKitEstimatorWithConfig` validate inputs + (non-empty names, non-nil estimators/clients) at construction so callers + fail fast instead of panicking on first use of a malformed value boxed into + the `chainfee.Estimator` interface. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/chainfees/CLAUDE.md b/chainfees/CLAUDE.md new file mode 100644 index 000000000..ec225bc3c --- /dev/null +++ b/chainfees/CLAUDE.md @@ -0,0 +1,62 @@ +# chainfees + +## Purpose + +Reusable `chainfee.Estimator` implementations and combinators for wallet and +daemon chain backends: an lnd `WalletKit`-backed estimator, a mempool.space +HTTP-backed estimator, and a `MinEstimator` selector that composes several +providers and picks the lowest live rate. + +## Key Types + +- `WalletKitEstimator` — Proxies `EstimateFeePerKW` to an + `lndclient.WalletKitClient`. Fail-fast by default (`NewWalletKitEstimator`); + `NewFallbackWalletKitEstimator` instead serves the last successful rate (or + fails closed before any success) rather than propagating errors. +- `MempoolSpaceEstimator` — Queries mempool.space's recommended-fee HTTP + endpoint, mapping `fastestFee`/`halfHourFee`/`hourFee`/`economyFee`/ + `minimumFee` buckets onto confirmation targets. Caches the response for + `CacheTTL` (default 30s) and rejects non-loopback plaintext HTTP endpoints. +- `MinEstimator` — Wraps one or more `NamedEstimator` children and returns the + minimum successful estimate per call; falls back to the last selected rate + (or the relay floor) only when every child fails. +- `NamedEstimator` — Pairs a `chainfee.Estimator` child with a stable `Name` + for logging inside `MinEstimator`. +- `DefaultMempoolSpaceURL(params)` — Resolves the network-specific + mempool.space recommended-fee URL (mainnet/testnet3/testnet4/signet). + +## Relationships + +- **Depends on**: `lndclient` (`WalletKitClient` for `WalletKitEstimator`), + `lnd/lnwallet/chainfee` (`Estimator` interface, `SatPerKWeight`, + `FeePerKwFloor`), `btcd/chaincfg` and `btcd/wire` (network selection in + `DefaultMempoolSpaceURL`), `btclog` (structured logging). +- **Depended on by**: `chainbackends` (`chainbackends/lndclient_adapters.go` + aliases `LndClientFeeEstimator = chainfees.WalletKitEstimator` and builds + the default lnd fee estimator via `NewFallbackWalletKitEstimator`), + `darepod` (`darepod/server.go`'s `lndFeeEstimator` composes a fail-fast + `WalletKitEstimator` and a `MempoolSpaceEstimator` under a `MinEstimator` + when the mempool.space provider is enabled; `darepod/logging.go` registers + `chainfees.Subsystem` as a log subsystem). + +## Invariants + +- A child estimator composed inside `MinEstimator` (or `WalletKitEstimator` + used there) must be fail-fast, not fallback-on-error: a stale fallback rate + could otherwise beat another provider's live estimate and win the minimum. + Never pass a `NewFallbackWalletKitEstimator` into `NewMinEstimator`. +- Every estimator clamps successful rates up to `chainfee.FeePerKwFloor` + before returning or caching them, so a cached value below the floor is the + sentinel for "no successful estimate yet" (see `WalletKitEstimator. + cachedRate`). +- `MempoolSpaceEstimator` requires an absolute `https` URL; plaintext `http` + is only accepted for a loopback host, and the HTTP response body is capped + at 64 KiB to bound memory from a misbehaving endpoint. +- `NewMinEstimator` and `NewWalletKitEstimatorWithConfig` validate inputs + (non-empty names, non-nil estimators/clients) at construction so callers + fail fast instead of panicking on first use of a malformed value boxed into + the `chainfee.Estimator` interface. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/cmd/darepocli/darepoclicommands/AGENTS.md b/cmd/darepocli/darepoclicommands/AGENTS.md index 45ad83e87..14d844b8f 100644 --- a/cmd/darepocli/darepoclicommands/AGENTS.md +++ b/cmd/darepocli/darepoclicommands/AGENTS.md @@ -27,8 +27,10 @@ The CLI surface is split into three tiers: | `recv` | `walletdkrpc.Recv` / `walletdkrpc.Deposit` | Inbound. `--offchain` (default) returns a Lightning invoice; `--onchain` returns a boarding address | | `activity` | `walletdkrpc.List` | Unified wallet activity view. Defaults to table output; `--format json` returns structured JSON. `--pending` and `--kind` narrow rows | | `balance` | `walletdkrpc.Balance` | Flat balance (confirmed_sat, pending_in_sat, pending_out_sat) | -| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Trigger a unilateral exit (proxies Unroll) | +| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Queue a cooperative leave by default; starts unilateral unroll only with `--force-unroll-ack` | | `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit job's status (proxies GetUnrollStatus) | +| `exit summary` | `walletdkrpc.ExitSummary` | Wallet-wide portfolio of in-progress exits: amount recovering, estimated fees, and estimated net recoverable | +| `exit plan --outpoint TXID:VOUT` | `walletdkrpc.GetExitPlan` | Preview backing-wallet funding readiness for one or more exits (repeatable `--outpoint`) | ### Daemon introspection diff --git a/cmd/darepocli/darepoclicommands/CLAUDE.md b/cmd/darepocli/darepoclicommands/CLAUDE.md index 45ad83e87..14d844b8f 100644 --- a/cmd/darepocli/darepoclicommands/CLAUDE.md +++ b/cmd/darepocli/darepoclicommands/CLAUDE.md @@ -27,8 +27,10 @@ The CLI surface is split into three tiers: | `recv` | `walletdkrpc.Recv` / `walletdkrpc.Deposit` | Inbound. `--offchain` (default) returns a Lightning invoice; `--onchain` returns a boarding address | | `activity` | `walletdkrpc.List` | Unified wallet activity view. Defaults to table output; `--format json` returns structured JSON. `--pending` and `--kind` narrow rows | | `balance` | `walletdkrpc.Balance` | Flat balance (confirmed_sat, pending_in_sat, pending_out_sat) | -| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Trigger a unilateral exit (proxies Unroll) | +| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Queue a cooperative leave by default; starts unilateral unroll only with `--force-unroll-ack` | | `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit job's status (proxies GetUnrollStatus) | +| `exit summary` | `walletdkrpc.ExitSummary` | Wallet-wide portfolio of in-progress exits: amount recovering, estimated fees, and estimated net recoverable | +| `exit plan --outpoint TXID:VOUT` | `walletdkrpc.GetExitPlan` | Preview backing-wallet funding readiness for one or more exits (repeatable `--outpoint`) | ### Daemon introspection diff --git a/cmd/walletdk-wasm/AGENTS.md b/cmd/walletdk-wasm/AGENTS.md new file mode 100644 index 000000000..8168c9d5e --- /dev/null +++ b/cmd/walletdk-wasm/AGENTS.md @@ -0,0 +1,58 @@ +# cmd/walletdk-wasm + +## Purpose + +Browser entry point that compiles the embedded `darepod` wallet runtime to +`js/wasm` and exposes it to page JavaScript as a single `walletdkCall(method, +req)` function returning a Promise, so a web app can run the daemon, swap, +and OOR machinery in-process in the browser VM with no separate gateway. + +## Key Types + +- `main` (js/wasm build) — installs `walletdkCall` on the JS global object, + fires a `walletdk-ready` `CustomEvent`, then parks the goroutine forever so + exported callbacks stay live for the page's lifetime. +- `main` (native stub build) — prints a build-tag hint to stderr and exits 1; + keeps `go build ./...` green on toolchains that are not `js/wasm`. +- `walletCall` — the single JS dispatch point; switches on a method name + string and forwards to the matching `sdk/walletdk/mobile` verb (`start`, + `stop`, `getInfo`, `balance`, `createWallet`, `prepareSend`, `exit`, + `subscribe`, …). +- `subscriptionHandle` — wraps a `mobile.Subscription` as a JS object with + `next()`/`close()` methods, since a Go channel cannot cross into + JavaScript. +- `promise` — runs a Go closure on a fresh goroutine and resolves/rejects a JS + `Promise`, recovering panics so they never kill the Go/wasm runtime. + +## Relationships + +- **Depends on**: `sdk/walletdk/mobile` (the JSON facade this bridge + dispatches every verb to; it never calls `sdk/walletdk` directly, so it + cannot drift from the mobile bindings' behavior). +- **Depended on by**: nothing in-repo — it is a leaf `cmd/` binary built + directly by browser tooling via `GOOS=js GOARCH=wasm go build`. + +## Invariants + +- Real build requires `//go:build js && wasm` plus `-tags "mobile + walletdkrpc swapruntime"`; without those tags the daemon and swap RPCs are + compiled out of `sdk/walletdk/mobile` and every verb would fail at + runtime, so `main.go`'s tag and the build command's `-tags` must stay in + sync. +- `stub.go` (`//go:build !js || !wasm`) exists purely so `go build ./...` + succeeds on native toolchains; it must never gain real logic — real logic + belongs in `main.go` behind the js/wasm tag. +- A browser has no `$HOME`, so `startConfig` injects a fixed + `browserDataDir` ("/darepo") when the caller omits `data_dir`; removing + that default breaks `mobile.Start`'s config validation under + `wasm_exec.js`. +- Every exported `js.Func` callback (e.g. in `subscriptionHandle`, the + `promise` executor) must be `Release()`d once it can no longer fire, or it + leaks a Go callback handle for the lifetime of the page. +- This bridge is a thin dispatcher only: it must not call into + `sdk/walletdk` or any daemon/actor code directly. Add new capabilities to + `sdk/walletdk/mobile` first, then add a `case` here. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/cmd/walletdk-wasm/CLAUDE.md b/cmd/walletdk-wasm/CLAUDE.md new file mode 100644 index 000000000..8168c9d5e --- /dev/null +++ b/cmd/walletdk-wasm/CLAUDE.md @@ -0,0 +1,58 @@ +# cmd/walletdk-wasm + +## Purpose + +Browser entry point that compiles the embedded `darepod` wallet runtime to +`js/wasm` and exposes it to page JavaScript as a single `walletdkCall(method, +req)` function returning a Promise, so a web app can run the daemon, swap, +and OOR machinery in-process in the browser VM with no separate gateway. + +## Key Types + +- `main` (js/wasm build) — installs `walletdkCall` on the JS global object, + fires a `walletdk-ready` `CustomEvent`, then parks the goroutine forever so + exported callbacks stay live for the page's lifetime. +- `main` (native stub build) — prints a build-tag hint to stderr and exits 1; + keeps `go build ./...` green on toolchains that are not `js/wasm`. +- `walletCall` — the single JS dispatch point; switches on a method name + string and forwards to the matching `sdk/walletdk/mobile` verb (`start`, + `stop`, `getInfo`, `balance`, `createWallet`, `prepareSend`, `exit`, + `subscribe`, …). +- `subscriptionHandle` — wraps a `mobile.Subscription` as a JS object with + `next()`/`close()` methods, since a Go channel cannot cross into + JavaScript. +- `promise` — runs a Go closure on a fresh goroutine and resolves/rejects a JS + `Promise`, recovering panics so they never kill the Go/wasm runtime. + +## Relationships + +- **Depends on**: `sdk/walletdk/mobile` (the JSON facade this bridge + dispatches every verb to; it never calls `sdk/walletdk` directly, so it + cannot drift from the mobile bindings' behavior). +- **Depended on by**: nothing in-repo — it is a leaf `cmd/` binary built + directly by browser tooling via `GOOS=js GOARCH=wasm go build`. + +## Invariants + +- Real build requires `//go:build js && wasm` plus `-tags "mobile + walletdkrpc swapruntime"`; without those tags the daemon and swap RPCs are + compiled out of `sdk/walletdk/mobile` and every verb would fail at + runtime, so `main.go`'s tag and the build command's `-tags` must stay in + sync. +- `stub.go` (`//go:build !js || !wasm`) exists purely so `go build ./...` + succeeds on native toolchains; it must never gain real logic — real logic + belongs in `main.go` behind the js/wasm tag. +- A browser has no `$HOME`, so `startConfig` injects a fixed + `browserDataDir` ("/darepo") when the caller omits `data_dir`; removing + that default breaks `mobile.Start`'s config validation under + `wasm_exec.js`. +- Every exported `js.Func` callback (e.g. in `subscriptionHandle`, the + `promise` executor) must be `Release()`d once it can no longer fire, or it + leaks a Go callback handle for the lifetime of the page. +- This bridge is a thin dispatcher only: it must not call into + `sdk/walletdk` or any daemon/actor code directly. Add new capabilities to + `sdk/walletdk/mobile` first, then add a `case` here. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/coinselect/AGENTS.md b/coinselect/AGENTS.md new file mode 100644 index 000000000..3a493df7e --- /dev/null +++ b/coinselect/AGENTS.md @@ -0,0 +1,45 @@ +# coinselect + +## Purpose + +Single, coin-type-agnostic largest-first coin-selection algorithm shared +across the client, so the VTXO manager's reservation path and the swap +wallet's send preview select through one implementation instead of growing +parallel ones. + +## Key Types + +- `Request` — selection parameters: a `Target` to cover, an optional + `MinChange` dust floor, or `SweepAll` to select every candidate. +- `Result[T]` — outcome of a pass: the `Selected` subset, its `Total`, and + the resulting `Change`. +- `AmountFunc[T]` — caller-supplied extractor of a candidate's + `btcutil.Amount`, the seam that keeps the selector agnostic to the + concrete coin type (VTXO descriptor, RPC VTXO, boarding intent, ...). + +## Relationships + +- **Depends on**: nothing in-repo — only `btcsuite/btcd/btcutil/v2` and the + standard library. Deliberately holds no wallet, actor, or RPC + dependencies. +- **Depended on by**: `vtxo` (reservation coin selection in + `manager.go`), `swapwallet` (send-preview coin selection in + `router.go`). + +## Invariants + +- `LargestFirst` never mutates the caller's candidate slice; it sorts a + copy. +- `SweepAll` takes precedence over `Target`/`MinChange` and selects every + candidate regardless of their value. +- An exact-fit (zero-change) selection is always accepted even when + `MinChange` is set; only a non-zero change below `MinChange` is + rejected in favor of continuing to accumulate (`ErrChangeBelowMin`). +- On failure the returned `Result.Total` still carries the relevant + covered/rejected total so callers can build precise diagnostics from + the typed errors (`ErrSelectionShortfall`, `ErrChangeBelowMin`, + `ErrNoCandidates`, `ErrInvalidTarget`). + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/coinselect/CLAUDE.md b/coinselect/CLAUDE.md new file mode 100644 index 000000000..3a493df7e --- /dev/null +++ b/coinselect/CLAUDE.md @@ -0,0 +1,45 @@ +# coinselect + +## Purpose + +Single, coin-type-agnostic largest-first coin-selection algorithm shared +across the client, so the VTXO manager's reservation path and the swap +wallet's send preview select through one implementation instead of growing +parallel ones. + +## Key Types + +- `Request` — selection parameters: a `Target` to cover, an optional + `MinChange` dust floor, or `SweepAll` to select every candidate. +- `Result[T]` — outcome of a pass: the `Selected` subset, its `Total`, and + the resulting `Change`. +- `AmountFunc[T]` — caller-supplied extractor of a candidate's + `btcutil.Amount`, the seam that keeps the selector agnostic to the + concrete coin type (VTXO descriptor, RPC VTXO, boarding intent, ...). + +## Relationships + +- **Depends on**: nothing in-repo — only `btcsuite/btcd/btcutil/v2` and the + standard library. Deliberately holds no wallet, actor, or RPC + dependencies. +- **Depended on by**: `vtxo` (reservation coin selection in + `manager.go`), `swapwallet` (send-preview coin selection in + `router.go`). + +## Invariants + +- `LargestFirst` never mutates the caller's candidate slice; it sorts a + copy. +- `SweepAll` takes precedence over `Target`/`MinChange` and selects every + candidate regardless of their value. +- An exact-fit (zero-change) selection is always accepted even when + `MinChange` is set; only a non-zero change below `MinChange` is + rejected in favor of continuing to accumulate (`ErrChangeBelowMin`). +- On failure the returned `Result.Total` still carries the relevant + covered/rejected total so callers can build precise diagnostics from + the typed errors (`ErrSelectionShortfall`, `ErrChangeBelowMin`, + `ErrNoCandidates`, `ErrInvalidTarget`). + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/credit/AGENTS.md b/credit/AGENTS.md new file mode 100644 index 000000000..bdafab7e0 --- /dev/null +++ b/credit/AGENTS.md @@ -0,0 +1,88 @@ +# credit + +## Purpose + +Durable actor subsystem that admits and drives server-side "credit" +account operations (sub-dust/shortfall pay, Lightning receive, redeem) +against the swap server's credit ledger, folding Ark top-ups and +wallet-owned auto-redeem into a single per-operation protofsm state +machine. + +## Key Types + +- `Registry` — plain in-memory supervisor actor. Admits operations by + writing their control-plane row, spawns/routes/reaps per-operation + durable children, restores in-flight operations on boot, and runs the + wallet-owned auto-redeem policy. +- `OpActor` — durable per-operation actor wrapping one pay/receive/redeem + operation's protofsm state machine (`opBehavior`), running on the + Read/Stage/Commit path. +- `CreditServer` — swap-server credit and pay surface the actor drives + (`CreateCredit`, `ListCredits`, `RedeemCredit`, `StartPay`); implemented + in production by `swapclientserver`'s bridge. +- `CreditDaemon` — local wallet/daemon surface the actor drives + (`IdentityPubKey`, `DustLimit`, `SendOOR`, `AllocateReceiveScript`, + `FindLiveVTXOByPkScript`). +- `Store` — durable control-plane store interface for + `credit_operations` rows; `*db.CreditOperationStoreDB` in production. +- `CreditTransitionTable` (`CreditTransitions`) — the static protofsm + transition table for the quoting/top-up/pay/receive/redeem state + machine, documented alongside the live dispatch in `transitions.go`. + +## Relationships + +- **Depends on**: `baselib/actor` (durable actor, mailbox, service-key + framework), `baselib/protofsm` (state, transition, and emitted-event + generics the FSM is built on), `db` (`CreditOperationRecord`, + `CreditOpKind`, `CreditOpStatus` schema), `timeout` (poll/retry timer + scheduling), `build` (logger-from-context). +- **Depended on by**: `swapwallet` (`router.go` admits credit/mixed pays, + `recv.go` admits credit receives, `credit_projector.go` lists ops and + projects terminal transitions into wallet entries, `deps.go` holds the + registry ref), `swapclientserver` (`credit_bridge.go` implements + `CreditServer`/`CreditDaemon` against the swap-server RPC and the + wallet), `darepod` (`credit_registry.go` constructs and starts the + `Registry` at daemon boot; `config.go`/`server.go` wire the ref through + `Config.Swap`). + +- **Sends**: + - → `timeout`: `ScheduleTimeoutRequest` (arms the reconciliation poll + timer for an awaiting state). +- **Receives**: + - ← `swapwallet`: `StartCreditPayRequest`, `StartCreditReceiveRequest`, + `ListCreditOpsRequest`. + - ← `timeout`: `*timeout.ExpiredMsg`, bridged by `NewRetryCallbackRef` + into a `ResumeCreditOpRequest` told to the registry. + +## Invariants + +- The supervisor (`Registry`) holds no durable state of its own: it + always writes the control-plane row in an ordinary transaction + *before* spawning or resuming the owning child, so a crash between the + write and the spawn is recovered by `RestoreNonTerminal` on the next + boot. +- Every external call an operation makes (`CreateCredit`, `SendOOR`, + `ListCredits`, `StartPay`, `RedeemCredit`) is idempotent by the op key + or the invoice payment hash, so redelivery after a crash never + double-executes an effect. +- A `stageRecord` outbox directive must be flushed (Stage write) before + the next state runs its side effect — the persist-before-effect + invariant that lets a crash re-drive from the checkpointed state + instead of re-deriving an identifier the in-flight effect no longer + matches. +- Only `ResumeCreditOpRequest` crosses a per-operation child's durable + mailbox at the application level (plus the framework-injected + `RestartMessage`); every other admission detail is reloaded from the + durable row rather than redelivered as a message. +- Redemption is never user-triggered: the wallet's auto-redeem policy + (steady-state via a settled receive, boot-time via a single + reconcile) is the only source of `RedeemRequest` admissions, gated by + a no-pending-pay/redeem interlock in the registry. +- Persisted `State` string values (`state.go`) must stay stable across + versions and match the `String()` methods of the concrete FSM states + in `states.go` exactly; an unrecognized string is treated as a corrupt + row and driven to a durable failure rather than silently retried. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/credit/CLAUDE.md b/credit/CLAUDE.md new file mode 100644 index 000000000..bdafab7e0 --- /dev/null +++ b/credit/CLAUDE.md @@ -0,0 +1,88 @@ +# credit + +## Purpose + +Durable actor subsystem that admits and drives server-side "credit" +account operations (sub-dust/shortfall pay, Lightning receive, redeem) +against the swap server's credit ledger, folding Ark top-ups and +wallet-owned auto-redeem into a single per-operation protofsm state +machine. + +## Key Types + +- `Registry` — plain in-memory supervisor actor. Admits operations by + writing their control-plane row, spawns/routes/reaps per-operation + durable children, restores in-flight operations on boot, and runs the + wallet-owned auto-redeem policy. +- `OpActor` — durable per-operation actor wrapping one pay/receive/redeem + operation's protofsm state machine (`opBehavior`), running on the + Read/Stage/Commit path. +- `CreditServer` — swap-server credit and pay surface the actor drives + (`CreateCredit`, `ListCredits`, `RedeemCredit`, `StartPay`); implemented + in production by `swapclientserver`'s bridge. +- `CreditDaemon` — local wallet/daemon surface the actor drives + (`IdentityPubKey`, `DustLimit`, `SendOOR`, `AllocateReceiveScript`, + `FindLiveVTXOByPkScript`). +- `Store` — durable control-plane store interface for + `credit_operations` rows; `*db.CreditOperationStoreDB` in production. +- `CreditTransitionTable` (`CreditTransitions`) — the static protofsm + transition table for the quoting/top-up/pay/receive/redeem state + machine, documented alongside the live dispatch in `transitions.go`. + +## Relationships + +- **Depends on**: `baselib/actor` (durable actor, mailbox, service-key + framework), `baselib/protofsm` (state, transition, and emitted-event + generics the FSM is built on), `db` (`CreditOperationRecord`, + `CreditOpKind`, `CreditOpStatus` schema), `timeout` (poll/retry timer + scheduling), `build` (logger-from-context). +- **Depended on by**: `swapwallet` (`router.go` admits credit/mixed pays, + `recv.go` admits credit receives, `credit_projector.go` lists ops and + projects terminal transitions into wallet entries, `deps.go` holds the + registry ref), `swapclientserver` (`credit_bridge.go` implements + `CreditServer`/`CreditDaemon` against the swap-server RPC and the + wallet), `darepod` (`credit_registry.go` constructs and starts the + `Registry` at daemon boot; `config.go`/`server.go` wire the ref through + `Config.Swap`). + +- **Sends**: + - → `timeout`: `ScheduleTimeoutRequest` (arms the reconciliation poll + timer for an awaiting state). +- **Receives**: + - ← `swapwallet`: `StartCreditPayRequest`, `StartCreditReceiveRequest`, + `ListCreditOpsRequest`. + - ← `timeout`: `*timeout.ExpiredMsg`, bridged by `NewRetryCallbackRef` + into a `ResumeCreditOpRequest` told to the registry. + +## Invariants + +- The supervisor (`Registry`) holds no durable state of its own: it + always writes the control-plane row in an ordinary transaction + *before* spawning or resuming the owning child, so a crash between the + write and the spawn is recovered by `RestoreNonTerminal` on the next + boot. +- Every external call an operation makes (`CreateCredit`, `SendOOR`, + `ListCredits`, `StartPay`, `RedeemCredit`) is idempotent by the op key + or the invoice payment hash, so redelivery after a crash never + double-executes an effect. +- A `stageRecord` outbox directive must be flushed (Stage write) before + the next state runs its side effect — the persist-before-effect + invariant that lets a crash re-drive from the checkpointed state + instead of re-deriving an identifier the in-flight effect no longer + matches. +- Only `ResumeCreditOpRequest` crosses a per-operation child's durable + mailbox at the application level (plus the framework-injected + `RestartMessage`); every other admission detail is reloaded from the + durable row rather than redelivered as a message. +- Redemption is never user-triggered: the wallet's auto-redeem policy + (steady-state via a settled receive, boot-time via a single + reconcile) is the only source of `RedeemRequest` admissions, gated by + a no-pending-pay/redeem interlock in the registry. +- Persisted `State` string values (`state.go`) must stay stable across + versions and match the `String()` methods of the concrete FSM states + in `states.go` exactly; an unrecognized string is treated as a corrupt + row and driven to a durable failure rather than silently retried. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/darepod/AGENTS.md b/darepod/AGENTS.md index 57f51ff42..ebeb8a237 100644 --- a/darepod/AGENTS.md +++ b/darepod/AGENTS.md @@ -76,7 +76,29 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep daemon-local handoff. `GetUnrollStatus` is read-through: prefers live registry via `queryUnrollRegistry`, falls back to `db.UnilateralExitPersistenceStore.GetJob`. Returns `Found=false` - (not error) when neither layer has a record. + (not error) when neither layer has a record. With `Detailed=true` + it additionally calls `enrichUnrollDetail` (live path) or + `enrichExitFees` directly (persisted-only path) to fill a + human `PhaseDetail` line (`unrollPhaseDetail`) and an + `UnrollFees` breakdown (CPFP/sweep/total/net plus `SpentSoFarSat`, + computed by `spentSoFarSat`) projected from the persisted + descriptor via `unroll.PlanExitFunding` — the same cost model + `GetExitPlan` and `ExitSummary` use. Both enrichment helpers are + best-effort: a missing live actor, unloaded planner, or + descriptor/fee-estimate failure just leaves the field unset + rather than failing the probe. +- `ExitSummary` — wallet-wide portfolio of every non-terminal + unilateral-exit job (`ueStore.ListNonTerminalJobs`), returning + `ExitSummaryResult{Entries []ExitSummaryEntry, TotalExits, + TotalVTXOAmountSat, TotalEstFeeSat, TotalEstNetRecoveredSat}`. + Deliberately cheap: it does not query live actors, projecting each + entry's amount/fee/net-recovered fields from the persisted VTXO + descriptor with a nil lineage (approximated from `ChainDepth`) and + a zero wallet snapshot via the shared `unroll.PlanExitFunding` + model (`exitSummaryEntry`). A fee-estimate failure degrades to a + zero rate (zeroing only the fee columns) instead of failing the + call; a per-outpoint descriptor lookup failure yields a phase-only + entry with zeroed amounts instead of failing the whole summary. - `unrollPhaseToProto` / `unrollJobStatusToProto` — dual mappers from live `unroll.Phase` and persisted `db.UnilateralExitJobStatus` to the same proto. `PhaseSweepBroadcast` and `PhaseSweepConfirmation` @@ -351,6 +373,19 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep - `OORConfig.OOR.Limits.MaxMailboxScriptBytes` must be at least `minOORMailboxScriptBytes = 34` (P2TR script length); validated during `Config.Validate()`. +- `spentSoFarSat` prorates the CPFP total over broadcast (confirmed + + in-flight) proof txs by multiplying by the broadcast count **before** + dividing by `progress.TotalTxs` — dividing per-child first truncates + to zero whenever the CPFP total is smaller than the tx count. + Numerator and denominator must count the same proof-graph universe + (deduped node count), since it's an estimate, not accounting: a + confirmed proof tx may be a shared ancestor another party paid the + CPFP for. The sweep leg is added only once `sweepBuilt` is true. +- `unrollPhaseDetail`'s materializing line clamps its 1-based + `CurrentLayer+1` display to `TotalLayers`: the frontier layer + collapses to `TotalLayers` once every proof node confirms, so + without the clamp a job read as `MATERIALIZING` right after the + frontier collapses would render "layer N+1 of N". - `Config.EagerRoundJoin` is seeded by build-tag-aware `defaultEagerRoundJoin()`: `false` on the standalone non-walletdkrpc build, `true` under the `walletdkrpc` tag (both `cmd/darepod` and @@ -364,5 +399,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep - [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — Installation, configuration, CLI reference. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/darepod/CLAUDE.md b/darepod/CLAUDE.md index 57f51ff42..ebeb8a237 100644 --- a/darepod/CLAUDE.md +++ b/darepod/CLAUDE.md @@ -76,7 +76,29 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep daemon-local handoff. `GetUnrollStatus` is read-through: prefers live registry via `queryUnrollRegistry`, falls back to `db.UnilateralExitPersistenceStore.GetJob`. Returns `Found=false` - (not error) when neither layer has a record. + (not error) when neither layer has a record. With `Detailed=true` + it additionally calls `enrichUnrollDetail` (live path) or + `enrichExitFees` directly (persisted-only path) to fill a + human `PhaseDetail` line (`unrollPhaseDetail`) and an + `UnrollFees` breakdown (CPFP/sweep/total/net plus `SpentSoFarSat`, + computed by `spentSoFarSat`) projected from the persisted + descriptor via `unroll.PlanExitFunding` — the same cost model + `GetExitPlan` and `ExitSummary` use. Both enrichment helpers are + best-effort: a missing live actor, unloaded planner, or + descriptor/fee-estimate failure just leaves the field unset + rather than failing the probe. +- `ExitSummary` — wallet-wide portfolio of every non-terminal + unilateral-exit job (`ueStore.ListNonTerminalJobs`), returning + `ExitSummaryResult{Entries []ExitSummaryEntry, TotalExits, + TotalVTXOAmountSat, TotalEstFeeSat, TotalEstNetRecoveredSat}`. + Deliberately cheap: it does not query live actors, projecting each + entry's amount/fee/net-recovered fields from the persisted VTXO + descriptor with a nil lineage (approximated from `ChainDepth`) and + a zero wallet snapshot via the shared `unroll.PlanExitFunding` + model (`exitSummaryEntry`). A fee-estimate failure degrades to a + zero rate (zeroing only the fee columns) instead of failing the + call; a per-outpoint descriptor lookup failure yields a phase-only + entry with zeroed amounts instead of failing the whole summary. - `unrollPhaseToProto` / `unrollJobStatusToProto` — dual mappers from live `unroll.Phase` and persisted `db.UnilateralExitJobStatus` to the same proto. `PhaseSweepBroadcast` and `PhaseSweepConfirmation` @@ -351,6 +373,19 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep - `OORConfig.OOR.Limits.MaxMailboxScriptBytes` must be at least `minOORMailboxScriptBytes = 34` (P2TR script length); validated during `Config.Validate()`. +- `spentSoFarSat` prorates the CPFP total over broadcast (confirmed + + in-flight) proof txs by multiplying by the broadcast count **before** + dividing by `progress.TotalTxs` — dividing per-child first truncates + to zero whenever the CPFP total is smaller than the tx count. + Numerator and denominator must count the same proof-graph universe + (deduped node count), since it's an estimate, not accounting: a + confirmed proof tx may be a shared ancestor another party paid the + CPFP for. The sweep leg is added only once `sweepBuilt` is true. +- `unrollPhaseDetail`'s materializing line clamps its 1-based + `CurrentLayer+1` display to `TotalLayers`: the frontier layer + collapses to `TotalLayers` once every proof node confirms, so + without the clamp a job read as `MATERIALIZING` right after the + frontier collapses would render "layer N+1 of N". - `Config.EagerRoundJoin` is seeded by build-tag-aware `defaultEagerRoundJoin()`: `false` on the standalone non-walletdkrpc build, `true` under the `walletdkrpc` tag (both `cmd/darepod` and @@ -364,5 +399,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep - [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — Installation, configuration, CLI reference. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/db/AGENTS.md b/db/AGENTS.md index 3cf684ded..2416859a6 100644 --- a/db/AGENTS.md +++ b/db/AGENTS.md @@ -41,6 +41,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db. - diff --git a/db/CLAUDE.md b/db/CLAUDE.md index 3cf684ded..2416859a6 100644 --- a/db/CLAUDE.md +++ b/db/CLAUDE.md @@ -41,6 +41,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db. - diff --git a/docs/index.md b/docs/index.md index 01d28ed72..8131d41bb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,9 +16,11 @@ into specific topics below. | [fee_ledger.md](fee_ledger.md) | Client-side double-entry fee ledger: chart of accounts, per-flow walkthroughs, emission sites, replay safety | | [fee-change-model.md](fee-change-model.md) | Seal-time fee handshake (#270): change-output designation rules, 11-scenario catalogue, proto contract, CLI mapping | | [credit_system.md](credit_system.md) | Sat-native credit accounts for below-dust receives, credit-assisted receives, credit-backed sends, top-ups, and walletdk integration, with mermaid diagrams | +| [credit_durable_actor_design.md](credit_durable_actor_design.md) | The `credit` package's durable protofsm-actor design: pay/receive/redeem flows, registry, and transition table | | [sdk_layered_architecture.md](sdk_layered_architecture.md) | SDK layering rationale: `sdk/ark` facade, remote vs. embedded modes, `sdk/swaps` future direction | | [swap_system.md](swap_system.md) | End-to-end swap walkthrough: vHTLC tree (collaborative vs. unilateral-exit leaves), receive (out-swap) and pay (in-swap) flows, the off-chain-first cancellation/timeout recovery ladder, same-Ark p2p detection, swap-server RPCs, and proof-gated indexer authorization, with mermaid diagrams | | [walletdk_integration.md](walletdk_integration.md) | Basic `walletdk` integration flow, startup/config examples, swap accounting, and wrapper guidance | +| [walletdk_mobile.md](walletdk_mobile.md) | `sdk/walletdk/mobile`: the gomobile-safe facade for driving an embedded `darepod` wallet in-process from Android/iOS hosts | | [canonical_activity_log_design.md](canonical_activity_log_design.md) | Design (#774): a canonical store with stable cross-restart ids replacing the derive-on-read activity feed — a current-state `activity_entries` projection plus an append-only, sequence-numbered `activity_events` log (mailbox-style cursor) for resumable subscribe; foundation of the event-log epic (#776) | | [walletdkrpc_build.md](walletdkrpc_build.md) | How to build and install the daemon and CLI with the wallet RPC subserver enabled (`walletdkrpc` + `swapruntime` tags) | | [seed_restore_recovery.md](seed_restore_recovery.md) | Restore a self-managed seed and recover Ark state from chain/indexer data | diff --git a/harness/AGENTS.md b/harness/AGENTS.md index 59179e972..2c99d6a06 100644 --- a/harness/AGENTS.md +++ b/harness/AGENTS.md @@ -8,6 +8,9 @@ containers with network isolation for end-to-end testing. ## Key Types - `Harness` — Top-level test harness owning bitcoind, lnd, and arkd lifecycle. + The primary `lnd` node always runs the bitcoind chain backend; additional + LND nodes started via `StartAdditionalLNDWithBackend` may instead run + neutrino. - `LndInstance` — Manages an LND container's lifecycle and connection. - `TapdHarness` — Optional Tapd instance for asset-related tests. - `Options` — Configuration struct passed to `NewHarness`. Controls image @@ -31,12 +34,24 @@ containers with network isolation for end-to-end testing. depth+1)` to produce a strictly longer replacement branch. - `ReconsiderBlock(hash string)` — Asks bitcoind to reconsider a previously invalidated block. +- `StartAdditionalLND(name string) *LndInstance` — Starts an extra LND node + backed by bitcoind (the default chain backend). +- `StartAdditionalLNDWithBackend(name, chainBackend string) *LndInstance` — + Starts an extra LND node with an explicit chain backend + (`LNDChainBackendBitcoind` or `LNDChainBackendNeutrino`). Neutrino syncs + and broadcasts over the regtest bitcoind's P2P interface (compact block + filters) instead of RPC/ZMQ, exercising lnd's native SPV / 1p1c broadcast + path — used to test the lnd-backed `chainbackends`/`lndsubmitter` + best-effort package broadcast when a light client cannot return a real + package-accept verdict. ## Relationships -- **Depends on**: `chain` (bitcoind RPC), `lndbackend` (LND integration), - `chainbackends` (PackageSubmitter interface). -- **Depended on by**: `systest` (system-level tests). +- **Depends on**: `chain` (bitcoind RPC client helpers), `lndclient` (drives + and queries the LND containers it starts). +- **Depended on by**: `systest` (system-level tests, which separately wire + `chainbackends`/`lndbackend` types around the LND instances this harness + starts). ## Key Constants @@ -45,4 +60,7 @@ containers with network isolation for end-to-end testing. tests. - `electrsReadyTimeout` = 2 minutes — separate extended timeout for the electrs container HTTP readiness check. +- `LNDChainBackendBitcoind` / `LNDChainBackendNeutrino` — Chain-backend + selectors for `StartAdditionalLNDWithBackend`; the primary `lnd` node is + always started with `LNDChainBackendBitcoind`. - Coinbase maturity: 100 blocks + 6-block buffer. diff --git a/harness/CLAUDE.md b/harness/CLAUDE.md index 59179e972..2c99d6a06 100644 --- a/harness/CLAUDE.md +++ b/harness/CLAUDE.md @@ -8,6 +8,9 @@ containers with network isolation for end-to-end testing. ## Key Types - `Harness` — Top-level test harness owning bitcoind, lnd, and arkd lifecycle. + The primary `lnd` node always runs the bitcoind chain backend; additional + LND nodes started via `StartAdditionalLNDWithBackend` may instead run + neutrino. - `LndInstance` — Manages an LND container's lifecycle and connection. - `TapdHarness` — Optional Tapd instance for asset-related tests. - `Options` — Configuration struct passed to `NewHarness`. Controls image @@ -31,12 +34,24 @@ containers with network isolation for end-to-end testing. depth+1)` to produce a strictly longer replacement branch. - `ReconsiderBlock(hash string)` — Asks bitcoind to reconsider a previously invalidated block. +- `StartAdditionalLND(name string) *LndInstance` — Starts an extra LND node + backed by bitcoind (the default chain backend). +- `StartAdditionalLNDWithBackend(name, chainBackend string) *LndInstance` — + Starts an extra LND node with an explicit chain backend + (`LNDChainBackendBitcoind` or `LNDChainBackendNeutrino`). Neutrino syncs + and broadcasts over the regtest bitcoind's P2P interface (compact block + filters) instead of RPC/ZMQ, exercising lnd's native SPV / 1p1c broadcast + path — used to test the lnd-backed `chainbackends`/`lndsubmitter` + best-effort package broadcast when a light client cannot return a real + package-accept verdict. ## Relationships -- **Depends on**: `chain` (bitcoind RPC), `lndbackend` (LND integration), - `chainbackends` (PackageSubmitter interface). -- **Depended on by**: `systest` (system-level tests). +- **Depends on**: `chain` (bitcoind RPC client helpers), `lndclient` (drives + and queries the LND containers it starts). +- **Depended on by**: `systest` (system-level tests, which separately wire + `chainbackends`/`lndbackend` types around the LND instances this harness + starts). ## Key Constants @@ -45,4 +60,7 @@ containers with network isolation for end-to-end testing. tests. - `electrsReadyTimeout` = 2 minutes — separate extended timeout for the electrs container HTTP readiness check. +- `LNDChainBackendBitcoind` / `LNDChainBackendNeutrino` — Chain-backend + selectors for `StartAdditionalLNDWithBackend`; the primary `lnd` node is + always started with `LNDChainBackendBitcoind`. - Coinbase maturity: 100 blocks + 6-block buffer. diff --git a/internal/sqlbase/AGENTS.md b/internal/sqlbase/AGENTS.md new file mode 100644 index 000000000..e3a0792b5 --- /dev/null +++ b/internal/sqlbase/AGENTS.md @@ -0,0 +1,67 @@ +# internal/sqlbase + +## Purpose + +A `walletdb.DB` implementation that emulates a bbolt-style nested +key/value/bucket hierarchy on top of a generic `database/sql` driver, so +btcwallet's walletdb consumers can run against SQL backends (SQLite/Postgres) +instead of bbolt. Built exclusively for `js && wasm` targets, where it backs +the browser OPFS SQLite store used by `lwwallet`. + +## Key Types + +- `Config` — Connection/driver settings (`DriverName`, `Dsn`, `Timeout`, + `Schema`, `TableNamePrefix`, `SQLiteCmdReplacements`, `WithTxLevelLock`) + passed to `NewSqlBackend`. +- `db` — Unexported `walletdb.DB` implementation; owns the `*sql.DB` handle, + the per-namespace table name, and (optionally) a process-wide `sync.RWMutex` + used when `WithTxLevelLock` is set. +- `dbConnSet` / `dbConn` — Process-global, reference-counted pool of open + `*sql.DB` handles keyed by DSN, so multiple `NewSqlBackend` callers sharing a + DSN reuse one connection. Initialized once via `Init(maxConnections)`. + `Open`/`Close` are the internal ref-count accessors, incrementing on each + new caller and closing the underlying `*sql.DB` only when the count drops + to zero. +- `readWriteTx` — `walletdb.ReadWriteTx` implementation wrapping a single + `*sql.Tx` opened at `sql.LevelSerializable`; provides the `QueryRow`/ + `Query`/`Exec` helpers (each with a timeout context) used by buckets and + cursors. +- `readWriteBucket` — `walletdb.ReadWriteBucket` implementation. A bucket is a + row group sharing a `parent_id`; nested buckets are rows with `value IS + NULL`, leaf keys are rows with a non-NULL `value`. +- `readWriteCursor` — `walletdb.ReadWriteCursor` implementation that walks a + bucket's rows in key order via `First`/`Next`/`Prev`/`Last`/`Seek`, tracking + position with `currKey` (not a live SQL cursor). + +## Relationships + +- **Depends on**: `github.com/btcsuite/btcwallet/walletdb` (interfaces this + package implements), `github.com/lightningnetwork/lnd/sqldb` (retryable + transaction execution and serialization-error classification), + `github.com/btcsuite/btclog/v2` (package logger via `UseLogger`). +- **Depended on by**: `lwwallet` (`lwwallet/walletdb_wasm.go`) — the only + in-repo caller, using it to open btcwallet's walletdb against an + OPFS-backed SQLite driver in the browser/WASM build. + +## Invariants + +- Every file carries the `//go:build js && wasm` tag; this package is never + compiled into native darepod/darepocli binaries, only into WASM builds. +- All buckets and keys within one `Config.TableNamePrefix` share a single + physical table (`_kv`); the nested-bucket hierarchy is simulated via + `parent_id` self-references, not real SQL schemas/tables per bucket. +- A row's `value IS NULL` marks it as a sub-bucket, not a stored value; `Get` + and `Put` must preserve this distinction (an empty `[]byte{}` value is valid + and distinct from NULL) or bucket/key semantics silently corrupt. +- `Init(maxConnections)` must run once before any `NewSqlBackend` call; + `dbConns` is a package-global guarded by `dbConnsMu`, shared by every caller + in the process, so connections for the same DSN are reference-counted rather + than reopened. +- Transactions run at `sql.LevelSerializable`; callers relying on + `db.Update`/`db.View` retries must keep their closures idempotent since + `sqldb.ExecuteSQLTransactionWithRetry` re-invokes `f` (after calling + `reset`) on serialization conflicts, up to `DefaultNumTxRetries`. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/internal/sqlbase/CLAUDE.md b/internal/sqlbase/CLAUDE.md new file mode 100644 index 000000000..e3a0792b5 --- /dev/null +++ b/internal/sqlbase/CLAUDE.md @@ -0,0 +1,67 @@ +# internal/sqlbase + +## Purpose + +A `walletdb.DB` implementation that emulates a bbolt-style nested +key/value/bucket hierarchy on top of a generic `database/sql` driver, so +btcwallet's walletdb consumers can run against SQL backends (SQLite/Postgres) +instead of bbolt. Built exclusively for `js && wasm` targets, where it backs +the browser OPFS SQLite store used by `lwwallet`. + +## Key Types + +- `Config` — Connection/driver settings (`DriverName`, `Dsn`, `Timeout`, + `Schema`, `TableNamePrefix`, `SQLiteCmdReplacements`, `WithTxLevelLock`) + passed to `NewSqlBackend`. +- `db` — Unexported `walletdb.DB` implementation; owns the `*sql.DB` handle, + the per-namespace table name, and (optionally) a process-wide `sync.RWMutex` + used when `WithTxLevelLock` is set. +- `dbConnSet` / `dbConn` — Process-global, reference-counted pool of open + `*sql.DB` handles keyed by DSN, so multiple `NewSqlBackend` callers sharing a + DSN reuse one connection. Initialized once via `Init(maxConnections)`. + `Open`/`Close` are the internal ref-count accessors, incrementing on each + new caller and closing the underlying `*sql.DB` only when the count drops + to zero. +- `readWriteTx` — `walletdb.ReadWriteTx` implementation wrapping a single + `*sql.Tx` opened at `sql.LevelSerializable`; provides the `QueryRow`/ + `Query`/`Exec` helpers (each with a timeout context) used by buckets and + cursors. +- `readWriteBucket` — `walletdb.ReadWriteBucket` implementation. A bucket is a + row group sharing a `parent_id`; nested buckets are rows with `value IS + NULL`, leaf keys are rows with a non-NULL `value`. +- `readWriteCursor` — `walletdb.ReadWriteCursor` implementation that walks a + bucket's rows in key order via `First`/`Next`/`Prev`/`Last`/`Seek`, tracking + position with `currKey` (not a live SQL cursor). + +## Relationships + +- **Depends on**: `github.com/btcsuite/btcwallet/walletdb` (interfaces this + package implements), `github.com/lightningnetwork/lnd/sqldb` (retryable + transaction execution and serialization-error classification), + `github.com/btcsuite/btclog/v2` (package logger via `UseLogger`). +- **Depended on by**: `lwwallet` (`lwwallet/walletdb_wasm.go`) — the only + in-repo caller, using it to open btcwallet's walletdb against an + OPFS-backed SQLite driver in the browser/WASM build. + +## Invariants + +- Every file carries the `//go:build js && wasm` tag; this package is never + compiled into native darepod/darepocli binaries, only into WASM builds. +- All buckets and keys within one `Config.TableNamePrefix` share a single + physical table (`_kv`); the nested-bucket hierarchy is simulated via + `parent_id` self-references, not real SQL schemas/tables per bucket. +- A row's `value IS NULL` marks it as a sub-bucket, not a stored value; `Get` + and `Put` must preserve this distinction (an empty `[]byte{}` value is valid + and distinct from NULL) or bucket/key semantics silently corrupt. +- `Init(maxConnections)` must run once before any `NewSqlBackend` call; + `dbConns` is a package-global guarded by `dbConnsMu`, shared by every caller + in the process, so connections for the same DSN are reference-counted rather + than reopened. +- Transactions run at `sql.LevelSerializable`; callers relying on + `db.Update`/`db.View` retries must keep their closures idempotent since + `sqldb.ExecuteSQLTransactionWithRetry` re-invokes `f` (after calling + `reset`) on serialization conflicts, up to `DefaultNumTxRetries`. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/ledger/AGENTS.md b/ledger/AGENTS.md index 0584ade1a..871062ddc 100644 --- a/ledger/AGENTS.md +++ b/ledger/AGENTS.md @@ -209,5 +209,3 @@ or balance reconciliation. Required emission pairs: - [db/CLAUDE.md](../db/CLAUDE.md) — `LedgerStoreDB` and `UTXOAuditStoreDB` adapters. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/ledger/CLAUDE.md b/ledger/CLAUDE.md index 0584ade1a..871062ddc 100644 --- a/ledger/CLAUDE.md +++ b/ledger/CLAUDE.md @@ -209,5 +209,3 @@ or balance reconciliation. Required emission pairs: - [db/CLAUDE.md](../db/CLAUDE.md) — `LedgerStoreDB` and `UTXOAuditStoreDB` adapters. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/lib/scripts/AGENTS.md b/lib/scripts/AGENTS.md index 42c2ec165..b605ff3c4 100644 --- a/lib/scripts/AGENTS.md +++ b/lib/scripts/AGENTS.md @@ -3,5 +3,3 @@ **Removed.** All functionality migrated to [`lib/arkscript`](../arkscript/CLAUDE.md) — see `VTXOPolicy`, `CheckpointPolicy`, `SpendInfo`, `AnchorOutput`, and `AnchorPkScript`. - - diff --git a/lib/scripts/CLAUDE.md b/lib/scripts/CLAUDE.md index 42c2ec165..b605ff3c4 100644 --- a/lib/scripts/CLAUDE.md +++ b/lib/scripts/CLAUDE.md @@ -3,5 +3,3 @@ **Removed.** All functionality migrated to [`lib/arkscript`](../arkscript/CLAUDE.md) — see `VTXOPolicy`, `CheckpointPolicy`, `SpendInfo`, `AnchorOutput`, and `AnchorPkScript`. - - diff --git a/mailbox/pb/AGENTS.md b/mailbox/pb/AGENTS.md new file mode 100644 index 000000000..1c29935d2 --- /dev/null +++ b/mailbox/pb/AGENTS.md @@ -0,0 +1,51 @@ +# mailbox/pb + +## Purpose + +Generated protobuf/gRPC wire types for the mailbox transport (`Envelope`, +`RpcMeta`, `Send`/`Pull`/`AckUpTo`), plus a hand-written constant that pins +the stable mailbox transport version every client must be able to decode. + +## Key Types + +All `*.pb.go` files are generated — never edit directly; regenerate with +`make rpc`. The manually-maintained `version.go` defines: + +- `MailboxProtocolVersionV1` — The stable mailbox transport version (`1`), + covering envelope framing, `RpcMeta` routing, `Send`/`Pull`/`AckUpTo` + behavior, and cursor/ack/durable-replay semantics. It is a code constant, + not operator configuration, because v1 is the bootstrap endpoint every + client must decode; a breaking mailbox transport must ship on a new + endpoint/proto package (e.g. `mailbox.v2`) rather than reuse this value. + +This is distinct from `Envelope.ArkProtocolVersion`, a separately-versioned +field carried inside the same envelope for the higher-level Ark protocol — +see `version_compat_test.go` for the additive-compatibility guarantees that +keep old-shape envelopes decoding cleanly as new version fields are added. + +## Relationships + +- **Depends on**: nothing (generated proto types plus one constant). +- **Depended on by**: `serverconn` (constructs `Envelope`/`RpcMeta`, + drives `Send`/`Pull`/`AckUpTo`, stamps `MailboxProtocolVersionV1`), + `mailbox/conn` (`ResponseRegistry`, `WrappedProto`, status errors wrap + `Status`/`Envelope`), `darepod` (server-side mailbox edge), `rpc/restclient`, + `sdk/swaps`, `swapclientserver` (mailbox-backed clients). + +## Invariants + +- **Never edit generated code** — regenerate via `make rpc`. +- `MailboxProtocolVersionV1` must never change value; a new mailbox + transport version is a new constant on a new endpoint, not a bump of + this one. +- New wire fields (e.g. `ArkProtocolVersion`, the `SupportedMailboxVersions` + / `SupportedArkVersions` lists on `Status`) must be additive so that + peers running older code decode them as zero/empty rather than failing — + see `version_compat_test.go` for the round-trip proofs this depends on. + +## Deep Docs + +- [mailbox/CLAUDE.md](../CLAUDE.md) — Parent mailbox package overview. +- [docs/mailbox_architecture.md](../../docs/mailbox_architecture.md) — Three-layer mailbox architecture. +- [docs/RPC_MAILBOX_CONTRACT.md](../../docs/RPC_MAILBOX_CONTRACT.md) — Envelope semantics and ack watermarks. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/mailbox/pb/CLAUDE.md b/mailbox/pb/CLAUDE.md new file mode 100644 index 000000000..1c29935d2 --- /dev/null +++ b/mailbox/pb/CLAUDE.md @@ -0,0 +1,51 @@ +# mailbox/pb + +## Purpose + +Generated protobuf/gRPC wire types for the mailbox transport (`Envelope`, +`RpcMeta`, `Send`/`Pull`/`AckUpTo`), plus a hand-written constant that pins +the stable mailbox transport version every client must be able to decode. + +## Key Types + +All `*.pb.go` files are generated — never edit directly; regenerate with +`make rpc`. The manually-maintained `version.go` defines: + +- `MailboxProtocolVersionV1` — The stable mailbox transport version (`1`), + covering envelope framing, `RpcMeta` routing, `Send`/`Pull`/`AckUpTo` + behavior, and cursor/ack/durable-replay semantics. It is a code constant, + not operator configuration, because v1 is the bootstrap endpoint every + client must decode; a breaking mailbox transport must ship on a new + endpoint/proto package (e.g. `mailbox.v2`) rather than reuse this value. + +This is distinct from `Envelope.ArkProtocolVersion`, a separately-versioned +field carried inside the same envelope for the higher-level Ark protocol — +see `version_compat_test.go` for the additive-compatibility guarantees that +keep old-shape envelopes decoding cleanly as new version fields are added. + +## Relationships + +- **Depends on**: nothing (generated proto types plus one constant). +- **Depended on by**: `serverconn` (constructs `Envelope`/`RpcMeta`, + drives `Send`/`Pull`/`AckUpTo`, stamps `MailboxProtocolVersionV1`), + `mailbox/conn` (`ResponseRegistry`, `WrappedProto`, status errors wrap + `Status`/`Envelope`), `darepod` (server-side mailbox edge), `rpc/restclient`, + `sdk/swaps`, `swapclientserver` (mailbox-backed clients). + +## Invariants + +- **Never edit generated code** — regenerate via `make rpc`. +- `MailboxProtocolVersionV1` must never change value; a new mailbox + transport version is a new constant on a new endpoint, not a bump of + this one. +- New wire fields (e.g. `ArkProtocolVersion`, the `SupportedMailboxVersions` + / `SupportedArkVersions` lists on `Status`) must be additive so that + peers running older code decode them as zero/empty rather than failing — + see `version_compat_test.go` for the round-trip proofs this depends on. + +## Deep Docs + +- [mailbox/CLAUDE.md](../CLAUDE.md) — Parent mailbox package overview. +- [docs/mailbox_architecture.md](../../docs/mailbox_architecture.md) — Three-layer mailbox architecture. +- [docs/RPC_MAILBOX_CONTRACT.md](../../docs/RPC_MAILBOX_CONTRACT.md) — Envelope semantics and ack watermarks. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/oor/AGENTS.md b/oor/AGENTS.md index 0bd1ecfbd..5971c9ddb 100644 --- a/oor/AGENTS.md +++ b/oor/AGENTS.md @@ -407,5 +407,3 @@ per-session durable actor's turn. - [oor/doc.go](doc.go) — Package overview. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/oor/CLAUDE.md b/oor/CLAUDE.md index 0bd1ecfbd..5971c9ddb 100644 --- a/oor/CLAUDE.md +++ b/oor/CLAUDE.md @@ -407,5 +407,3 @@ per-session durable actor's turn. - [oor/doc.go](doc.go) — Package overview. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/round/AGENTS.md b/round/AGENTS.md index d055c8b8f..b2a304d4c 100644 --- a/round/AGENTS.md +++ b/round/AGENTS.md @@ -251,5 +251,3 @@ state transitions and validation rules live under [Invariants](#invariants). - [round/README.md](README.md) — Full state machine walkthrough with diagrams. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - \ No newline at end of file diff --git a/round/CLAUDE.md b/round/CLAUDE.md index d055c8b8f..b2a304d4c 100644 --- a/round/CLAUDE.md +++ b/round/CLAUDE.md @@ -251,5 +251,3 @@ state transitions and validation rules live under [Invariants](#invariants). - [round/README.md](README.md) — Full state machine walkthrough with diagrams. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - \ No newline at end of file diff --git a/rpc/oorpb/AGENTS.md b/rpc/oorpb/AGENTS.md new file mode 100644 index 000000000..bd4ebea63 --- /dev/null +++ b/rpc/oorpb/AGENTS.md @@ -0,0 +1,67 @@ +# rpc/oorpb + +## Purpose + +Generated protobuf/gRPC/mailbox-RPC stubs for the out-of-round (OOR) transfer +protocol, plus hand-written helpers that convert between the wire messages +and domain types (`psbt.Packet`, `wire.OutPoint`, `chainhash.Hash`) and pin +the per-session OOR flow version. + +## Key Types + +All `*.pb.go` files are generated — never edit directly; regenerate with +`make rpc`. The manually-maintained `payloads.go` and `version.go` define: + +- `SigningDescriptor` — Domain-side signing metadata (outpoint, VTXO policy + template, spend path, owner-leaf policy) for one checkpoint input; mirrors + the generated `OORSigningDescriptor` wire type. +- `NewSubmitPackageRequest` / `ParseSubmitPackageRequest` — Build/decode a + `SubmitPackageRequest`, serializing/parsing the Ark and checkpoint PSBTs + and signing descriptors between domain types and wire bytes. +- `NewSubmitPackageResponse` / `NewSubmitPackageRejection` / + `ParseSubmitPackageResponse` — Build the success or rejection branch of + `SubmitPackageResponse` and decode either branch back out; a rejection + decodes into a typed `*SubmitRejectedError`. +- `SubmitRejectedError` — Typed error (`Code OORRejectCode`, `Reason string`) + returned by `ParseSubmitPackageResponse` so callers route on `Code` + (e.g. fall back to in-round payment) instead of string-matching `Reason`. +- `NewFinalizePackageRequest` / `ParseFinalizePackageRequest`, + `NewFinalizePackageResponse` / `ParseFinalizePackageResponse` — Build/decode + the finalize-package request/response pair. +- `FlowVersion` / `FlowVersionV1` — Permanent per-session choreography + version stamped on the submit request and persisted with the session, so + client and operator never drift on how a given OOR transfer was conducted. + Zero-indexed: `FlowVersionV1` is the Go zero value, so an unstamped field + reads as V1 with no normalization step. +- `ValidateFlowVersion` — Ingress guard that fails closed on any flow version + this build does not understand (i.e. anything past the latest known + version), applied where a version arrives from the other party. + +## Relationships + +- **Depends on**: `mailbox/rpc` (`rpc.Router`, `rpc.RPCClient` for the + generated `OORMailboxServiceMailbox{Client,Server}`), `lib/tx/oor` + (`oortx.RecipientOutput`), `lib/tx/psbtutil` (PSBT serialize/parse). +- **Depended on by**: `oor` (session actor, outbox messages, errors — + the primary consumer of the typed constructors/parsers and + `ValidateFlowVersion`), `db` (`oor_session_registry_store.go` persists + `FlowVersion`), `darepod` (server-side OOR mailbox wiring). + +## Invariants + +- **Never edit generated code** — regenerate via `make rpc`. +- New wire fields on `SubmitPackageResponse`/`SubmitPackageRequest` must be + additive: `ParseSubmitPackageResponse` already treats an absent + `CoSignedArkPsbt` as "operator not yet upgraded" rather than a parse + error, and new fields must preserve that rolling-upgrade compatibility. +- `FlowVersion` values are permanent once assigned to a session; a build + must reject (`ValidateFlowVersion`) any version it does not implement + rather than guess at unknown choreography rules. +- Rejection and success payloads both echo `session_id` so the client-side + `EventRouter`/session FSM can route the response without stalling the + ingress cursor on an undispatchable envelope. + +## Deep Docs + +- [rpc/CLAUDE.md](../CLAUDE.md) — Parent rpc package overview. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/rpc/oorpb/CLAUDE.md b/rpc/oorpb/CLAUDE.md new file mode 100644 index 000000000..bd4ebea63 --- /dev/null +++ b/rpc/oorpb/CLAUDE.md @@ -0,0 +1,67 @@ +# rpc/oorpb + +## Purpose + +Generated protobuf/gRPC/mailbox-RPC stubs for the out-of-round (OOR) transfer +protocol, plus hand-written helpers that convert between the wire messages +and domain types (`psbt.Packet`, `wire.OutPoint`, `chainhash.Hash`) and pin +the per-session OOR flow version. + +## Key Types + +All `*.pb.go` files are generated — never edit directly; regenerate with +`make rpc`. The manually-maintained `payloads.go` and `version.go` define: + +- `SigningDescriptor` — Domain-side signing metadata (outpoint, VTXO policy + template, spend path, owner-leaf policy) for one checkpoint input; mirrors + the generated `OORSigningDescriptor` wire type. +- `NewSubmitPackageRequest` / `ParseSubmitPackageRequest` — Build/decode a + `SubmitPackageRequest`, serializing/parsing the Ark and checkpoint PSBTs + and signing descriptors between domain types and wire bytes. +- `NewSubmitPackageResponse` / `NewSubmitPackageRejection` / + `ParseSubmitPackageResponse` — Build the success or rejection branch of + `SubmitPackageResponse` and decode either branch back out; a rejection + decodes into a typed `*SubmitRejectedError`. +- `SubmitRejectedError` — Typed error (`Code OORRejectCode`, `Reason string`) + returned by `ParseSubmitPackageResponse` so callers route on `Code` + (e.g. fall back to in-round payment) instead of string-matching `Reason`. +- `NewFinalizePackageRequest` / `ParseFinalizePackageRequest`, + `NewFinalizePackageResponse` / `ParseFinalizePackageResponse` — Build/decode + the finalize-package request/response pair. +- `FlowVersion` / `FlowVersionV1` — Permanent per-session choreography + version stamped on the submit request and persisted with the session, so + client and operator never drift on how a given OOR transfer was conducted. + Zero-indexed: `FlowVersionV1` is the Go zero value, so an unstamped field + reads as V1 with no normalization step. +- `ValidateFlowVersion` — Ingress guard that fails closed on any flow version + this build does not understand (i.e. anything past the latest known + version), applied where a version arrives from the other party. + +## Relationships + +- **Depends on**: `mailbox/rpc` (`rpc.Router`, `rpc.RPCClient` for the + generated `OORMailboxServiceMailbox{Client,Server}`), `lib/tx/oor` + (`oortx.RecipientOutput`), `lib/tx/psbtutil` (PSBT serialize/parse). +- **Depended on by**: `oor` (session actor, outbox messages, errors — + the primary consumer of the typed constructors/parsers and + `ValidateFlowVersion`), `db` (`oor_session_registry_store.go` persists + `FlowVersion`), `darepod` (server-side OOR mailbox wiring). + +## Invariants + +- **Never edit generated code** — regenerate via `make rpc`. +- New wire fields on `SubmitPackageResponse`/`SubmitPackageRequest` must be + additive: `ParseSubmitPackageResponse` already treats an absent + `CoSignedArkPsbt` as "operator not yet upgraded" rather than a parse + error, and new fields must preserve that rolling-upgrade compatibility. +- `FlowVersion` values are permanent once assigned to a session; a build + must reject (`ValidateFlowVersion`) any version it does not implement + rather than guess at unknown choreography rules. +- Rejection and success payloads both echo `session_id` so the client-side + `EventRouter`/session FSM can route the response without stalling the + ingress cursor on an undispatchable envelope. + +## Deep Docs + +- [rpc/CLAUDE.md](../CLAUDE.md) — Parent rpc package overview. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/rpcauth/AGENTS.md b/rpcauth/AGENTS.md new file mode 100644 index 000000000..192b20236 --- /dev/null +++ b/rpcauth/AGENTS.md @@ -0,0 +1,58 @@ +# rpcauth + +## Purpose + +Shared helpers for securing darepod's gRPC/REST surface and its clients: +TLS certificate provisioning plus macaroon-based per-RPC credentials. Used by +both the daemon (server-side TLS/macaroon setup) and its clients (darepocli, +the wallet SDK) to dial in with matching credentials. + +## Key Types + +- `MacaroonMetadataKey` — The gRPC metadata / HTTP header key + (`"macaroon"`) that carries the hex-encoded macaroon; shared by server + (gateway header allow-list) and clients (dial options, HTTP headers) so + both sides agree on the wire key. +- `EnsureTLSCert(certPath, keyPath, organization)` — Idempotently loads an + existing cert/key pair or self-signs a new one (via `lnd/cert`) if neither + file exists yet; errors on a partial pair. +- `ServerTLSCredentials(certPath, keyPath)` / `ClientTLSCredentials(certPath)` + — Build gRPC `credentials.TransportCredentials` for the server and client + sides of a TLS-secured connection respectively. +- `HTTPClientForCert(certPath)` — Builds an `*http.Client` trusting the given + cert (or system roots if empty), for REST/gateway clients. +- `DialOptionFromFile(path)` — Loads a macaroon from disk and returns a + `grpc.DialOption` that attaches it as per-RPC credentials. +- `HexFromFile(path)` — Reads a macaroon file and hex-encodes it, for callers + that need the raw header value instead of a gRPC dial option (e.g. HTTP/ + gateway clients). + +## Relationships + +- **Depends on**: `github.com/lightningnetwork/lnd/macaroons` (macaroon gRPC + credentials), `github.com/lightningnetwork/lnd/cert` (self-signed cert + generation), `google.golang.org/grpc`/`credentials`, + `gopkg.in/macaroon.v2`. +- **Depended on by**: `darepod` (`rpc_security.go` server TLS setup, + `gateway_server.go` REST gateway TLS/macaroon forwarding, + `outbound_clients.go` and `server.go` outbound gRPC dialing), + `cmd/darepocli/darepoclicommands` (CLI client dial options), + `sdk/walletdk` (wallet SDK gRPC/HTTP client setup). + +## Invariants + +- `EnsureTLSCert` treats "cert exists but key missing" (or vice versa) as an + error rather than silently regenerating, to avoid clobbering a cert whose + key was lost or vice versa. +- Generated and loaded certs enforce `tls.VersionTLS12` as the minimum TLS + version on both server and client credential paths. +- Cert/key files written by `EnsureTLSCert` are chmod'd `0o600` and their + parent directories `0o700`; callers must not relax these permissions. +- `MacaroonMetadataKey` must stay identical across `DialOptionFromFile`, + `HexFromFile` callers, and the gateway's header allow-list in + `darepod/gateway_server.go` — a mismatch silently drops macaroon auth on + one side of the client/server pair. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/rpcauth/CLAUDE.md b/rpcauth/CLAUDE.md new file mode 100644 index 000000000..192b20236 --- /dev/null +++ b/rpcauth/CLAUDE.md @@ -0,0 +1,58 @@ +# rpcauth + +## Purpose + +Shared helpers for securing darepod's gRPC/REST surface and its clients: +TLS certificate provisioning plus macaroon-based per-RPC credentials. Used by +both the daemon (server-side TLS/macaroon setup) and its clients (darepocli, +the wallet SDK) to dial in with matching credentials. + +## Key Types + +- `MacaroonMetadataKey` — The gRPC metadata / HTTP header key + (`"macaroon"`) that carries the hex-encoded macaroon; shared by server + (gateway header allow-list) and clients (dial options, HTTP headers) so + both sides agree on the wire key. +- `EnsureTLSCert(certPath, keyPath, organization)` — Idempotently loads an + existing cert/key pair or self-signs a new one (via `lnd/cert`) if neither + file exists yet; errors on a partial pair. +- `ServerTLSCredentials(certPath, keyPath)` / `ClientTLSCredentials(certPath)` + — Build gRPC `credentials.TransportCredentials` for the server and client + sides of a TLS-secured connection respectively. +- `HTTPClientForCert(certPath)` — Builds an `*http.Client` trusting the given + cert (or system roots if empty), for REST/gateway clients. +- `DialOptionFromFile(path)` — Loads a macaroon from disk and returns a + `grpc.DialOption` that attaches it as per-RPC credentials. +- `HexFromFile(path)` — Reads a macaroon file and hex-encodes it, for callers + that need the raw header value instead of a gRPC dial option (e.g. HTTP/ + gateway clients). + +## Relationships + +- **Depends on**: `github.com/lightningnetwork/lnd/macaroons` (macaroon gRPC + credentials), `github.com/lightningnetwork/lnd/cert` (self-signed cert + generation), `google.golang.org/grpc`/`credentials`, + `gopkg.in/macaroon.v2`. +- **Depended on by**: `darepod` (`rpc_security.go` server TLS setup, + `gateway_server.go` REST gateway TLS/macaroon forwarding, + `outbound_clients.go` and `server.go` outbound gRPC dialing), + `cmd/darepocli/darepoclicommands` (CLI client dial options), + `sdk/walletdk` (wallet SDK gRPC/HTTP client setup). + +## Invariants + +- `EnsureTLSCert` treats "cert exists but key missing" (or vice versa) as an + error rather than silently regenerating, to avoid clobbering a cert whose + key was lost or vice versa. +- Generated and loaded certs enforce `tls.VersionTLS12` as the minimum TLS + version on both server and client credential paths. +- Cert/key files written by `EnsureTLSCert` are chmod'd `0o600` and their + parent directories `0o700`; callers must not relax these permissions. +- `MacaroonMetadataKey` must stay identical across `DialOptionFromFile`, + `HexFromFile` callers, and the gateway's header allow-list in + `darepod/gateway_server.go` — a mismatch silently drops macaroon auth on + one side of the client/server pair. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/swaps/AGENTS.md b/sdk/swaps/AGENTS.md index 949f0b6be..0a44702a2 100644 --- a/sdk/swaps/AGENTS.md +++ b/sdk/swaps/AGENTS.md @@ -129,5 +129,3 @@ result into an `IncomingVHTLCNotification`. ## Deep Docs - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/swaps/CLAUDE.md b/sdk/swaps/CLAUDE.md index 949f0b6be..0a44702a2 100644 --- a/sdk/swaps/CLAUDE.md +++ b/sdk/swaps/CLAUDE.md @@ -129,5 +129,3 @@ result into an `IncomingVHTLCNotification`. ## Deep Docs - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/walletdk/AGENTS.md b/sdk/walletdk/AGENTS.md index 73da35ef1..842feb11e 100644 --- a/sdk/walletdk/AGENTS.md +++ b/sdk/walletdk/AGENTS.md @@ -49,8 +49,12 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w bounded send and the swept total for sweep-all), `DepositRequest`/`Result` (boarding address + initial `Entry`), `ListRequest`, `ListResult` (tagged union on `View`, populates one of `Activity`/`VTXOs`/`Onchain`), - `ActivityList`, `VTXOInventory`, `OnchainHistory`, `Entry`, - `WalletVTXO`, `OnchainTx`. + `ActivityList`, `VTXOInventory`, `OnchainHistory`, `Entry` + (optional `Progress *EntryProgress` and `Request *EntryRequest` + sub-objects, both nil when absent; `Request` is a `Type`-tagged + union over lightning/onchain/ark; `Cursor` is the event-log position + of a streamed update, zero outside the subscription path), `WalletVTXO`, + `OnchainTx`. - `ExitRequest` / `ExitResult` / `ExitStatusRequest` / `ExitStatusResult` / `ExitJobStatus` — exit DTOs. `ExitRequest` carries the target outpoint plus an optional on-chain `Destination` @@ -67,6 +71,20 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w populated by current behavior. Status strings are the wrapper-owned lowercase set (`pending`/`materializing`/`csv_pending`/`sweeping`/`completed`/`failed`/`unspecified`). + `ExitStatusRequest.Detailed` opts into the pricier query: when set, + `ExitStatusResult` also populates `PhaseDetail`, `Progress + *ExitProgress` (recovery-tree materialization counts/layers), `CSV + *ExitCSV` (maturity countdown, nil until the target confirms), `Fees + *ExitFees` (on-chain cost breakdown), `BestCaseBlocksRemaining`, and + `CurrentHeight`; a coarse (non-detailed) query leaves those nil/zero. +- `ExitSummaryRequest` / `ExitSummaryResult` / `ExitSummaryEntry` — + wallet-wide portfolio of in-progress exits (completed/failed exits + omitted) plus aggregate totals (`TotalExits`, + `TotalVTXOAmountSat`, `TotalEstFeeSat`, `TotalEstNetRecoveredSat`). +- `SubscribeGapError` — typed terminal error delivered on `Subscribe`'s + errs channel when the server-side send buffer overflows; carries the + resume `Cursor` and a human-readable `Reason`. No activity is lost: + reopen `Subscribe` with `SubscribeRequest.Cursor` set to it. - `ErrWalletRPCUnavailable` — sentinel returned by every wallet method on builds without the `walletdkrpc` tag. - `ErrSwapRuntimeUnavailable` — back-compat alias for @@ -86,9 +104,10 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w | `SendPrepared` | Dispatch a prepared send (consumes `SendIntentID`). Returns `{Entry, ActualAmountSat}`. | | `List` | Unified history view (Activity / VTXOs / Onchain) as a tagged-union `ListResult`. | | `Exit` | Trigger cooperative leave or unilateral unroll for a VTXO. | -| `ExitStatus` | Query the phase of an exit job. | +| `ExitStatus` | Query the phase of an exit job; `Detailed` adds progress/CSV/fee sub-objects. | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits plus aggregate totals. | | `Status` | Wallet readiness, balance, pending-entry count. | -| `Subscribe` | Stream wallet activity (`Entry`) updates. | +| `Subscribe` | Stream wallet activity (`Entry`) updates; resumable from a `Cursor` for lossless replay. | | `Stop` / `Close` | Shut down the embedded daemon, release the private transport. | | `Wait` | Single shared channel yielding the daemon's terminal run error. | | `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | @@ -142,21 +161,36 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w the wrapper boundary on builds without the `walletdkrpc` tag, before any RPC is attempted. `ErrSwapRuntimeUnavailable` is an alias for source-level compatibility with older swap-only callers. -- `Entry.Kind`/`Entry.Status` / `ListResult.View` / - `WalletVTXO.Status` / `OnchainTx.Kind` / `ExitJobStatus` are - wrapper-owned lowercase strings (not proto enums). Projection lives - in `convert.go`, intentionally decoupled from proto enum - renumbering. +- `Entry.Kind`/`Entry.Status` / `Entry.Progress.Phase` / + `Entry.Request.Type` / `ListResult.View` / `WalletVTXO.Status` / + `OnchainTx.Kind` / `ExitJobStatus` are wrapper-owned lowercase + strings (not proto enums). Projection lives in `convert.go`, + intentionally decoupled from proto enum renumbering. - `ListResult` is a discriminated union: read the variant named by `View` and treat the others as `nil`. Exhaustiveness is not enforced at compile time — switch on `View` rather than chaining nil checks. +- `Entry.Progress` and `Entry.Request` are optional pointers: both are + `nil` when the daemon supplied no progress hint / persisted no + request, so nil-check before dereferencing. `Entry.Request` is a + discriminated union — read the variant named by `Type` + (`lightning`/`onchain`/`ark`) and treat the other fields as zero, + the same idiom as `ListResult.View`. - `Wait()` is single-reader: same shared channel on every call. The channel delivers the daemon's terminal run error then closes; a closed channel reads as the zero error indefinitely. - `Subscribe` returns an unbuffered updates channel so a slow consumer applies backpressure end-to-end; the errs channel is cap-1 for a single terminal error. +- `SubscribeWallet` is backed by the daemon's canonical activity event + log: each streamed `Entry.Cursor` is that update's `event_seq`. The + host must persist the latest `Cursor` it has processed and pass it + back as `SubscribeRequest.Cursor` to resume without gaps — the daemon + replays every event after that cursor before switching to live. On + overflow the daemon ends the stream with a `SubscribeGapError` + (never drops updates silently); reopen `Subscribe` with that error's + `Cursor` rather than falling back to `List` plus a fresh live-only + subscription. - New options should follow the "apply after merge" placement so override semantics stay consistent. @@ -177,5 +211,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w - [swapclientserver/CLAUDE.md](../../swapclientserver/CLAUDE.md) — Daemon-side swap subserver (`-tags swapruntime`). - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/walletdk/CLAUDE.md b/sdk/walletdk/CLAUDE.md index c435f39ae..842feb11e 100644 --- a/sdk/walletdk/CLAUDE.md +++ b/sdk/walletdk/CLAUDE.md @@ -52,7 +52,9 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w `ActivityList`, `VTXOInventory`, `OnchainHistory`, `Entry` (optional `Progress *EntryProgress` and `Request *EntryRequest` sub-objects, both nil when absent; `Request` is a `Type`-tagged - union over lightning/onchain/ark), `WalletVTXO`, `OnchainTx`. + union over lightning/onchain/ark; `Cursor` is the event-log position + of a streamed update, zero outside the subscription path), `WalletVTXO`, + `OnchainTx`. - `ExitRequest` / `ExitResult` / `ExitStatusRequest` / `ExitStatusResult` / `ExitJobStatus` — exit DTOs. `ExitRequest` carries the target outpoint plus an optional on-chain `Destination` @@ -69,6 +71,20 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w populated by current behavior. Status strings are the wrapper-owned lowercase set (`pending`/`materializing`/`csv_pending`/`sweeping`/`completed`/`failed`/`unspecified`). + `ExitStatusRequest.Detailed` opts into the pricier query: when set, + `ExitStatusResult` also populates `PhaseDetail`, `Progress + *ExitProgress` (recovery-tree materialization counts/layers), `CSV + *ExitCSV` (maturity countdown, nil until the target confirms), `Fees + *ExitFees` (on-chain cost breakdown), `BestCaseBlocksRemaining`, and + `CurrentHeight`; a coarse (non-detailed) query leaves those nil/zero. +- `ExitSummaryRequest` / `ExitSummaryResult` / `ExitSummaryEntry` — + wallet-wide portfolio of in-progress exits (completed/failed exits + omitted) plus aggregate totals (`TotalExits`, + `TotalVTXOAmountSat`, `TotalEstFeeSat`, `TotalEstNetRecoveredSat`). +- `SubscribeGapError` — typed terminal error delivered on `Subscribe`'s + errs channel when the server-side send buffer overflows; carries the + resume `Cursor` and a human-readable `Reason`. No activity is lost: + reopen `Subscribe` with `SubscribeRequest.Cursor` set to it. - `ErrWalletRPCUnavailable` — sentinel returned by every wallet method on builds without the `walletdkrpc` tag. - `ErrSwapRuntimeUnavailable` — back-compat alias for @@ -88,9 +104,10 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w | `SendPrepared` | Dispatch a prepared send (consumes `SendIntentID`). Returns `{Entry, ActualAmountSat}`. | | `List` | Unified history view (Activity / VTXOs / Onchain) as a tagged-union `ListResult`. | | `Exit` | Trigger cooperative leave or unilateral unroll for a VTXO. | -| `ExitStatus` | Query the phase of an exit job. | +| `ExitStatus` | Query the phase of an exit job; `Detailed` adds progress/CSV/fee sub-objects. | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits plus aggregate totals. | | `Status` | Wallet readiness, balance, pending-entry count. | -| `Subscribe` | Stream wallet activity (`Entry`) updates. | +| `Subscribe` | Stream wallet activity (`Entry`) updates; resumable from a `Cursor` for lossless replay. | | `Stop` / `Close` | Shut down the embedded daemon, release the private transport. | | `Wait` | Single shared channel yielding the daemon's terminal run error. | | `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | @@ -165,6 +182,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w - `Subscribe` returns an unbuffered updates channel so a slow consumer applies backpressure end-to-end; the errs channel is cap-1 for a single terminal error. +- `SubscribeWallet` is backed by the daemon's canonical activity event + log: each streamed `Entry.Cursor` is that update's `event_seq`. The + host must persist the latest `Cursor` it has processed and pass it + back as `SubscribeRequest.Cursor` to resume without gaps — the daemon + replays every event after that cursor before switching to live. On + overflow the daemon ends the stream with a `SubscribeGapError` + (never drops updates silently); reopen `Subscribe` with that error's + `Cursor` rather than falling back to `List` plus a fresh live-only + subscription. - New options should follow the "apply after merge" placement so override semantics stay consistent. @@ -185,5 +211,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w - [swapclientserver/CLAUDE.md](../../swapclientserver/CLAUDE.md) — Daemon-side swap subserver (`-tags swapruntime`). - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/walletdk/mobile/AGENTS.md b/sdk/walletdk/mobile/AGENTS.md new file mode 100644 index 000000000..e239845f6 --- /dev/null +++ b/sdk/walletdk/mobile/AGENTS.md @@ -0,0 +1,79 @@ +# sdk/walletdk/mobile + +## Purpose + +Gomobile-safe facade over `sdk/walletdk`: a flat, JSON-bytes-in/bytes-out API +(plus a few scalar convenience methods) that respects gomobile's type +restrictions, so iOS/Android hosts — and the `cmd/walletdk-wasm` browser +bridge — can drive the embedded wallet daemon without a protobuf runtime or +callback interfaces. + +## Key Types + +- `Start(cfgJSON string) error` / `Stop() error` — singleton lifecycle for + the package-level embedded daemon; gomobile exposes free functions, so the + live client lives in a package-global `state`, not a host-carried handle. + Both are safe to call from any thread and race-guarded via an explicit + four-state machine (`statusStopped/Starting/Started/Stopping`). +- `Subscription` — pull-based handle (`Next`/`Close`) over a wallet activity + stream, standing in for the Go channel that cannot cross the gomobile + boundary; maps to a Kotlin `Flow` or Swift `AsyncStream`. +- Verb functions in `wallet.go` (`GetInfo`, `CreateWallet`, `UnlockWallet`, + `Balance`, `Deposit`, `Receive`, `PrepareSend`, `SendPrepared`, `List`, + `Exit`, `ExitStatus`, `ExitSummary`, `GetExitPlan`, `SweepWallet`, + `Status`, `Subscribe`) — each decodes a JSON request into the matching + `walletdk` DTO, calls the singleton `*walletdk.Client`, and marshals the + `walletdk` result back to JSON. +- `OpenWalletFromPasskey` (`passkey.go`) — imports/unlocks the wallet from a + hex-encoded WebAuthn PRF assertion output; the PRF→seed derivation lives + here in Go so the wasm and gomobile bindings share one source of truth and + the browser never handles raw seed material. +- `mobileConfig` (`config.go`) — flat, JSON-serializable subset of + `walletdk.Config`; `parseConfig`/`applyMobileConfig` overlay only the + fields a host actually set onto `walletdk.DefaultConfig()`, mirroring + walletdk's own enable-only convenience-merge semantics. +- Scalar conveniences (`convenience.go`): `ConfirmedBalanceSat`, + `PendingInboundSat`, `WalletReady`, `IsRunning` — avoid round-tripping JSON + for the hottest UI paths. + +## Relationships + +- **Depends on**: `sdk/walletdk` (the wrapped Go SDK; every verb here + proxies a `*walletdk.Client` method and reuses its request/response DTOs). +- **Depended on by**: `cmd/walletdk-wasm` (dispatches every browser + `walletdkCall` verb to this package), and out-of-repo iOS/Android hosts + that consume the gomobile-generated `.xcframework`/`.aar` built by + `gen_bindings.sh`. + +## Invariants + +- Every hand-written file except `doc.go` is gated behind `//go:build mobile + && walletdkrpc && swapruntime`. Without those tags the package compiles + down to just `doc.go`'s package comment (there is no separate `stub.go` + here, unlike `cmd/walletdk-wasm`) — `go build ./...` sees an effectively + empty package, and the real API only exists in a `gomobile bind` output. +- Gomobile type restrictions are load-bearing, not stylistic: no + `context.Context`, no channels, no maps, no slices other than `[]byte`, no + unsigned integers may cross an exported function signature. `Subscription` + and the JSON-bytes convention exist specifically to route around this. +- `Start` is synchronous and singleton-guarded: a second `Start` before + `Stop` returns an error rather than booting a second daemon. A `Stop` that + races an in-progress `Start` cancels the boot via `startCancel`/`gen`, and + that `Start` then tears down any client it produced instead of publishing + it — do not "simplify" the status/gen bookkeeping in `mobile.go` without + preserving that race guard. +- `activeClient` hands out the wrapper-owned `callCtx`; callers must not + retain it past the call, since `Stop` cancels it to unblock any in-flight + `Subscription.Next`. +- `gen_bindings.sh` is a separate build-artifact step (invokes `gomobile + bind` to produce the Android `.aar` / iOS `.xcframework`) — it does not + generate any of the `.go` files in this package, which are all + hand-written and reviewed normally. +- New host-facing capabilities should be added as a new verb/function here + mirroring an existing `walletdk.Client` method, then wired into + `cmd/walletdk-wasm`'s `walletCall` switch — keep the two in sync so the + wasm bridge never drifts from the mobile bindings. + +## Deep Docs + +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/sdk/walletdk/mobile/CLAUDE.md b/sdk/walletdk/mobile/CLAUDE.md new file mode 100644 index 000000000..e239845f6 --- /dev/null +++ b/sdk/walletdk/mobile/CLAUDE.md @@ -0,0 +1,79 @@ +# sdk/walletdk/mobile + +## Purpose + +Gomobile-safe facade over `sdk/walletdk`: a flat, JSON-bytes-in/bytes-out API +(plus a few scalar convenience methods) that respects gomobile's type +restrictions, so iOS/Android hosts — and the `cmd/walletdk-wasm` browser +bridge — can drive the embedded wallet daemon without a protobuf runtime or +callback interfaces. + +## Key Types + +- `Start(cfgJSON string) error` / `Stop() error` — singleton lifecycle for + the package-level embedded daemon; gomobile exposes free functions, so the + live client lives in a package-global `state`, not a host-carried handle. + Both are safe to call from any thread and race-guarded via an explicit + four-state machine (`statusStopped/Starting/Started/Stopping`). +- `Subscription` — pull-based handle (`Next`/`Close`) over a wallet activity + stream, standing in for the Go channel that cannot cross the gomobile + boundary; maps to a Kotlin `Flow` or Swift `AsyncStream`. +- Verb functions in `wallet.go` (`GetInfo`, `CreateWallet`, `UnlockWallet`, + `Balance`, `Deposit`, `Receive`, `PrepareSend`, `SendPrepared`, `List`, + `Exit`, `ExitStatus`, `ExitSummary`, `GetExitPlan`, `SweepWallet`, + `Status`, `Subscribe`) — each decodes a JSON request into the matching + `walletdk` DTO, calls the singleton `*walletdk.Client`, and marshals the + `walletdk` result back to JSON. +- `OpenWalletFromPasskey` (`passkey.go`) — imports/unlocks the wallet from a + hex-encoded WebAuthn PRF assertion output; the PRF→seed derivation lives + here in Go so the wasm and gomobile bindings share one source of truth and + the browser never handles raw seed material. +- `mobileConfig` (`config.go`) — flat, JSON-serializable subset of + `walletdk.Config`; `parseConfig`/`applyMobileConfig` overlay only the + fields a host actually set onto `walletdk.DefaultConfig()`, mirroring + walletdk's own enable-only convenience-merge semantics. +- Scalar conveniences (`convenience.go`): `ConfirmedBalanceSat`, + `PendingInboundSat`, `WalletReady`, `IsRunning` — avoid round-tripping JSON + for the hottest UI paths. + +## Relationships + +- **Depends on**: `sdk/walletdk` (the wrapped Go SDK; every verb here + proxies a `*walletdk.Client` method and reuses its request/response DTOs). +- **Depended on by**: `cmd/walletdk-wasm` (dispatches every browser + `walletdkCall` verb to this package), and out-of-repo iOS/Android hosts + that consume the gomobile-generated `.xcframework`/`.aar` built by + `gen_bindings.sh`. + +## Invariants + +- Every hand-written file except `doc.go` is gated behind `//go:build mobile + && walletdkrpc && swapruntime`. Without those tags the package compiles + down to just `doc.go`'s package comment (there is no separate `stub.go` + here, unlike `cmd/walletdk-wasm`) — `go build ./...` sees an effectively + empty package, and the real API only exists in a `gomobile bind` output. +- Gomobile type restrictions are load-bearing, not stylistic: no + `context.Context`, no channels, no maps, no slices other than `[]byte`, no + unsigned integers may cross an exported function signature. `Subscription` + and the JSON-bytes convention exist specifically to route around this. +- `Start` is synchronous and singleton-guarded: a second `Start` before + `Stop` returns an error rather than booting a second daemon. A `Stop` that + races an in-progress `Start` cancels the boot via `startCancel`/`gen`, and + that `Start` then tears down any client it produced instead of publishing + it — do not "simplify" the status/gen bookkeeping in `mobile.go` without + preserving that race guard. +- `activeClient` hands out the wrapper-owned `callCtx`; callers must not + retain it past the call, since `Stop` cancels it to unblock any in-flight + `Subscription.Next`. +- `gen_bindings.sh` is a separate build-artifact step (invokes `gomobile + bind` to produce the Android `.aar` / iOS `.xcframework`) — it does not + generate any of the `.go` files in this package, which are all + hand-written and reviewed normally. +- New host-facing capabilities should be added as a new verb/function here + mirroring an existing `walletdk.Client` method, then wired into + `cmd/walletdk-wasm`'s `walletCall` switch — keep the two in sync so the + wasm bridge never drifts from the mobile bindings. + +## Deep Docs + +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/serverconn/AGENTS.md b/serverconn/AGENTS.md index 365942130..4a53b12b3 100644 --- a/serverconn/AGENTS.md +++ b/serverconn/AGENTS.md @@ -10,6 +10,7 @@ background ingress polling with event routing. - `Runtime` — Main entry point wrapping DurableActor, ServerConnectionActor, and UnaryFacade. The egress DurableActor runs on the Read/Commit (`TxBehavior`) path: each handler builds its envelope and calls `Edge.Send` with NO SQLite writer held, then a short lease-fenced Commit folds the ack + dedup. It runs as a competing-consumer pool of `ConnectorConfig.EgressWorkers` worker loops, so the round and out-of-round actors' sends proceed concurrently; the single ingress puller is separate and unaffected. - `ServerConnectionActor` — Core behavior handling egress messages and the ingress loop. Dispatches `DurableUnaryQuery` values generically via `buildDurableUnary`. +- `ArkVersionNegotiator` — Single home for Ark protocol version selection (`ark_version.go`). `Bootstrap` performs the one bootstrap `GetInfo` over the operator's **direct** ArkService connection (`ArkVersionGetInfoClient`, never the mailbox edge) and returns the response + selected version; the daemon parses domain terms from the same response. The free function `ValidateRefreshSelection(resp, boundVersion)` enforces that a refresh-only `GetInfo` keeps the runtime bound (returns a permanent `*StatusError` on drift/disable). Enabled versions are derived from the response's ACTIVE `ArkVersionPolicy` entries. - `UnaryFacade` — Implements `mailboxrpc.RPCClient` for generated RPC stubs (low-latency path). Also provides `AwaitRPCTimeout` for bounded waits. - `ConnectorConfig` — Wiring configuration (edge address, mailbox IDs, dispatchers, store, durable unary builder, `EgressWorkers`). `EgressWorkers` sizes the egress worker pool (default `DefaultEgressWorkers` = 4); `<= 1` keeps the legacy single sender. The `DurableUnaryBuilder` field must be set to handle `DurableUnaryQuery` message types; otherwise those messages are rejected. The `AuthSignature` field holds the Schnorr auth sig injected into every outbound envelope via `mergeAuthHeaders` (auth header always wins over caller-provided headers). - `PubKeyMailboxID` — Derives canonical mailbox ID from a public key (hex-encoded compressed SEC). Panics on nil. @@ -34,7 +35,7 @@ background ingress polling with event routing. ## Relationships -- **Depends on**: `baselib/actor` (DurableActor infrastructure), `mailbox/*` (Envelope, RpcMeta, MailboxServiceClient). +- **Depends on**: `baselib/actor` (DurableActor infrastructure), `mailbox/*` (Envelope, RpcMeta, MailboxServiceClient), `arkrpc` (`GetInfo` request/response + `ArkVersionPolicy` for version negotiation). - **Depended on by**: `round` (outbound RPCs), `oor` (durable transport), `darepod` (wiring). - **Sends (egress → remote mailbox)**: - `SendClientEventRequest` (durable): wraps `JoinRoundRequest`, `JoinRoundAccept`, `JoinRoundReject`, `SubmitNoncesRequest`, `SubmitPartialSigRequest`, `SubmitForfeitSigRequest`. `JoinRoundAccept` / `JoinRoundReject` are the explicit responses to a server-issued seal-time `JoinRoundQuote` (#270); both echo the `quote_id` so the server can drop stale responses after a reseal. diff --git a/swapwallet/AGENTS.md b/swapwallet/AGENTS.md index 81ca50136..ef7eafc18 100644 --- a/swapwallet/AGENTS.md +++ b/swapwallet/AGENTS.md @@ -19,9 +19,13 @@ default builds avoid the swap executor's dependency graph. `history`, or admin proxy helpers; no business logic lives here. - `Runtime` — Owns the in-process swap lifecycle: synchronous resume-on-startup, deadline watcher (overlays stuck entries as - FAILED), monitor loop (fans normalized updates to subscribers). - Anchored to the daemon root context so an RPC client disconnect can - never cancel in-flight work. + FAILED), monitor loop (fans normalized updates to subscribers), and + the canonical activity-log projector (`project`/`projectAndEmit`) + that durably writes every emitted `WalletEntry` and stamps it with + the store-assigned `event_seq` before fanning it out, so + `SubscribeWallet` can hand callers a resumable cursor. Anchored to + the daemon root context so an RPC client disconnect can never + cancel in-flight work. - `Deps` — Composition struct: `SwapBackend` (in-Go swap runtime), `SwapService` (gRPC-shaped swap subserver handle), `RPCServer` (narrow daemonrpc contract), `ChainParams` (Bitcoin network — used to @@ -55,6 +59,8 @@ default builds avoid the swap executor's dependency graph. - `swapclientserver` (typed `Backend` handle and runtime resume) - `darepod` (`SwapBackend` interface) - `ledger` (account name constants for OOR ledger projection) + - `db` (`ActivityProjection` DTO fed to the canonical activity-log + projector wired in via `Deps.ActivityStore`) - `btclog/v2` (subsystem logger) - **Depended on by**: - `cmd/darepod` (`walletdkrpc.go` registers the subserver behind the @@ -71,21 +77,48 @@ default builds avoid the swap executor's dependency graph. `StartReceiveRequest`, `ListSwapsRequest`, `SubscribeSwapsRequest` - **Receives**: - ← API: `walletdkrpc.{Create,Unlock,Send,Recv,List,Balance,Deposit, - Status,Exit,ExitStatus,SubscribeWallet}Request` + Status,Exit,ExitStatus,ExitSummary,SubscribeWallet}Request` ## Invariants -- Admin handlers (`Create`/`Unlock`/`Exit`/`ExitStatus`) are - admin-shape: they reach daemonrpc via the injected `RPCServer` and - DO NOT depend on `Runtime`, router, recv, or history. Create and +- Admin handlers (`Create`/`Unlock`/`Exit`/`ExitStatus`/`ExitSummary`) + are admin-shape: they reach daemonrpc via the injected `RPCServer` + and DO NOT depend on `Runtime`, router, recv, or history. Create and Unlock must work before the swap subsystem is live. - Background goroutines (monitor loop, deadline watcher, resume sweep) are anchored to the daemon root context, NEVER to RPC-call contexts. An RPC client disconnect cannot cancel in-flight work. -- `WalletEntry.id` is the stable canonical id for SEND-invoice and - RECV (Lightning payment_hash) across the entire pending → terminal - lifecycle. EXIT and DEPOSIT rows do not yet share an id between - pending and confirmed in v1; see `doc.go`. +- `projectMu` serializes every project-then-emit across all concurrent + producers (monitor loop, reconciler, credit poll, deadline watcher, + RPC handlers) so the `event_seq` a transition is assigned by the + store and the live emit that carries it stay in the same order. A + transition only reaches subscribers when it is durable (`seq > 0`); + a change-suppressed no-op or a failed projection emits nothing. + Without this lock a later-committed but lower-seq event could emit + after a higher one, and a `SubscribeWallet` cursor would advance + past it, silently dropping the update. +- A dispatched pure-Lightning invoice send (`sendInvoiceIntent`) + eagerly calls `project` (not `projectAndEmit`) for its pending row + immediately after `StartPay` accepts it, off the RPC context, so a + caller polling `List`/`InspectActivity` to block on settlement + observes the row instantly instead of racing the swap monitor's + asynchronous first `SubscribeSwaps` update. The monitor still owns + the live `SubscribeWallet` emit for that row — calling + `projectAndEmit` here too would fan the same pending row out twice. + The credit-backed pay path (`sendCreditInvoiceIntent`) instead calls + `projectAndEmit` directly for its initial pending row: a credit-only + pay has no swap session to emit a follow-up update at all, so the + eager write must also be the live emit. A later monitor update for a + mixed pay simply supersedes it. +- `WalletEntry.id` is the stable canonical id across the entire pending + → terminal lifecycle for SEND-invoice and RECV (Lightning + payment_hash), on-chain-send / cooperative-leave EXIT (the daemon's + leave-job id / `send_job_id`), and DEPOSIT (`deposit-
`, keyed + on the allocated boarding address surfaced on the confirmed history + row). A unilateral EXIT still keys by the consumed VTXO outpoint. The + pending → COMPLETE transition for EXIT/DEPOSIT lands via the + derive/backfill pass; live cross-restart reconciliation is C2. See + `doc.go`. - Onchain SEND is routed through `RPCServer.SendOnChain` which delegates to `wallet.SendOnChainRequest`. Two modes: **sweep-all** (non-empty `SweepOutpoints` — drains those VTXOs exactly, no change, leave output @@ -109,10 +142,17 @@ default builds avoid the swap executor's dependency graph. stuck row appears as FAILED even when the caller asks for `pending_only=false`. - **DEPOSIT rows backed by the `wallet_utxo_created` ledger event** - mirror the ledger confirmation status. Confirmed on-chain boarding - deposits surface as `ENTRY_STATUS_COMPLETE`, while unconfirmed - boarding funds are represented by the synthetic - `boarding-unconfirmed` pending row from `GetBalance`. + mirror the ledger confirmation status and are keyed + `deposit-
` from the confirmed row's + `TransactionHistoryEntry.boarding_address`; every UTXO paid to that + address is SUMMED into one row (`sumDepositsByAddress`) so a reused + address shows its total. `Deposit` does NOT project a row — allocating + an address is not a pending deposit — it only returns that id so a + caller can correlate the eventual confirmed row. Per-address is the + CONFIRMED phase only: unconfirmed boarding funds have no per-address + source (the daemon exposes only aggregate `boarding_unconfirmed_sat`), + so they surface via `Balance` and the single derive-path-only + `boarding-unconfirmed` row, never projected into the store. - **`Balance` projection** maps daemonrpc fields onto the walletdkrpc shape: `confirmed_sat` is VTXO-only (`vtxo_balance_sat`), `pending_in_sat` sums `boarding_confirmed_sat + diff --git a/swapwallet/CLAUDE.md b/swapwallet/CLAUDE.md index 735c0a003..ef7eafc18 100644 --- a/swapwallet/CLAUDE.md +++ b/swapwallet/CLAUDE.md @@ -19,9 +19,13 @@ default builds avoid the swap executor's dependency graph. `history`, or admin proxy helpers; no business logic lives here. - `Runtime` — Owns the in-process swap lifecycle: synchronous resume-on-startup, deadline watcher (overlays stuck entries as - FAILED), monitor loop (fans normalized updates to subscribers). - Anchored to the daemon root context so an RPC client disconnect can - never cancel in-flight work. + FAILED), monitor loop (fans normalized updates to subscribers), and + the canonical activity-log projector (`project`/`projectAndEmit`) + that durably writes every emitted `WalletEntry` and stamps it with + the store-assigned `event_seq` before fanning it out, so + `SubscribeWallet` can hand callers a resumable cursor. Anchored to + the daemon root context so an RPC client disconnect can never + cancel in-flight work. - `Deps` — Composition struct: `SwapBackend` (in-Go swap runtime), `SwapService` (gRPC-shaped swap subserver handle), `RPCServer` (narrow daemonrpc contract), `ChainParams` (Bitcoin network — used to @@ -55,6 +59,8 @@ default builds avoid the swap executor's dependency graph. - `swapclientserver` (typed `Backend` handle and runtime resume) - `darepod` (`SwapBackend` interface) - `ledger` (account name constants for OOR ledger projection) + - `db` (`ActivityProjection` DTO fed to the canonical activity-log + projector wired in via `Deps.ActivityStore`) - `btclog/v2` (subsystem logger) - **Depended on by**: - `cmd/darepod` (`walletdkrpc.go` registers the subserver behind the @@ -71,17 +77,39 @@ default builds avoid the swap executor's dependency graph. `StartReceiveRequest`, `ListSwapsRequest`, `SubscribeSwapsRequest` - **Receives**: - ← API: `walletdkrpc.{Create,Unlock,Send,Recv,List,Balance,Deposit, - Status,Exit,ExitStatus,SubscribeWallet}Request` + Status,Exit,ExitStatus,ExitSummary,SubscribeWallet}Request` ## Invariants -- Admin handlers (`Create`/`Unlock`/`Exit`/`ExitStatus`) are - admin-shape: they reach daemonrpc via the injected `RPCServer` and - DO NOT depend on `Runtime`, router, recv, or history. Create and +- Admin handlers (`Create`/`Unlock`/`Exit`/`ExitStatus`/`ExitSummary`) + are admin-shape: they reach daemonrpc via the injected `RPCServer` + and DO NOT depend on `Runtime`, router, recv, or history. Create and Unlock must work before the swap subsystem is live. - Background goroutines (monitor loop, deadline watcher, resume sweep) are anchored to the daemon root context, NEVER to RPC-call contexts. An RPC client disconnect cannot cancel in-flight work. +- `projectMu` serializes every project-then-emit across all concurrent + producers (monitor loop, reconciler, credit poll, deadline watcher, + RPC handlers) so the `event_seq` a transition is assigned by the + store and the live emit that carries it stay in the same order. A + transition only reaches subscribers when it is durable (`seq > 0`); + a change-suppressed no-op or a failed projection emits nothing. + Without this lock a later-committed but lower-seq event could emit + after a higher one, and a `SubscribeWallet` cursor would advance + past it, silently dropping the update. +- A dispatched pure-Lightning invoice send (`sendInvoiceIntent`) + eagerly calls `project` (not `projectAndEmit`) for its pending row + immediately after `StartPay` accepts it, off the RPC context, so a + caller polling `List`/`InspectActivity` to block on settlement + observes the row instantly instead of racing the swap monitor's + asynchronous first `SubscribeSwaps` update. The monitor still owns + the live `SubscribeWallet` emit for that row — calling + `projectAndEmit` here too would fan the same pending row out twice. + The credit-backed pay path (`sendCreditInvoiceIntent`) instead calls + `projectAndEmit` directly for its initial pending row: a credit-only + pay has no swap session to emit a follow-up update at all, so the + eager write must also be the live emit. A later monitor update for a + mixed pay simply supersedes it. - `WalletEntry.id` is the stable canonical id across the entire pending → terminal lifecycle for SEND-invoice and RECV (Lightning payment_hash), on-chain-send / cooperative-leave EXIT (the daemon's diff --git a/txconfirm/AGENTS.md b/txconfirm/AGENTS.md index 6af44f7e9..2244f01d9 100644 --- a/txconfirm/AGENTS.md +++ b/txconfirm/AGENTS.md @@ -147,5 +147,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon lifecycle, CPFP correctness invariants, PSBT finalization, service-key round trip, and eviction. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/txconfirm/CLAUDE.md b/txconfirm/CLAUDE.md index 6af44f7e9..2244f01d9 100644 --- a/txconfirm/CLAUDE.md +++ b/txconfirm/CLAUDE.md @@ -147,5 +147,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon lifecycle, CPFP correctness invariants, PSBT finalization, service-key round trip, and eviction. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/unroll/AGENTS.md b/unroll/AGENTS.md index 1ce09d608..ed60f931a 100644 --- a/unroll/AGENTS.md +++ b/unroll/AGENTS.md @@ -25,10 +25,12 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol across a cross-actor Ask. - `Config` — per-actor wiring. Notable: `TargetOutpoint`, `ActorID`, `DeliveryStore`, `ProofAssembler`, `VTXOStore`, `TxConfirmRef`, - `ChainSource`, `Wallet` (`SweepWallet`), + `ChainSource`, `ExitSpendPolicyResolver` (nil falls back to the + built-in standard VTXO timeout resolver), `Wallet` (`SweepWallet`), `MaxSweepFeeRateSatPerVByte`, `FraudCheckpointSafetyMargin int32` (overrides the fraud-triggered unroll backstop margin in blocks; - zero falls back to the default), `RegistryRef`. + zero falls back to the default), `RegistryRef`, `LedgerSink` + (receives the confirmed exit fee after the final sweep confirms). - `behavior` — actor behavior implementing `actor.TxBehavior[Msg, Resp, unrollTx]`. Holds `b.sweepTx` (restored from checkpoint on boot) so retries and replays converge on a single sweep txid / pkScript under @@ -42,6 +44,24 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol `SpendObservedMsg`, `GetStateRequest`. Each ships a per-message TLV codec (no JSON) with a pinned record-type layout; round-trips in `messages_test.go`. +- `GetStateResp` — `GetStateRequest`'s Ask reply. Carries the durable + `PlannerState`/`Phase`/`FailReason`/`SweepTxid` plus `Progress + *ExitProgress`, a derived view populated by `behavior.exitProgress` + from a fresh `planner.Plan` at the actor's current best height. The + reply is an in-memory Ask response rather than a durable mailbox + message, so adding `Progress` needed no codec change. +- `ExitProgress` — human-facing progress summary: confirmed / in-flight + / ready / blocked proof-tx counts, the frontier `CurrentLayer` (see + `frontierLayer`), `BestCaseBlocksRemaining` (`bestCaseBlocksRemaining`; + folds in both the CSV wait and the exit policy's cached + `requiredLockTime`, whichever is longer, so a vHTLC + refund-without-receiver policy is not reported as imminent right at + CSV maturity), and `ActualSweepFeeSat` once a sweep tx exists. The fee + (`actualSweepFeeSat`) is the proof's `TargetOutput().Value` — what the + exit-spend policy actually spends — minus the swept output value, not + the descriptor's nominal `Amount`, which can diverge (tree-level fee + deduction, dust). `nil` when the planner/proof are not yet loaded or + the actor is Idle. - `StartTrigger` — `TriggerManual`, `TriggerCriticalExpiry`, `TriggerRestart`, `TriggerFraudSpend`. - `Phase` — control-plane phase: `PhasePending`, @@ -97,6 +117,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - `ExitSpendPolicyResolver` — interface for looking up the final spend policy by `(ExitPolicyKind, ExitPolicyRef)`. Implemented by `vhtlcrecovery/unrollpolicy.ExitSpendPolicyResolver`. +- `GetStatusRequest` / `GetStatusResp` — read-only status probe. The + default coarse probe (`Detailed: false`) only reads `r.active` / + `Store.GetRecord` and never touches a live child, so routine polling + can't write a read-only mailbox row. `Detailed: true` additionally + Asks the live child (`detailedChildState` → `childState`) under a + short local timeout (`detailedStatusAskTimeout`) for a `GetStateResp` + (including `ExitProgress`); a timeout or Ask error degrades to the + coarse view rather than failing the probe or stalling the + single-goroutine registry. ### Support @@ -137,6 +166,36 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol spend/confirmation watches (proof roots and intermediate ancestors can confirm before the target VTXO's creation height). +### Exit funding & feasibility (`feasibility.go`, `exit_plan.go`) + +- `AssessExitFeasibility` / `ExitFeasibility` — pure, IO-free economic + model deciding whether a unilateral exit is worth admitting: sweep + fee vs. dust, total recovery cost vs. VTXO value, wallet balance and + distinct-input count vs. the CPFP budget. The CPFP budget is + `CPFPChildVBytes*NumRecoveryTxs + RecoveryTxVBytes` — every recovery + (branch/checkpoint) tx on the exit path is zero-fee, so its CPFP + child actually pays the ancestor-package fee over parent+child + weight; omitting `RecoveryTxVBytes` under-counts the cost and + under-recommends funding for anything past the smallest VTXOs. +- `RecoveryTxCount` / `RecoveryTxVBytes` — descriptor-only estimate of + the recovery-tx count, ancestry-path count, and summed parent + vBytes. Both derive from the shared `recoveryEstimate`, which sizes + commitment-tree fragments exactly from the extracted `TreePath` + (`Tree.NumTx`, `treePathVBytes`) and falls back to + `defaultRecoveryTxVBytes` per tx for pruned fragments or an OOR + chain with no resolved lineage material — so the count and the + parent weight can never drift apart. When `LineageMaterial` is + supplied (`PlanExitFunding`), each finalized OOR checkpoint/ark tx is + measured directly (`extraNodeVBytes`) instead of approximated from + `ChainDepth`. +- `ExitFundingPlan` / `PlanExitFunding` — user-facing funding + projection layered on `AssessExitFeasibility`. + `RecommendedExitFeeInputAmount` derives the per-UTXO funding + suggestion from the feasibility verdict, floored at + `DefaultFeeInputMinAmountSat`. +- `ExitFundingAddressBook` — caches one funding address per target + outpoint so repeated plan polling does not burn wallet addresses. + ## Relationships - **Depends on**: `baselib/actor` (`DurableActor`, `TLVMessage`, @@ -165,9 +224,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - → `vtxo` manager (Tell, via `RegistryConfig.VTXOExitObserver`): `ExitOutcomeNotification` on each child's terminal outcome — the reverse feedback edge for darepo-client#602. + - → per-target child (Ask, bounded local timeout): `GetStateRequest` + from the registry when `GetStatusRequest.Detailed` is set, to + enrich `GetStatusResp` with the child's live `ExitProgress`; a + timeout or Ask error falls back to the coarse registry record. - **Receives**: - ← API (registry): `EnsureUnrollRequest`, `GetStatusRequest` - (from `darepod` RPC via chain resolver). + (from `darepod` RPC via chain resolver; `Detailed` opts into the + child Ask above). - ← registry (internal): `persistActiveRecordMsg`, `persistRecordResultMsg`, `UnrollTerminatedMsg`. - ← per-target mailbox: all messages listed under Per-target actor. @@ -260,6 +324,26 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol operator-sourced OOR artifacts flow into proof assembly, so a zero- or short-output node maps to a retryable error rather than a goroutine panic. +- **Derived fee/cost figures read the proof's target output, not the + descriptor amount.** `actualSweepFeeSat` (feeding + `ExitProgress.ActualSweepFeeSat`) and `exitCostMsg` (feeding the + ledger `ExitCostMsg`) both take the swept input value from + `proof.TargetOutput()` — the exact output the exit-spend policy + spends — because tree-level fee deduction or dust handling can make + it diverge from the descriptor's nominal `Amount`. +- **CPFP funding must charge for recovery-tx weight, not just the CPFP + children.** Every recovery (branch/checkpoint) tx on the exit path is + zero-fee; `txconfirm`'s broadcaster pays the ancestor-package fee + over parent+child weight, so `AssessExitFeasibility`'s CPFP budget + includes `RecoveryTxVBytes` alongside + `CPFPChildVBytes*NumRecoveryTxs`; dropping the parent term + under-recommends funding for anything past the smallest VTXOs. +- **`ExitProgress` is a pure read-only projection, never persisted.** + `behavior.exitProgress` recomputes it on every detailed status Ask + from the durable planner snapshot; it is not part of `JobState` or + the TLV checkpoint, and a status probe that races actor startup + degrades to `nil` (coarse phase only) instead of a misleading zero + snapshot. ## Deep Docs @@ -272,5 +356,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - [lib/recovery/CLAUDE.md](../lib/recovery/CLAUDE.md) — immutable proof graph. - [ARCHITECTURE.md](../ARCHITECTURE.md) — system-wide package map. - - diff --git a/unroll/CLAUDE.md b/unroll/CLAUDE.md index 1ce09d608..ed60f931a 100644 --- a/unroll/CLAUDE.md +++ b/unroll/CLAUDE.md @@ -25,10 +25,12 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol across a cross-actor Ask. - `Config` — per-actor wiring. Notable: `TargetOutpoint`, `ActorID`, `DeliveryStore`, `ProofAssembler`, `VTXOStore`, `TxConfirmRef`, - `ChainSource`, `Wallet` (`SweepWallet`), + `ChainSource`, `ExitSpendPolicyResolver` (nil falls back to the + built-in standard VTXO timeout resolver), `Wallet` (`SweepWallet`), `MaxSweepFeeRateSatPerVByte`, `FraudCheckpointSafetyMargin int32` (overrides the fraud-triggered unroll backstop margin in blocks; - zero falls back to the default), `RegistryRef`. + zero falls back to the default), `RegistryRef`, `LedgerSink` + (receives the confirmed exit fee after the final sweep confirms). - `behavior` — actor behavior implementing `actor.TxBehavior[Msg, Resp, unrollTx]`. Holds `b.sweepTx` (restored from checkpoint on boot) so retries and replays converge on a single sweep txid / pkScript under @@ -42,6 +44,24 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol `SpendObservedMsg`, `GetStateRequest`. Each ships a per-message TLV codec (no JSON) with a pinned record-type layout; round-trips in `messages_test.go`. +- `GetStateResp` — `GetStateRequest`'s Ask reply. Carries the durable + `PlannerState`/`Phase`/`FailReason`/`SweepTxid` plus `Progress + *ExitProgress`, a derived view populated by `behavior.exitProgress` + from a fresh `planner.Plan` at the actor's current best height. The + reply is an in-memory Ask response rather than a durable mailbox + message, so adding `Progress` needed no codec change. +- `ExitProgress` — human-facing progress summary: confirmed / in-flight + / ready / blocked proof-tx counts, the frontier `CurrentLayer` (see + `frontierLayer`), `BestCaseBlocksRemaining` (`bestCaseBlocksRemaining`; + folds in both the CSV wait and the exit policy's cached + `requiredLockTime`, whichever is longer, so a vHTLC + refund-without-receiver policy is not reported as imminent right at + CSV maturity), and `ActualSweepFeeSat` once a sweep tx exists. The fee + (`actualSweepFeeSat`) is the proof's `TargetOutput().Value` — what the + exit-spend policy actually spends — minus the swept output value, not + the descriptor's nominal `Amount`, which can diverge (tree-level fee + deduction, dust). `nil` when the planner/proof are not yet loaded or + the actor is Idle. - `StartTrigger` — `TriggerManual`, `TriggerCriticalExpiry`, `TriggerRestart`, `TriggerFraudSpend`. - `Phase` — control-plane phase: `PhasePending`, @@ -97,6 +117,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - `ExitSpendPolicyResolver` — interface for looking up the final spend policy by `(ExitPolicyKind, ExitPolicyRef)`. Implemented by `vhtlcrecovery/unrollpolicy.ExitSpendPolicyResolver`. +- `GetStatusRequest` / `GetStatusResp` — read-only status probe. The + default coarse probe (`Detailed: false`) only reads `r.active` / + `Store.GetRecord` and never touches a live child, so routine polling + can't write a read-only mailbox row. `Detailed: true` additionally + Asks the live child (`detailedChildState` → `childState`) under a + short local timeout (`detailedStatusAskTimeout`) for a `GetStateResp` + (including `ExitProgress`); a timeout or Ask error degrades to the + coarse view rather than failing the probe or stalling the + single-goroutine registry. ### Support @@ -137,6 +166,36 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol spend/confirmation watches (proof roots and intermediate ancestors can confirm before the target VTXO's creation height). +### Exit funding & feasibility (`feasibility.go`, `exit_plan.go`) + +- `AssessExitFeasibility` / `ExitFeasibility` — pure, IO-free economic + model deciding whether a unilateral exit is worth admitting: sweep + fee vs. dust, total recovery cost vs. VTXO value, wallet balance and + distinct-input count vs. the CPFP budget. The CPFP budget is + `CPFPChildVBytes*NumRecoveryTxs + RecoveryTxVBytes` — every recovery + (branch/checkpoint) tx on the exit path is zero-fee, so its CPFP + child actually pays the ancestor-package fee over parent+child + weight; omitting `RecoveryTxVBytes` under-counts the cost and + under-recommends funding for anything past the smallest VTXOs. +- `RecoveryTxCount` / `RecoveryTxVBytes` — descriptor-only estimate of + the recovery-tx count, ancestry-path count, and summed parent + vBytes. Both derive from the shared `recoveryEstimate`, which sizes + commitment-tree fragments exactly from the extracted `TreePath` + (`Tree.NumTx`, `treePathVBytes`) and falls back to + `defaultRecoveryTxVBytes` per tx for pruned fragments or an OOR + chain with no resolved lineage material — so the count and the + parent weight can never drift apart. When `LineageMaterial` is + supplied (`PlanExitFunding`), each finalized OOR checkpoint/ark tx is + measured directly (`extraNodeVBytes`) instead of approximated from + `ChainDepth`. +- `ExitFundingPlan` / `PlanExitFunding` — user-facing funding + projection layered on `AssessExitFeasibility`. + `RecommendedExitFeeInputAmount` derives the per-UTXO funding + suggestion from the feasibility verdict, floored at + `DefaultFeeInputMinAmountSat`. +- `ExitFundingAddressBook` — caches one funding address per target + outpoint so repeated plan polling does not burn wallet addresses. + ## Relationships - **Depends on**: `baselib/actor` (`DurableActor`, `TLVMessage`, @@ -165,9 +224,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - → `vtxo` manager (Tell, via `RegistryConfig.VTXOExitObserver`): `ExitOutcomeNotification` on each child's terminal outcome — the reverse feedback edge for darepo-client#602. + - → per-target child (Ask, bounded local timeout): `GetStateRequest` + from the registry when `GetStatusRequest.Detailed` is set, to + enrich `GetStatusResp` with the child's live `ExitProgress`; a + timeout or Ask error falls back to the coarse registry record. - **Receives**: - ← API (registry): `EnsureUnrollRequest`, `GetStatusRequest` - (from `darepod` RPC via chain resolver). + (from `darepod` RPC via chain resolver; `Detailed` opts into the + child Ask above). - ← registry (internal): `persistActiveRecordMsg`, `persistRecordResultMsg`, `UnrollTerminatedMsg`. - ← per-target mailbox: all messages listed under Per-target actor. @@ -260,6 +324,26 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol operator-sourced OOR artifacts flow into proof assembly, so a zero- or short-output node maps to a retryable error rather than a goroutine panic. +- **Derived fee/cost figures read the proof's target output, not the + descriptor amount.** `actualSweepFeeSat` (feeding + `ExitProgress.ActualSweepFeeSat`) and `exitCostMsg` (feeding the + ledger `ExitCostMsg`) both take the swept input value from + `proof.TargetOutput()` — the exact output the exit-spend policy + spends — because tree-level fee deduction or dust handling can make + it diverge from the descriptor's nominal `Amount`. +- **CPFP funding must charge for recovery-tx weight, not just the CPFP + children.** Every recovery (branch/checkpoint) tx on the exit path is + zero-fee; `txconfirm`'s broadcaster pays the ancestor-package fee + over parent+child weight, so `AssessExitFeasibility`'s CPFP budget + includes `RecoveryTxVBytes` alongside + `CPFPChildVBytes*NumRecoveryTxs`; dropping the parent term + under-recommends funding for anything past the smallest VTXOs. +- **`ExitProgress` is a pure read-only projection, never persisted.** + `behavior.exitProgress` recomputes it on every detailed status Ask + from the durable planner snapshot; it is not part of `JobState` or + the TLV checkpoint, and a status probe that races actor startup + degrades to `nil` (coarse phase only) instead of a misleading zero + snapshot. ## Deep Docs @@ -272,5 +356,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - [lib/recovery/CLAUDE.md](../lib/recovery/CLAUDE.md) — immutable proof graph. - [ARCHITECTURE.md](../ARCHITECTURE.md) — system-wide package map. - - diff --git a/walletcore/AGENTS.md b/walletcore/AGENTS.md index 9a71ea916..97405efad 100644 --- a/walletcore/AGENTS.md +++ b/walletcore/AGENTS.md @@ -36,7 +36,11 @@ btcwallet.BtcWallet regardless of the underlying chain source. `addressForTaprootScript`, repopulating the filter without a second import attempt — covers the case where btcwallet already persisted the script but the in-memory filter started empty. -- `WalletPassphrase` is shared across all wallet backends for both `PrivatePass` and `PublicPass`. +- The user-supplied `Config.WalletPassword` is btcwallet's `PrivatePass`; the + static `PublicWalletPassphrase` constant covers only public (watch-only) + data. A nil `Config.Seed` opens an existing wallet database; a non-nil seed + creates a new one, and backends must refuse a seed when a database already + exists (btcwallet would silently ignore it). ## Deep Docs