Skip to content

feat(nipcash): bearer slice lifecycle — check, rekey, multi-source transfer - #22

Merged
naliyi merged 11 commits into
mainfrom
feat/nipcash-bearer-slice-lifecycle
Sep 16, 2026
Merged

naliyi merged 11 commits into
mainfrom
feat/nipcash-bearer-slice-lifecycle

Conversation

@naliyi

@naliyi naliyi commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Brings main up to date with develop, including the already-committed 85b129c (ResolvedConnectionKey + connection_key consolidate sources), plus new work building on top of it:
  • cash_consolidate now accepts a bearer or connection_key To target, not just pubkey (ErrConsolidateTargetNotPubkeyErrConsolidateTargetInvalid); IAPubkey now passes through for connection_key targets.
  • nipcash.SplitBearerSliceString splits the combined "<token>#<bearer_secret>" presentation back apart.
  • nipcash.CheckClaim (nipcash/client.CheckClaim) confirms a token has a real, unclaimed recipient before a caller acts on it.
  • nipcash/client.RekeyBearerSlice moves a bearer-mode slice out of shared custody under a fresh secret, optionally consolidating with other same-issuer sources.
  • nipcash/client.TransferFromSources transfers an amount drawn from one or more sources, auto-consolidating first (and reconnecting to the newly-consolidated wallet, fixing a NOT_FOUND failure the naive approach hits).
  • nipcash/client.PartialProgressError reports partial progress if the interim call of either composite above lands but the following one fails.
  • README walkthrough for the bearer-mode mint → verify → rekey flow.

Split into 9 commits, one concern each (see commit log) — infra (transferConsolidater/PartialProgressError) lands before its two consumers, cash_consolidate's target-acceptance change lands before RekeyBearerSlice depends on it, and CHANGELOG.md/README.md entries land with (or right after) the piece they document.

This is independent of #21 (fix(nip47)), which is a separate, unrelated wire-compat fix split into its own PR.

Test plan

  • go build ./...
  • go vet ./...
  • go test -count=1 ./... (full suite, all packages green)

🤖 Generated with Claude Code

naliyi and others added 11 commits September 15, 2026 08:22
…ion_key sources

ResolvedConnectionKey builds a connection_key Recipient/Target directly from
a nipIC.ConnectionKey the caller already has (e.g. decoded from an
nconnection1... string via nipIC.DecodeNConnection), without needing the raw
external ID ConnectionKey itself hashes internally.

cash_consolidate's wire shape (consolidateSourceParam) had no
attestation_event field at all, so a connection_key source's proof had
nowhere to travel — added the field and wired Request() to populate it
instead of discarding buildProof's attestation return value. Sources may now
be pubkey or connection_key; bearer sources remain rejected (no signature,
no binding to the request carrying it).
cash_consolidate's new_identity target was pubkey-only client-side, even
though this NIP-CASH revision's server side already accepts bearer and
connection_key targets too. ErrConsolidateTargetNotPubkey renamed to
ErrConsolidateTargetInvalid: the only thing rejected client-side now is a
nil To, not a real Target's own type. Also passes IAPubkey through for a
connection_key target, previously dropped entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sentation

Splits a bearer slice's optional combined "<token>#<bearer_secret>"
display-layer presentation back into its two parts, so a caller
presenting the two together in one string has a symmetric way to pull
them back apart on the receiving end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves the three-way NewWalletToken ambiguity on a transfer result
(in-place reassignment vs. spin-off vs. partial split) into the one
answer a caller actually needs: the token the recipient should use.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RecipientStatus.IsBearer keeps identityTypeBearer's own comparison inside
the package. MatchClaim/MatchClaimAuto are the pure matching rule
CheckClaim (nipcash/client) builds on: only ever considers an unclaimed
recipient a match, bearer matches any unclaimed bearer entry, non-bearer
matches only an unclaimed entry whose IdentityValue equals the caller's
own pubkey exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…fore acting on it

Confirms a matching, unclaimed recipient actually exists on the Hub for a
token, live via ListRecipients + MatchClaimAuto, rather than trusting a
token's own identity_required TLV (a stale-prone hint, not a live
guarantee). A method, not a self-dialing package function: several
callers already hold a live, connected *Client for the same wallet by the
time they need this check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rogressError

transferConsolidater is the narrow slice of *Client that
RekeyBearerSlice/TransferFromSources actually call, factored out so their
own call-sequencing logic is unit-testable against a hand-built fake,
without a live Cash Hub. PartialProgressError is returned by both when
their first wire call lands for real but their second then fails --
exactly one of Transferred/Consolidated is set, whichever call type
actually succeeded, so the caller can update its own ledger from
whichever landed without an extra round trip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nder a fresh secret

Moves a bearer-mode slice out of shared custody: the current secret is
presented once, consumed, and replaced with a fresh secret only the
caller knows. Optionally merges the slice with other same-issuer sources
into a freshly-minted bearer wallet -- requires an interim reassignment
onto a pubkey identity first, since cash_consolidate never accepts a
bearer-identified source. nipcash.IsPubkeyTarget validates that interim
identity client-side, before any wire call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rawn from multiple sources

Transfers Amount to To, drawing from one or more Sources. A single
source transfers (or splits) directly; more than one consolidates first
into InterimIdentity, then reconnects to that newly-consolidated wallet
specifically before transferring onward -- cash_consolidate always spins
off a genuinely new wallet pubkey, but CashTransfer's own proof-building
binds to the Client's original dial-target pubkey, so acting through the
original client on the new wallet would bind the proof to the wrong
pubkey entirely (confirmed live: NOT_FOUND). Reconnecting first avoids
that failure mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Walks through minting a bearer slice, handing over the combined
"<token>#<bearer_secret>" string, and the recipient's side: split it back
apart, CheckClaim to confirm it's real before trusting it, then
RekeyBearerSlice to move it out of shared custody under a fresh secret
only the new holder knows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@naliyi
naliyi merged commit 30285d3 into main Sep 16, 2026
3 checks passed
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