Skip to content

Add Bitcoin Lightning sends via Breez SDK - Spark#6017

Open
j0ntz wants to merge 2 commits into
developfrom
jon/feat/breez-lightning-send
Open

Add Bitcoin Lightning sends via Breez SDK - Spark#6017
j0ntz wants to merge 2 commits into
developfrom
jon/feat/breez-lightning-send

Conversation

@j0ntz
Copy link
Copy Markdown
Contributor

@j0ntz j0ntz commented May 27, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

  • Adds @breeztech/breez-sdk-spark-react-native@^0.14.0 (+ transitive uniffi-bindgen-react-native); package-lock.json + iOS Podfile.lock updated. Pinned to 0.14.0 — the workspace registry time-cap excludes 0.15.0.
  • scripts/prepare.sh now 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 fresh npm ci would 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.ios produces a buildable tree.
  • Runtime dependency on edge-currency-plugins' Taproot fix (separate dep PR — branch jon/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 the bc1p… Spark deposit address (initEccLib was never called before Taproot address parsing).

Requirements

Visual changes (the Lightning send modal):

  • Tested on iOS device (simulator) — verified end-to-end with a real mainnet send
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Summary

Enables Bitcoin Lightning sends. When a user pastes or scans a BOLT11 invoice into the send flow of a bitcoin wallet, 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 from account.getDisplayPrivateKey, apiKey from env, per-wallet RNFS storage dir), prepare/send/disconnect wrappers, BOLT11 detection, and error mapping (extracts the uniffi error's structured .inner detail 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 — reads BREEZ_API_KEY from the gitignored env.json via the existing asEnvConfig cleaner. No key value is committed.
  • src/locales/en_US.ts — strings for the modal.
  • scripts/prepare.sh — the postinstall hook described above.

UX notes

  • No URI scheme required. Paste a raw lnbc… invoice into the send field and the Lightning modal opens automatically. A lightning: 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.
  • Out of scope for this PR (future canonical expansions): BOLT12 offers, LNURL-pay, Lightning addresses, Spark addresses/invoices, the receive side, and amountless BOLT11 invoices. The SDK supports all of these — they're documented as next-step work.

Branch base

This branch sits directly on top of develop as two commits: the yarn→npm migration (cherry-picked from test-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.
  • End-to-end real Lightning send executed on the simulator: funded a Spark wallet via an on-chain deposit (txid 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.
  • See the LEARNINGS doc on the Asana task for the full story (including two bugs found + fixed along the way: this PR's postinstall hook, and the cross-repo Taproot initEccLib fix).

🤖 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, a BREEZ_API_KEY env 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.sh now use npm ci, npm run prepare, and npm scripts; .yarnrc is removed and .npmrc gains ignore-scripts=true and legacy-peer-deps=true. scripts/prepare.sh explicitly 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.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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>>()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Review Rules

Reviewed by Cursor Bugbot for commit 995da75. Configure here.

setErrorMessage(describeLightningError(error))
setStatus('error')
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Review Rules

Reviewed by Cursor Bugbot for commit 995da75. Configure here.

@j0ntz j0ntz force-pushed the jon/feat/breez-lightning-send branch from 995da75 to c6b03b7 Compare May 28, 2026 02:55
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Comment thread scripts/prepare.sh
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 )
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit c6b03b7. Configure here.

j0ntz and others added 2 commits May 27, 2026 22:09
- 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>
@j0ntz j0ntz force-pushed the jon/feat/breez-lightning-send branch from c6b03b7 to 4c0cedc Compare May 28, 2026 05:09
@socket-security
Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedreact-native-custom-tabs@​0.1.8 ⏵ 0.1.800000
Updatedreact-native-store-review@​0.4.3 ⏵ 0.4.300000
Added@​breeztech/​breez-sdk-spark-react-native@​0.14.07010010098100
Updatedpatch-package@​6.4.7 ⏵ 8.0.197100100 +181100
Updatedreact-native-reanimated@​3.19.1 ⏵ 3.19.597 +110089 +198 +1100

View full report

@socket-security
Copy link
Copy Markdown

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.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Medium
Low adoption: npm @breeztech/breez-sdk-spark-react-native

Location: Package overview

From: package-lock.jsonnpm/@breeztech/breez-sdk-spark-react-native@0.14.0

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@breeztech/breez-sdk-spark-react-native@0.14.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm hashes-grs

Location: Package overview

From: package-lock.jsonnpm/edge-currency-plugins@3.9.0npm/hashes-grs@1.2.0

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/hashes-grs@1.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

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.

1 participant