Skip to content

feat(node): control.wallet.balance read (#1851) - #140

Merged
MichaelTaylor3d merged 6 commits into
mainfrom
claude/task-reminder-routine-45xjbj
Aug 2, 2026
Merged

feat(node): control.wallet.balance read (#1851)#140
MichaelTaylor3d merged 6 commits into
mainfrom
claude/task-reminder-routine-45xjbj

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

What

Implements control.wallet.balance — the READ-ONLY balance of a PUBLIC xch1… address for one asset (XCH or $DIG) — closing the node side of dig_ecosystem #1851 (the dig-app ⇄ dig-node balance byte-contract).

  • WalletBackend::balance_for_address(address, asset) (crates/dig-wallet/src/sage/rpc.rs) → WalletBalanceResult { balance, pending, synced, peak_height }. Reuses the existing sync-state routing (routing::route(db_synced, scoped_to_wallet)): the local DB (balance_scoped confirmed-only + a new pending_scoped) is authoritative for the wallet's own synced addresses; otherwise the coinset fallback (chia-query) answers, with synced reflecting the real DB state. peak_height comes from db.sync_state().peak_height (the real chain view, never faked).
  • control.wallet.balance dispatch (crates/dig-node-service/src/control.rs) — an OPEN read (is_open_control_read; wallet_authz::classifyOther): it needs only an address (no seed, no signing key, zero custody risk), served on the loopback read plane while still routing through the control catalog (so it gets a CLI verb + stays in the lockstep drift gate). Params { address, asset:"xch"|"dig" }; result { balance, pending, synced, peak_height }.
  • CLI parity: dig-node wallet balance <addr> [--asset xch|dig] (required by the #426 every-control.*-has-a-CLI-verb drift gate + the CONTROL_METHODS↔dispatch lockstep).
  • Distinct failure errors (never a fabricated 0): -32040 WALLET_NO_CHAIN_SOURCE / -32041 WALLET_NOT_SYNCED / -32042 WALLET_READ_FAILED. A synced empty address is a SUCCESS with balance:0. The -3204x wallet range was chosen to avoid a collision with dig-chat's -3205x (the whole -320xx registry was verified; recorded in the superproject canonical skill).
  • $DIG CAT id sourced from digstore_chain::dig::DIG_ASSET_ID (canonical) — never hardcoded.

Byte-contract with dig-app (the point of #1851)

The shared dig-node-control-interface 0.3.0 crate (crates.io, consumed by dig-app-core) defines WalletBalanceResult { balance: u64, pending: u64, synced: bool, peak_height: Option<u32> } + Asset { xch, dig }. dig-app-core's frozen BalanceResponse { balance: u64 } reads balance as a strict SUPERSET (numeric u64, no deny_unknown_fields — it ignores the richer fields), so the node needs no dig-app code change.

balance/pending are emitted as numeric u64 (saturating u128→u64 at the wire boundary; a single-address total fits u64 ≈ 18.4M XCH) — NOT JSON strings. Emitting strings would break dig-app's u64 deserialize; the last commit here fixes exactly that and pins it with balance_wire_emits_numeric_amounts_matching_app_contract, which deserializes the emitted value into a u64-typed mirror of dig-app's struct (proven load-bearing by reverting only the fix → the test fails on String("12345") vs Number(12345)).

Blast radius

balance_wire() is called only from wallet_balance(), reached only via the control.wallet.balance RPC method. The money/spend/signing path (#1702) is untouched; the auth plane is untouched (open read). Confirmed via direct code read + grep (gitnexus unavailable for the standalone dig-node checkout — the §2.0 documented fallback).

Verified

  • cargo fmt, cargo check --lib/--tests, cargo clippy --lib -- -D warnings — all clean on dig-node-service.
  • dig-wallet: full compile + all 40 unit tests pass (5 new, 2 proven load-bearing by revert).
  • dig-node-service: balance_wire tests 2/2 pass.
  • Deferred to CI (local sandbox ENOSPC while linking the full integration-test binaries): the full cargo test -p dig-node-service integration suite + coverage. This PR is scoped to a single wire-mapping function + read handler with no money-path touch; CI's larger disk runs the full gate.

Version

Workspace 0.74.2 → 0.75.0 (minor — new capability), dig-wallet 0.11.0 → 0.12.0 (new public API). Rebased onto main after #139 (workspace patch 0.74.3); resolved to 0.75.0 (the minor supersedes the patch).

Closes #1851.

Co-Authored-By: Claude noreply@anthropic.com


Generated by Claude Code

MichaelTaylor3d and others added 6 commits August 2, 2026 10:12
Co-Authored-By: Claude <noreply@anthropic.com>
…851)

- dig-node wallet balance <addr> [--asset xch|dig] CLI verb (control-parity
  drift gate: every control.* method needs a CLI verb).
- SPEC: control catalog + error registry (-3204x wallet range) + the
  is_open_control_read exception documented.
- Version: workspace 0.74.2 -> 0.75.0; dig-wallet 0.11.0 -> 0.12.0.

Co-Authored-By: Claude <noreply@anthropic.com>
…h contract (#1851)

control.wallet.balance emitted balance/pending as JSON strings
(r.balance.to_string()), but dig-node-control-interface 0.3.0's
WalletBalanceResult { balance: u64, pending: u64, .. } and dig-app's
BalanceResponse { balance: u64 } both expect JSON numbers, breaking
dig-app's balance read (serde u64-from-string errors) and the leg-1
conformance KAT.

Extract the wire mapping into a pure balance_wire() helper that
saturating-casts the internal u128 total to u64 (a single address can
never exceed u64::MAX mojos, ~18.4M XCH) and emits it as a JSON number.
Internal WalletBalanceResult fields stay u128 for headroom in summed
intermediate math; only the wire boundary changes.

Blast radius checked: balance_wire() is called only from
wallet_balance() (control.rs), which is reached only via the
control.wallet.balance RPC method — no other caller emits these
fields. The -3204x wallet error codes are untouched.

Adds two tests: a golden-shape test proving the emitted balance
deserializes into a u64-typed mirror struct (fails against the
string-emitting version - proven by reverting the fix and rerunning),
and a u128-overflow saturation test. Corrects SPEC.md's
control.wallet.balance description accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
…e default (#1851)

Co-Authored-By: Claude <noreply@anthropic.com>
…routine-45xjbj

# Conflicts:
#	Cargo.lock
#	Cargo.toml
@MichaelTaylor3d
MichaelTaylor3d merged commit 8f75fd9 into main Aug 2, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the claude/task-reminder-routine-45xjbj branch August 2, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant