Skip to content

feat(wallet-sdk): receive cashu token slice (step 12) - #1185

Merged
jbojcic1 merged 1 commit into
masterfrom
sdk/receive-cashu-token-slice
Sep 23, 2026
Merged

jbojcic1 merged 1 commit into
masterfrom
sdk/receive-cashu-token-slice

Conversation

@ditto-agent

Copy link
Copy Markdown
Contributor

Step 12 of the no-cache wallet-SDK extraction (spec: docs/superpowers/specs/2026-06-24-wallet-sdk-no-cache-production-design.md). Implements the receive.cashuToken sub-namespace and flips the web cross-account token-quote hook and the protected token route's auto-claim off @agicash/wallet-sdk/temporary.

Plan (full spec, incl. the applied plan-attack corrections): docs/superpowers/plans/2026-09-21-wallet-sdk-receive-cashu-token-slice.md.

What changed

  • Contract (domain/sdk/receive.ts): the step-4 placeholder member cashuToken.getQuote is renamed createQuotes — it persists (RPC create_cashu_receive_quote / create_spark_receive_quote), and the contract conventions say get* = stateless preview, create* = persists. Plural because it persists a receive quote plus its melt quote (CrossAccountReceiveQuotesResult). New types: CreateReceiveCashuTokenQuotesParams, ReceiveCashuTokenQuotes, ClaimCashuTokenParams, ClaimCashuTokenResult (= the service's ClaimTokenResult, so the route applies cache upserts with no extra reads).
  • sdk.receive.cashuToken.createQuotes({ token, sourceAccount, destinationAccount, exchangeRate }) wraps ReceiveCashuTokenQuoteService.createCrossAccountReceiveQuotes; sdk.receive.cashuToken.claim({ token, claimTo, accounts, user }) wraps ClaimCashuTokenService.claimToken. Both follow the step-9/10/11 fence template (requireUserId → capture sessionSignal → await builder → re-check → call with { abortSignal } → re-check). Caller-supplied domain objects throughout (feat(wallet-sdk): cashu receive quote slice (step 9) #1176 param precedent); the SDK builds the service graph internally from session keys + the accounts.getRepository bridge — the web route's getServices() is deleted.
  • Services: createCrossAccountReceiveQuotes and claimToken gain an optional options?: { abortSignal? } last param, threaded to the writes that already accept it (in-package callers updated in the same PR).
  • Web: useCreateCrossAccountReceiveQuotes body → sdk.receive.cashuToken.createQuotes (FX still fetched in the hook via TanStack and passed as a string); _protected.receive.cashu_.token.tsx loader → sdk.receive.cashuToken.claim. Hook/route external APIs unchanged; receive-cashu-token.tsx untouched.
  • Canary: pruned exactly ReceiveCashuTokenQuoteService + ClaimCashuTokenService from temporary.ts (zero importers left outside the package). Page-load account discovery, public placeholders, processors and change handlers stay on /temporary (step-18 boundary; no host/processing split).
  • Tests: 11 new receive-api.test.ts cases (a–k), incl. abort-signal identity through the real default token-quote service (j) and a spark-destination cashu-seed non-access test with the real default cashu service (k). Net +10 (the throwing-getter test is deleted).

Foreground parity

Zero added network requests on every runnable path (accounting tables A–E in the plan). Two deltas exist only on paths unreachable with test mints, documented in the plan (decision 3 / table E): on a cross-currency ?claimTo= auto-claim with a warm TanStack FX cache the SDK's uncached exchangeRateService can add one provider GET; and cryptography.getPrivateKey is not memoized SDK-side (only the cross-cashu in-band completeReceive reaches it).

Gates (local, this branch)

Gate Result
bun run fix:all exit 0, no fixes
bun run typecheck exit 0 (9/9 workspaces)
packages/wallet-sdk bun test 206 pass, 0 fail
apps/web-wallet bun test 38 pass, 0 fail

Smoke (local stack, chrome-devtools, isolated context)

21-sat testnut token → public page shows ₿21 → Claim as Guest → terms → guest signup → loader auto-claim via sdk.receive.cashuToken.claim → home shows ₿20, transactions list shows "+₿20 Received", 0 console errors/warnings. Network for the claim: 1× create_cashu_receive_swap, 1× mint /v1/swap, 1× complete_cashu_receive_swap, zero accounts/users selects inside the claim (table E, same-mint row). The interactive cross-account createQuotes path and claimTo=spark need a non-test mint that can pay Lightning and were not run locally (plan "Paths that need a real Lightning payment").

Process

Built with the step-10/11 delegation pattern (maxplayer contribution jobs): plan (grok-4.6) → two cross-model plan-attacks (grok delivered NOT READY 2C/4I/2M/1N — all verified locally and applied before any code: the createQuotes rename, tests (j)/(k) rewritten to the real harness, FX/getPrivateKey parity caveats, getExchangeRate import prune; the claude-harness attack never delivered) → one whole-slice implementation (grok-4.6; file set = the plan's 8 files exactly) → adversarial review (grok-4.6: READY, 0 Critical/0 Important/0 Minor/2 Nit; reviewer ran all gates green). Nit 2 (JSDoc for the new options param) applied; Nit 1 ((b)/(g) assert the abort signal is defined rather than identical to the session signal) declined for consistency with the sibling namespace tests — identity is locked by (j). The codex-harness reviewer was unavailable; the claude-harness review did not deliver.

For the maintainer

  • The verb rename getQuote → createQuotes on ReceiveApi.cashuToken is the one contract-surface decision in this slice; the contract-proposal doc's representative sketch is left untouched.
  • claim takes the wallet User (needed by getExtendedAccounts); requireUserId() is the session fence and the persisted user id is params.user.id, same implicit trust as master's loader (no runtime equality check added).

🤖 Generated with Claude Code

Implement sdk.receive.cashuToken.{createQuotes, claim} and flip the web
cross-account token quote hook and the protected token route auto-claim
off @agicash/wallet-sdk/temporary. The placeholder contract member
getQuote is renamed createQuotes because it persists (contract
conventions: get* = preview, create* = persists).

Plan + applied plan-attack corrections:
docs/superpowers/plans/2026-09-21-wallet-sdk-receive-cashu-token-slice.md

Gates: fix:all 0, typecheck 0, SDK 206 pass, web 38 pass. Smoke: guest
auto-claim of a testnut token through sdk.receive.cashuToken.claim.

Pre-squash commits: 87d82e5 (plan), e3d2b69 (attack corrections),
7aebc4e (slice), 526f4581 (review nit).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agicash Ready Ready Preview Sep 21, 2026 5:23pm UTC

Request Review

@supabase

supabase Bot commented Sep 21, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project hrebgkfhjpkbxpztqqke because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@jbojcic1
jbojcic1 merged commit 0a91e26 into master Sep 23, 2026
6 checks passed
@jbojcic1
jbojcic1 deleted the sdk/receive-cashu-token-slice branch September 23, 2026 13:30

This branch was successfully deployed

1 active deployment
Preview — 409d7276 Deployed Sep 21, 2026 by vercel[bot]
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.

2 participants