wallet-sdk PR5c: spark send + receive ops#1125
Closed
orveth wants to merge 1 commit into
Closed
Conversation
Implements §6 (Spark) of the no-cache contract — sub-slice 3c, stacked on PR5b.
Replaces PR2's spark stub with real SparkSendOps + SparkReceiveOps, mirroring
PR5b's cashu structure, driving each account's live Breez wallet (PR5a).
Lifted framework-free from master (dropping the React factories + `measureOperation`
telemetry; `~/lib/bolt11`→`./lib-scan`; `~/lib/spark`→the specific `lib/spark/errors`
module so the native WASM barrel is never pulled):
- SparkSendQuoteService + repository (send/spark-send-quote-{service,repository}.ts):
getLightningSendQuote / createSendQuote / initiateSend / complete / fail. initiateSend
drives Breez prepareSendPayment+sendPayment with `idempotencyKey: quote.id` (the
idempotency keystone) and maps already-paid / insufficient-balance Breez errors to
DomainError.
- SparkReceiveQuoteService + repository + core (receive/spark-receive-quote-*.ts):
getLightningQuote (Breez receivePayment) / createReceiveQuote / complete / expire /
fail / markMeltInitiated.
- SparkBalanceTracker (shared/spark.ts#useTrackAndUpdateSparkAccountBalances): registers
Breez event listeners on online spark accounts and emits `account:updated` with
compare-before-emit (the spark domain owns the balance source — Breez getInfo, not a
DB trigger). Wired into Sdk.create + torn down in destroy(); started by S5.
- Single-source runtime schemas re-exported (lib-spark-quotes.ts) so types/spark.ts can
never drift from master's zod schemas.
Public surface: spark.send.createLightningQuote / failQuote / get;
spark.receive.createLightningQuote / get (ln-address resolved internally via LNURL-pay,
matching cashu; listPending stays processor-internal).
executeQuote DEFERRED to the PR5d orchestrator sub-slice (NotImplementedError stub), the
SAME placement PR5b chose for cashu.send.executeQuote: the unified executeQuote orchestrator
absorbs all six master useProcess*Tasks hooks (incl. useProcessSparkSendQuoteTasks) into one
framework-free state machine kept as its own reviewable unit (build plan §1/§4). Spark's
terminal UNPAID→PENDING→COMPLETED/FAILED transition is driven by the Breez paymentSucceeded/
paymentFailed callback — the same listener substrate the orchestrator owns — so building only
spark's drive loop here would duplicate it and split the orchestrator across PRs. PR5c ships
the idempotent primitives it sequences.
Tests (Breez SDK mocked — no WASM/network): spark quote creation + ln-address fold + executeQuote
stub (domains/spark.test.ts); the Breez-driven send (initiateSend idempotency-key + fee-change +
error mapping) + state guards (spark-send-quote-service.test.ts); receive create + state guards
(spark-receive-quote-service.test.ts); the balance source + account:updated compare-before-emit
(spark-balance-tracker.test.ts).
Full CI gate green locally: biome format+lint, root `bun --filter='*' run test` (wallet-sdk
237 pass / web-wallet 133 pass), root typecheck (all packages).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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. |
Contributor
Author
|
Superseded by the reactive design-B stack (SDK #1129-#1138 + lib-extract #1140-#1143). This no-cache stack was the reference substrate the reactive PRs were built from (~90% reused, reads adapted to Query); it was never reviewed/merged, so nothing is lost. Closing as superseded — branch retained for reference. |
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.
Sub-slice 3c of the
@agicash/wallet-sdkextraction — spark send + receive ops — stacked on PR5b (#1124,sdk/pr5b-cashu-ops). Implements §6 (Spark) of the no-cache contract, mirroring PR5b's cashu structure and driving each account's live Breez wallet (PR5a). Replaces PR2's spark stub, wired inSdk.create.Implemented
SparkSendOps—createLightningQuote({ account, destination, amount? })(bolt11 or ln-address; ln-address resolved internally via LNURL-pay using the amount, matching cashu's §3 fold),failQuote,get(id).SparkReceiveOps—createLightningQuote({ account, amount, description?, purpose? })(BUY_CASHAPP→'Pay to Agicash'),get(id).send/spark-send-quote-{service,repository}.ts,receive/spark-receive-quote-{service,repository,core}.ts): the idempotent primitives —getLightningSendQuote/createSendQuote/initiateSend(BreezprepareSendPayment+sendPayment,idempotencyKey: quote.idthe keystone, already-paid / insufficient-balance Breez errors →DomainError) /complete/fail; receivegetLightningQuote(BreezreceivePayment) /createReceiveQuote/complete/expire/fail/markMeltInitiated.SparkBalanceTracker, fromshared/spark.ts#useTrackAndUpdateSparkAccountBalances): registers Breez event listeners on online spark accounts and emitsaccount:updatedwith compare-before-emit — the spark domain owns the balance source (BreezgetInfo, not a DB trigger). Wired intoSdk.create+ torn down indestroy(); started by S5.listPendingkept processor-internal (getUnresolved/getPendingon the repos) — not on the public surface.Re-housing notes
lib-spark-quotes.ts) sotypes/spark.tscan never drift from master's zod.~/lib/bolt11→./lib-scan;measureOperationtelemetry dropped (§3).lib-spark.tsre-exports the specificapp/lib/spark/errorsmodule, NOT thelib/sparkbarrel — the barrel pulls./wasm, whose top-levelimport @agicash/breez-sdk-sparkloads native WASM at module-eval (same disciplinelib-cashu-quotesuses for the mint-WS managers). Keeps WASM off the unit-test path.Deferred —
executeQuote(placement decision)spark.send.executeQuoteis a documentedNotImplementedErrorstub — the same placement PR5b chose forcashu.send.executeQuote, deliberately mirrored:executeQuoteorchestrator (the build plan's single biggest net-new construct) absorbs all six masteruseProcess*Taskshooks — includinguseProcessSparkSendQuoteTasks— into one framework-free state machine, kept as its own reviewable unit (build plan §1 + §4 + risk callout).paymentSucceeded/paymentFailedevent callback — the same listener substrate the orchestrator owns (and that emitssend:completed/send:failed). Building only spark's drive loop here would duplicate that substrate and split the orchestrator across two PRs.initiateSend/complete/fail); the drive loop lands in PR5d.Tests (Breez SDK mocked — never loads WASM / requires network)
domains/spark.test.ts— quote creation (bolt11 passthrough + ln-address LNURL fold + LNURL-error→DomainError),BUY_CASHAPPdescription/purpose,executeQuotedeferred stub.spark-send-quote-service.test.ts— the Breez-driven send:initiateSendidempotency-key + Lightning-routing + mark-pending, fee-changed rejection, already-paid / insufficient-balance error mapping;complete/failstate guards.spark-receive-quote-service.test.ts—createReceiveQuote(LIGHTNING zero-fee + CASHU_TOKEN melt-data/fee-sum/earlier-expiry) + state guards.spark-balance-tracker.test.ts— balance source +account:updatedemission, compare-before-emit (unchanged balance not forwarded), irrelevant events ignored, single-listener-per-account,stop()/ untrack cleanup.Full CI gate — green locally
biome format --write+biome lint+biome format(check) onpackages/wallet-sdk— clean.bun --filter='*' run test—@agicash/wallet-sdk237 pass / 0 fail,web-wallet133 pass / 0 fail (both exit 0).bun --filter='*' run typecheck— web-wallet, web-wallet-e2e, @agicash/wallet-sdk all exit 0.🤖 Generated with Claude Code