Task
Paginate the unspent-coin read: control.wallet.coins and the dign wallet coins CLI that fronts it.
Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3170
User directive, verbatim: "the dig-node cli also needs a command in its wallet to list available
coins, paginated"
What already exists — do NOT rebuild it
control.wallet.coins — shipped, per-address, unspent-only (crates/dig-node-service/src/control.rs:148)
dign wallet coins <ADDRESS> --asset xch|dig [--json] — shipped, unpaginated
- The persisted
coins table with idx_coins_unspent … WHERE spent_height IS NULL, and
WalletDb::unspent_coins(asset_id) (crates/dig-wallet/src/sage/db.rs)
The gap is only pagination. An address with thousands of coins currently returns everything in one
response.
The shape — reuse the repo's own Sage-parity idiom, do not invent one
crates/dig-wallet/src/sage/rpc.rs:358-366 already implements this correctly for coinsByParent.
Match it:
- ASCENDING by
coin_id — a stable total order, so a page boundary is well-defined.
- Cursor, not offset. Return the last
coin_id of the page; the caller resumes from it. An offset
re-reads a shifting table and silently skips or repeats rows when a coin is spent between pages —
and on a money surface a skipped coin reads as missing funds.
has_more derived from rows remaining BEYOND the page, never from whether the page filled. The
repo's own comment says why: the two differ exactly when the count is a multiple of the page size.
- An empty page is an answer, not an error (
rpc.rs:1853).
- A short page is never marked complete to salvage a partial read (
rpc.rs:1859).
Note the existing query's scope, and decide deliberately
unspent_coins(asset_id) filters by asset_id only — it does not filter by puzzle_hash. Check
what the RPC layer does with that before adding a page on top: if the address filter is applied above
the query, paginating the unfiltered query and then filtering would produce short pages and a wrong
has_more. Filter and paginate at the same level, in SQL, or the two disagree.
Also
- Keep
--json machine output (§6.2) and make the cursor/has_more fields part of it — a scripted
caller must be able to page without parsing prose.
control.wallet.coins is a published contract in dig-node-control-interface; adding optional
paging params is additive, but the catalog must be updated in the same family (§4.1 release-first).
State the SemVer judgement.
- Update
SPEC.md (§4.2) and the CLI parity test (#426 guards drift between the RPC and the CLI).
- §2.4b: bring
dig-*/chia-* deps to latest in the PR; index checks need a User-Agent header.
Acceptance
dign wallet coins <address> --asset dig pages through a real funded address, --json carries the
cursor, and the last page reports has_more: false without a spurious extra request. Evidence is a
real run (§2.6).
Task
Paginate the unspent-coin read:
control.wallet.coinsand thedign wallet coinsCLI that fronts it.Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3170
User directive, verbatim: "the dig-node cli also needs a command in its wallet to list available
coins, paginated"
What already exists — do NOT rebuild it
control.wallet.coins— shipped, per-address, unspent-only (crates/dig-node-service/src/control.rs:148)dign wallet coins <ADDRESS> --asset xch|dig [--json]— shipped, unpaginatedcoinstable withidx_coins_unspent … WHERE spent_height IS NULL, andWalletDb::unspent_coins(asset_id)(crates/dig-wallet/src/sage/db.rs)The gap is only pagination. An address with thousands of coins currently returns everything in one
response.
The shape — reuse the repo's own Sage-parity idiom, do not invent one
crates/dig-wallet/src/sage/rpc.rs:358-366already implements this correctly forcoinsByParent.Match it:
coin_id— a stable total order, so a page boundary is well-defined.coin_idof the page; the caller resumes from it. An offsetre-reads a shifting table and silently skips or repeats rows when a coin is spent between pages —
and on a money surface a skipped coin reads as missing funds.
has_morederived from rows remaining BEYOND the page, never from whether the page filled. Therepo's own comment says why: the two differ exactly when the count is a multiple of the page size.
rpc.rs:1853).rpc.rs:1859).Note the existing query's scope, and decide deliberately
unspent_coins(asset_id)filters byasset_idonly — it does not filter bypuzzle_hash. Checkwhat the RPC layer does with that before adding a page on top: if the address filter is applied above
the query, paginating the unfiltered query and then filtering would produce short pages and a wrong
has_more. Filter and paginate at the same level, in SQL, or the two disagree.Also
--jsonmachine output (§6.2) and make the cursor/has_morefields part of it — a scriptedcaller must be able to page without parsing prose.
control.wallet.coinsis a published contract indig-node-control-interface; adding optionalpaging params is additive, but the catalog must be updated in the same family (§4.1 release-first).
State the SemVer judgement.
SPEC.md(§4.2) and the CLI parity test (#426 guards drift between the RPC and the CLI).dig-*/chia-*deps to latest in the PR; index checks need aUser-Agentheader.Acceptance
dign wallet coins <address> --asset digpages through a real funded address,--jsoncarries thecursor, and the last page reports
has_more: falsewithout a spurious extra request. Evidence is areal run (§2.6).