Skip to content

wallet-sdk PR5c: spark send + receive ops#1125

Closed
orveth wants to merge 1 commit into
sdk/pr5b-cashu-opsfrom
sdk/pr5c-spark-ops
Closed

wallet-sdk PR5c: spark send + receive ops#1125
orveth wants to merge 1 commit into
sdk/pr5b-cashu-opsfrom
sdk/pr5c-spark-ops

Conversation

@orveth

@orveth orveth commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Sub-slice 3c of the @agicash/wallet-sdk extraction — 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 in Sdk.create.

Implemented

  • SparkSendOpscreateLightningQuote({ 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).
  • SparkReceiveOpscreateLightningQuote({ account, amount, description?, purpose? }) (BUY_CASHAPP'Pay to Agicash'), get(id).
  • Spark send/receive services + repos + receive-core, lifted framework-free from master (send/spark-send-quote-{service,repository}.ts, receive/spark-receive-quote-{service,repository,core}.ts): the idempotent primitives — getLightningSendQuote / createSendQuote / initiateSend (Breez prepareSendPayment+sendPayment, idempotencyKey: quote.id the keystone, already-paid / insufficient-balance Breez errors → DomainError) / complete / fail; receive getLightningQuote (Breez receivePayment) / createReceiveQuote / complete / expire / fail / markMeltInitiated.
  • Spark balance source (SparkBalanceTracker, from 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.
  • listPending kept processor-internal (getUnresolved / getPending on the repos) — not on the public surface.

Re-housing notes

  • Single-source schemas re-exported (lib-spark-quotes.ts) so types/spark.ts can never drift from master's zod.
  • ~/lib/bolt11./lib-scan; measureOperation telemetry dropped (§3).
  • lib-spark.ts re-exports the specific app/lib/spark/errors module, NOT the lib/spark barrel — the barrel pulls ./wasm, whose top-level import @agicash/breez-sdk-spark loads native WASM at module-eval (same discipline lib-cashu-quotes uses for the mint-WS managers). Keeps WASM off the unit-test path.

Deferred — executeQuote (placement decision)

spark.send.executeQuote is a documented NotImplementedError stub — the same placement PR5b chose for cashu.send.executeQuote, deliberately mirrored:

  • The unified executeQuote orchestrator (the build plan's single biggest net-new construct) absorbs all six master useProcess*Tasks hooks — including useProcessSparkSendQuoteTasks — into one framework-free state machine, kept as its own reviewable unit (build plan §1 + §4 + risk callout).
  • Spark's terminal transition (UNPAID→PENDING→COMPLETED/FAILED) is driven by the Breez paymentSucceeded/paymentFailed event callback — the same listener substrate the orchestrator owns (and that emits send:completed/send:failed). Building only spark's drive loop here would duplicate that substrate and split the orchestrator across two PRs.
  • PR5c therefore ships the idempotent primitives the orchestrator sequences (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_CASHAPP description/purpose, executeQuote deferred stub.
  • spark-send-quote-service.test.ts — the Breez-driven send: initiateSend idempotency-key + Lightning-routing + mark-pending, fee-changed rejection, already-paid / insufficient-balance error mapping; complete/fail state guards.
  • spark-receive-quote-service.test.tscreateReceiveQuote (LIGHTNING zero-fee + CASHU_TOKEN melt-data/fee-sum/earlier-expiry) + state guards.
  • spark-balance-tracker.test.ts — balance source + account:updated emission, 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) on packages/wallet-sdk — clean.
  • root bun --filter='*' run test@agicash/wallet-sdk 237 pass / 0 fail, web-wallet 133 pass / 0 fail (both exit 0).
  • root bun --filter='*' run typecheck — web-wallet, web-wallet-e2e, @agicash/wallet-sdk all exit 0.

🤖 Generated with Claude Code

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>
@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agicash Error Error Jun 3, 2026 11:27pm
agicash-root-dir-test Ready Ready Preview, Comment Jun 3, 2026 11:27pm

Request Review

@supabase

supabase Bot commented Jun 3, 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 ↗︎.

@orveth

orveth commented Jun 5, 2026

Copy link
Copy Markdown
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.

@orveth orveth closed this Jun 5, 2026
@pmilic021 pmilic021 deleted the sdk/pr5c-spark-ops branch June 22, 2026 09:43
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