Add Bitcoin Lightning sends via Breez SDK - Spark#6017
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
0009013 to
995da75
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 995da75. Configure here.
|
|
||
| // One live SDK connection per walletId. Connecting is expensive, so we cache the | ||
| // promise (not just the result) to coalesce concurrent callers. | ||
| const connections = new Map<string, Promise<BreezSdkInterface>>() |
There was a problem hiding this comment.
Module-level SDK cache never cleared on logout
Medium Severity
The module-level connections Map caches live Breez SDK connections but is never cleared on logout. The disconnectLightning function exists for this purpose but is never called anywhere in the codebase. The logoutRequest in LoginActions.tsx clears other module-level caches (resetZnsClient, clearZnsLookupCache, resetLocalAccountSettingsCache) but has no call to disconnect or clear Lightning connections. This causes resource leaks and stale connections persisting across user sessions.
Additional Locations (1)
Triggered by project rule: Bugbot Review Rules
Reviewed by Cursor Bugbot for commit 995da75. Configure here.
| setErrorMessage(describeLightningError(error)) | ||
| setStatus('error') | ||
| } | ||
| }) |
There was a problem hiding this comment.
Double-tap can trigger duplicate Lightning payment
High Severity
The handlePay handler lacks a synchronous guard against concurrent execution. It relies on setStatus('sending') to remove the button, but React state updates are asynchronous — a rapid double-tap can invoke confirmLightningSend twice before the re-render removes the button. For an irreversible real-money Lightning payment, this could result in a duplicate send attempt.
Triggered by project rule: Bugbot Review Rules
Reviewed by Cursor Bugbot for commit 995da75. Configure here.
995da75 to
c6b03b7
Compare
| if [ -d "$breez_native_pkg" ] && \ | ||
| [ ! -d "$breez_native_pkg/build/RnBreezSdkSpark.xcframework" ]; then | ||
| echo "Fetching @breeztech/breez-sdk-spark-react-native prebuilt artifacts..." | ||
| ( cd "$breez_native_pkg" && sh scripts/postinstall.sh ) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
The new prepare step directly executes node_modules/@breeztech/breez-sdk-spark-react-native/scripts/postinstall.sh, which bypasses the newly added global ignore-scripts=true install safeguard and reintroduces third-party install-time shell execution.
Impact: A compromised dependency release or artifact-fetch path could run arbitrary commands during CI/local prepare, potentially exposing build secrets or tampering release artifacts.
Reviewed by Cursor Security Reviewer for commit c6b03b7. Configure here.
- Replace yarn.lock with package-lock.json (generated via socket npm install for Socket.dev scanning). - Swap package.json `resolutions` for npm `overrides`, drop `yarn` and `yarn-deduplicate` devDeps, bump `patch-package` to ^8, set `packageManager` to npm@11.15.0, and update the `fix` script to use `npm dedupe`. - Persist prior yarn `--ignore-scripts` behavior via `ignore-scripts=true` in .npmrc, and add `legacy-peer-deps=true` so npm tolerates the same peer-dep conflicts yarn classic did (e.g. async-storage@1.19.4 vs RN 0.79). - Convert yarn invocations to npm equivalents in scripts/prepare.sh, Jenkinsfile, .travis.yml, and developer docs (README.md, AGENTS.md, docs/MAESTRO.md, webpack.config.js, scripts/gitVersionFile.ts). - Remove the yarn global-install step from maestro.sh and rename the bootstrap function accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Integrate `@breeztech/breez-sdk-spark-react-native` so a user who pastes or scans a BOLT11 invoice into the send flow of a bitcoin wallet is routed to a Lightning payment instead of failing as an invalid on-chain address. - Add `src/util/breez/breezLightningSend.ts`: a self-contained service that connects the Spark SDK per-wallet (mnemonic sourced from the host bitcoin wallet via `account.getDisplayPrivateKey`, apiKey from env, per-wallet storage dir under RNFS), and wraps prepare/send/disconnect plus BOLT11 detection and friendly error mapping. Uses the SDK's shipped TypeScript types directly (no ambient declaration needed). Error mapping extracts the uniffi error's structured `inner` detail and distinguishes the Spark "Tree service / leaf-headroom" failure (which looks like an "insufficient balance" error but is actually a temporary leaf-arrangement issue on a freshly-claimed deposit) from real insufficient-balance errors. - Add `src/components/modals/LightningSendModal.tsx`: confirmation UI that prepares the payment (surfacing the amount) and sends on confirm, with graceful error states; reports the paid amount + fee on success. - Wire detection into `AddressTile2`: an additive early branch opens the modal for bitcoin wallets when the input is a Lightning invoice; the existing on-chain path is untouched otherwise. The `lightning:` URI scheme prefix is optional — raw `lnbc…` paste works. - Read `BREEZ_API_KEY` from the gitignored `env.json` via the existing `asEnvConfig` cleaner (no key value is committed). - Add localized strings for the Lightning send modal; add the iOS pod (`Podfile.lock`). - `scripts/prepare.sh`: fetch the breez SDK's prebuilt Rust artifacts (xcframework + Android jniLibs). The repo's `.npmrc ignore-scripts=true` (from the npm migration) otherwise suppresses the package's postinstall, so a fresh `npm ci` would leave the native binary missing and the iOS link would fail with undefined `_ffi_breez_sdk_spark_*` symbols. Self-custodial caveat: the Spark wallet manages its own balance/liquidity (it is not the Edge bitcoin engine's UTXO set), so a real payment requires the Spark wallet to be funded first. Funding/receive flows are out of scope for this PR (send path only). Verified end-to-end on iOS: a real 50-sat mainnet Lightning payment between two controlled wallets settled. Note: funding a Spark wallet on-chain depends on the Taproot send fix in edge-currency-plugins (separate dep PR) — Edge's BTC engine otherwise can't send to the `bc1p…` Spark deposit address. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c6b03b7 to
4c0cedc
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|


CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
@breeztech/breez-sdk-spark-react-native@^0.14.0(+ transitiveuniffi-bindgen-react-native);package-lock.json+ iOSPodfile.lockupdated. Pinned to 0.14.0 — the workspace registry time-cap excludes 0.15.0.scripts/prepare.shnow fetches the breez SDK's prebuilt Rust artifacts. The repo's.npmrc ignore-scripts=true(from the npm migration) otherwise suppresses the package's postinstall, so a freshnpm ciwould leave the native binary missing and the iOS link would fail with undefined_ffi_breez_sdk_spark_*symbols. With this hook in place,npm ci+npm run prepare+npm run prepare.iosproduces a buildable tree.edge-currency-plugins' Taproot fix (separate dep PR — branchjon/fix/taproot-initecclib-eager). The Lightning send path here does not need it, but funding a Spark wallet does — Edge's BTC engine otherwise can't send to thebc1p…Spark deposit address (initEccLibwas never called before Taproot address parsing).Requirements
Visual changes (the Lightning send modal):
Summary
Enables Bitcoin Lightning sends. When a user pastes or scans a BOLT11 invoice into the send flow of a
bitcoinwallet, the app routes it to a Lightning payment via the Breez SDK - Spark instead of rejecting it as an invalid on-chain address.src/util/breez/breezLightningSend.ts— self-contained service: per-wallet SDK connect (mnemonic fromaccount.getDisplayPrivateKey,apiKeyfrom env, per-wallet RNFS storage dir), prepare/send/disconnect wrappers, BOLT11 detection, and error mapping (extracts the uniffi error's structured.innerdetail to distinguish real causes — e.g. Spark "Tree service" leaf-headroom errors map to "balance is still settling, try again or send a smaller amount" rather than the misleading "insufficient balance"). Uses the SDK's shipped TypeScript types directly.src/components/modals/LightningSendModal.tsx— confirmation modal: prepares the payment (shows amount), sends on confirm, reports paid amount + fee, handles error states.src/components/tiles/AddressTile2.tsx— additive early branch that opens the modal for bitcoin wallets when the input is a Lightning invoice. The existing on-chain path is unchanged otherwise.src/envConfig.ts— readsBREEZ_API_KEYfrom the gitignoredenv.jsonvia the existingasEnvConfigcleaner. No key value is committed.src/locales/en_US.ts— strings for the modal.scripts/prepare.sh— the postinstall hook described above.UX notes
lnbc…invoice into the send field and the Lightning modal opens automatically. Alightning:prefix is accepted but optional (stripped). This matches the canonical Breez Spark "single unified send field" pattern (sdk.parse(input) → InputType → dispatch); we currently only dispatch the BOLT11 case.Branch base
This branch sits directly on top of
developas two commits: the yarn→npm migration (cherry-picked fromtest-feta, currently still unmerged on develop) and the Lightning feature itself. If the migration lands on develop independently first, drop its commit from this branch on rebase.Verification (real, on iOS)
tsc— passes project-wide (0 errors);eslint— clean on changed files.8c084a20…1f749, confirmed at block 951326 → 4511 sats claimed), then sent a 50-sat BOLT11 invoice between two controlled wallets. Sender Spark 4511 → 4459 (50 + ~2 fee); receiver 0 → 50. Total real spend ≈ $0.35 in fees.initEccLibfix).🤖 Generated with Claude Code
Note
High Risk
Lightning payments use wallet keys and a third-party SDK with native binaries; the npm migration and suppressed postinstall scripts affect every developer and CI install path.
Overview
Bitcoin Lightning sends are added for BTC wallets: users can paste or scan a BOLT11 invoice in the existing send flow instead of getting an invalid on-chain address error. The work wires in Breez SDK – Spark (
@breeztech/breez-sdk-spark-react-native), iOS native pods, aBREEZ_API_KEYenv hook, send UI/modal and service-layer handling (per the feature branch), plus a CHANGELOG entry.The same branch also moves the repo from Yarn to npm: CI (Travis, Jenkins), README / AGENTS.md / Maestro docs, and
maestro.shnow usenpm ci,npm run prepare, and npm scripts;.yarnrcis removed and.npmrcgainsignore-scripts=trueandlegacy-peer-deps=true.scripts/prepare.shexplicitly runs the Breez package postinstall to fetch prebuilt Rust/iOS artifacts, since ignored install scripts would otherwise break native linking.Reviewed by Cursor Bugbot for commit 4c0cedc. Bugbot is set up for automated code reviews on this repo. Configure here.