feat(wallet-sdk): cashu receive quote slice (step 9) - #1176
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
jbojcic1
reviewed
Aug 14, 2026
ditto-agent
added a commit
that referenced
this pull request
Aug 14, 2026
Review follow-up (#1176 r3784641517): getLightningQuote and createQuote took an accountId and fetched the account per call — a Supabase read of the account plus all unspent proofs, proof decryption, and a fresh wallet init (three mint HTTP requests), paid twice per receive flow. The caller already holds the account, so the params now take CashuAccount directly and the internal getCashuAccount lookup is gone. The not-found and non-cashu runtime guards became compile-time; fetch-by-id remains only for dark paths that work off quote.accountId. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ditto-agent
added a commit
that referenced
this pull request
Aug 14, 2026
The step-9 review (#1176 r3784641517) caught host-facing receive methods taking an accountId and re-fetching the account per call, against the no-cache design's premise that only background/orchestrator work gains DB reads. The premise was implicit; nothing in the contract conventions constrained param shapes, so the slice plan drifted. This makes it binding: a new convention bullet in the contract proposal and a foreground-parity corollary in the production design, both stating that host-initiated methods take caller-held domain objects and that fetch-by-id is reserved for background work and server routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ditto-agent
added a commit
that referenced
this pull request
Aug 14, 2026
…sing line From the #1176 review discussion: createReceiveApi threads the accounts bridge only to satisfy repository constructor deps used by processPayment/ completeReceive — verbs the host surface can never reach, background-only per the contract conventions. The split is deferred to step 18, when the background domain (the sole consumer of the processing verbs) lands and its grounding fixes the exact shape; slices 10-15 keep wrapping the bundled classes until then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wraps the cashu receive quote domain in the sdk.receive.cashu contract
namespace and flips web quote creation and tracking off
@agicash/wallet-sdk/temporary.
- createReceiveApi returns the full ReceiveApi: cashu implemented
(getLightningQuote/createQuote/getQuote), spark/cashuToken as throwing
getters for steps 11/12; session-fenced per the accounts pattern
(requireUserId + sessionSignal pre/post checks); first SDK-side
CashuCryptography assembly from session keys.
- Params take the caller-held CashuAccount (review r3784641517): the SDK
does no per-call account fetch (was a proofs-inclusive read + wallet
init, twice per flow); the not-found/non-cashu runtime guards became
compile-time. Codified as a contract convention (proposal 'Conventions
across all namespaces') with a foreground-parity corollary in the
production design; the step-18 bullet gains the host/processing
repo+service split from the same review discussion.
- CashuReceiveQuoteService.createReceiveQuote gains optional
{ abortSignal }, threaded from the api; in-package callers unchanged.
- receiveType is pinned 'LIGHTNING' in the api; CASHU_TOKEN quotes stay
in-package (steps 12/16/17).
- Web flip: useCreateCashuReceiveQuote and useTrackCashuReceiveQuote on
sdk.receive.cashu.*; the background processor, change handlers, and
pending reads stay on /temporary until step 18.
- temporary.ts sheds CashuReceiveQuoteSchema, computeTotalFee, and
deriveNut20LockingPublicKey re-exports.
- receive-api test suite: session fences (pre/mid/post), crypto parity
(locking key derives from the session cashu locking xpub), contract
passthrough.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ditto-agent
force-pushed
the
sdk/cashu-receive-quote-slice
branch
from
August 14, 2026 15:31
a6b8a9b to
5004d67
Compare
jbojcic1
approved these changes
Aug 14, 2026
This was referenced Aug 18, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 9 of the 19-step no-cache wallet-SDK extraction (spec:
docs/superpowers/specs/2026-06-24-wallet-sdk-no-cache-production-design.md; plan committed in this PR:docs/superpowers/plans/2026-08-13-wallet-sdk-cashu-receive-quote-slice.md). This wiressdk.receive.cashu(getLightningQuote/createQuote/getQuote) and flips web cashu-receive-quote creation and tracking from@agicash/wallet-sdk/temporarytosdk.receive.cashu.*. The web background processor (useProcessCashuReceiveQuoteTasks) keeps completing, expiring, failing, and melt-initiating quotes through/temporaryuntil step 18 — the money-path boundary stays intact.What changed
SDK
domain/sdk/receive.ts— fillsGetCashuReceiveLightningQuoteParams(account: CashuAccount,amount: Money,description?) andCreateCashuReceiveQuoteParams(account: CashuAccount,lightningQuote,purpose?,transferId?), replacing the two step-9unknownplaceholders. Spark/cashuToken placeholders stay. (Initially shipped asaccountIdwith an internal fetch; flipped to the full account inf30a3315after review — see r3784641517.)domain/receive/receive-api.ts—createReceiveApi(deps)returns the fullReceiveApiwithcashuimplemented and session fences on the accounts template (capturesessionSignal(), await repository/service deps, re-check, threadabortSignalwhere the layer accepts it, re-check).spark/cashuTokenremain throwing getters (NotImplementedError('receive.spark')/('receive.cashuToken')) so steps 11/12 can fill them without reshapingsdk.ts.domain/receive/cashu-receive-quote-service.ts—createReceiveQuotegains an optional second paramoptions?: { abortSignal?: AbortSignal }, threaded to bothrepository.createcalls. Backward-compatible; in-package callers are unchanged.domain/sdk/sdk.ts— the throwingget receive()getter is replaced withreadonly receivewired viacreateReceiveApi({ db, getSession: getLiveSession, keys, getAccountRepository: accounts.getRepository }).Web
apps/web-wallet/app/features/receive/cashu-receive-quote-hooks.ts—useCreateCashuReceiveQuotenow callssdk.receive.cashu.getLightningQuote+createQuote;useTrackCashuReceiveQuotenow callssdk.receive.cashu.getQuote. The hooks' external APIs are unchanged, so callers are untouched. Caches, change handlers,usePendingCashuReceiveQuotes,useProcessCashuReceiveQuoteTasks, and the repository/service hook exports stay on/temporary.Canary
packages/wallet-sdk/temporary.ts— two dead cashu-receive-quote re-exports pruned:CashuReceiveQuoteSchemaand{ computeTotalFee, deriveNut20LockingPublicKey }fromcashu-receive-quote-core.AgicashDbCashuReceiveQuote,getInitializedCashuWallet,CashuReceiveQuoteRepository, andCashuReceiveQuoteServicestay (live consumers).Tests
domain/receive/receive-api.test.ts—createQuotethrowsNoSessionErrorbefore any repository work; happy path passes{ userId, account, receiveType: 'LIGHTNING', lightningQuote, purpose, transferId }and{ abortSignal }to the service; mid-construction abort yieldsSessionEndedErrorwith the service never called (both methods);getLightningQuotecalls the service with the given account's wallet;getQuotereturns the repository result includingnulland threads the abort signal, and rejects withSessionEndedErrorwhen the signal aborts; accessingreceive.spark/receive.cashuTokenthrowsNotImplementedError. Wallet-sdk suite 174 green.Design decisions
receiveTypepinned to'LIGHTNING'inside the API.CASHU_TOKENquotes are created only by in-package flows (steps 12/16/17:receive-cashu-token-quote-service,transfer-service,lightning-address-service), never by the host.getLightningQuoteandcreateQuotetake the fullCashuAccountin params (f30a3315, review r3784641517). The caller already holds the account, so the SDK does no per-call account fetch (which cost a Supabase read of the account plus all unspent proofs, proof decryption, and a fresh wallet init of three mint HTTP requests — twice per receive flow). The cashu-only constraint is compile-time;CashuAccountis structurally assignable toRedactedCashuAccountand passes tocreateReceiveQuote;account.walletfeedsgetLightningQuote. Fetch-by-id remains the shape only for dark/background paths that work offquote.accountId.getAccountRepositorystays a dep — it feeds the quote repository constructor.CashuCryptographyassembled from session keys (first SDK-side assembly; the web builds its own from TanStack caches):getSeed/getXpubgo throughkeys.getCashuSeed().getPrivateKeyis a direct Open Secret read (getCashuPrivateKey) and is unfenced; it is only reachable throughcompleteReceive, which no step-9 contract method calls — the processor path stays web-side until step 18.cashu-receive-quote.created/.updatedstay type-only until the step-18 realtime feed (contacts + transactions precedent).events.tsis untouched.getLightningQuote+createQuote, each fetching the account fresh (DB read + wallet init) instead of using the web's cached wallet. This is the no-cache design working as intended; flipped account reads have behaved this way since step 6.transaction-additional-details.tsx(getByTransactionIdis not on the contract);transfer-service-hooks.ts(step 16);receive-cashu-token-hooks.ts+_protected.receive.cashu_.token.tsx(step 12);spark-receive-quote-hooks.ts(step 11);cashu-receive-quote-*.server.ts(step 17).Verification
bun run fix:allexit 0;bun run typecheckexit 0 (all packages).sdk.receive.cashu.*and completed end-to-end through the untouched web processor (/temporary); zero console errors.Delegation note
Implementation, tests, web flip, the
temporary.tsprune, and this description were produced as paid maxplayer marketplace jobs and integrated + verified locally by the orchestrating agent.Adversarial review findings and dispositions
A marketplace adversarial review of the integrated diff returned NOT READY with 0 Critical, 2 Important, and 2 Minor findings. Dispositions:
createQuotedoes not bind thelightningQuotepreview to the account that produced it. Kept as-is, documented for the maintainer. The pre-slice service had the same two-step shape with no origin check, and no current caller can mix accounts (the web mutation threads one account through both calls). A proper fix reshapes the maintainer-endorsed contract (CashuReceiveLightningQuotewould carry its originating account id andcreateQuotewould reject a mismatch) and equally affects the step-11 spark namespace — a contract decision to take once, not a slice-local patch.createQuotere-fetches the account with wallet initialization after the mint quote already exists. RESOLVED inf30a3315(review r3784641517): the params take the fullCashuAccountand the internal account lookup is gone entirely, which removes both the re-initialization failure surface and the latency regression versus the live branch.ca71e695: post-opSessionEndedErrortests forgetLightningQuoteandcreateQuote, plus a signal-identity test across the account lookup and the service write.CashuCryptographyadapter was untested. Fixed inca71e695: a default-service-path test proves the mint quote's locking key derives from the same base xpub asSessionKeys.getCashuLockingXpub.An earlier review attempt was delivered blocked: the seller's harness had no shell or network, so it could not clone the repo; it reported that honestly instead of fabricating a verdict, and the review was re-run as a contribution-mode job (the seller forks the repo and reads it directly).
🤖 Generated with Claude Code