Repository navigation
Lightning network support - #1896
Open
gemcoder21 wants to merge 54 commits into
Open
gemcoder21 wants to merge 54 commits into
gemcoder21 wants to merge 54 commits into
Conversation
* Core adds Chain::Lightning, its own chain type, with the asset Bitcoin Lightning (BTC, 8 decimals, so base units are sats). Phrase wallets derive the account through the usual missing-chain setup; the address is the Spark address of m/8797555'/1'/0'. It is off by default, takes the Bitcoin price, and is left out of device subscriptions and private-key import. * gem_spark wraps Breez's spark-wallet with no API key. The mnemonic stays in GemKeystore, the signer drops its key on lock, and the address and invoice code build without the wallet stack, so the backend links none of it. * Spark reads need the identity key, so Core keeps open wallets in a session registry. The Lightning provider reads balance, payments, fee quotes and payment status from it; a locked wallet keeps its stored balance instead of failing the refresh. * Sends use the standard flow: an invoice with an amount goes straight to confirm, an amountless one to the amount screen. The signer signs the invoice, amount and quoted fee with the identity key, and broadcast pays only when that signature matches the open wallet. * Transaction rows show Lightning amounts without the dust cutoff and without a counterparty.
… through the standard flow * The unlock context is kept for the unlocked period and cleared on relock. Only the Lightning password provider reads the keystore password with it, so opening Lightning needs no second prompt. * The current wallet's Lightning opens on wallet refresh and on return to the foreground when it is enabled, and every move to the background locks all sessions. * Receive on the coin opens an invoice screen with an optional amount, QR, copy and share, and closes when the invoice is paid. Send goes through Recipient, which asks for a Lightning invoice. * The Bitcoin asset screen links to Bitcoin Lightning.
…ends through the standard flow
Mainnet only: no LightningNetwork or regtest paths. The receive screen's logic lives in GemLightningService.receive and receive_state, and the payment decoder reads lightning: and bare invoices so recipients take the generic path. Lightning checks move to their owners: Chain::is_indexed for subscriptions and backend sync, an empty counterparty drops the row subtitle, the Bitcoin screen's Lightning row comes from asset details through a generic GemRowAction::Asset, and the backend alone prices Lightning. Operator sessions use the SDK's in-memory store. Opens read the lock generation before any await, a send that fails after commit returns its transfer id, a retry with another amount is refused, and a running send is never resumed or dropped. CI installs protoc for the Spark SDK.
…im unused API A send whose commit check fails, or whose retry cannot resume, returns its transfer id instead of an error, and the record of a successful send stays until a sync confirms it; a status check reads a missing transfer with no record as failed. open_enabled reads the current wallet itself, the invoice debounce is a Core constant, and the unused sync export, invoice fields, provider payments path and invoice fuzz target are gone.
…ghtning without a wallet lookup
… forwarding API resume deletes a pending-send record only when the operators have no such transfer or it failed, or once the invoice expires, so confirming the same invoice again keeps returning the first transfer. The provider asks for the send fee instead of a whole quote, open_enabled and the wallet lookup reuse the session helpers, and the Lightning decoder test moves to the URL router.
…or a replaced invoice
# Conflicts: # android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/asset/models/AssetAction.kt
…one except Spark's own client The wallet node check has no Lightning node to probe, so it asks for the basic check and the test skips it. The CI check that keeps reqwest out of gemstone prunes Spark's platform-utils, which brings its own HTTP client; LIGHTNING.md records that Spark traffic bypasses the native provider and that release builds need protoc. Lightning error conversions live with the other error conversions, a bad stored transfer id reads as InvalidTransfer, the broadcast classifier uses the trait default, and the gemstone iOS recipe links the system SQLite like generate-stone.
# Conflicts: # android/app/src/main/kotlin/com/gemwallet/android/di/DataModule.kt # core/gemstone/justfile
1. StoredTreeStore implements Spark's TreeStore over GemLightningStore: Spark's in-memory store keeps the selection and reservation rules and each changed leaf is written through with its exit chain 2. Pending sends move to the same store, so rusqlite and spark-sqlite leave Core and wallets no longer have a spark.sqlite file 3. LIGHTNING.md describes only the current design
1. lightning_leaves and lightning_pending_sends tables keyed by wallet id and deleted with the wallet 2. GemstoneLightningStore hands LightningStore to Core in place of a storage directory 3. Gemstone no longer links the system SQLite for Spark
1. lightning_leaves and lightning_pending_sends tables keyed by wallet id and deleted with the wallet 2. GemstoneLightningStore hands LightningDao to Core in place of a storage directory
1. Spark uses its default in-memory tree store, so StoredTreeStore and the leaf half of GemLightningStore go 2. LIGHTNING.md: leaves are read from the operators on each open and exit chains are not kept on the device
# Conflicts: # core/crates/primitives/src/wallet_connect_namespace.rs # core/gemstone/src/services/recipient/rules.rs
gemcoder21
marked this pull request as ready for review
November 14, 2026 02:34
…yped JSON and drop partner attribution
…e Lightning service
…store synchronous and drop the transfer fuzz target
…en until the wallet is deleted
…er and password wrapper
…thorization and duplicate tests
…state Balance, fee, send, status and payment sync open a wallet from its stored key when no session is open. Turning Lightning on in Manage derives and stores the key, and the foreground open reads only the stored key. A wallet without a stored key fails as cancelled, and the receive screen keeps Unlock for that case.
# Conflicts: # docs/FEATURES.md
Receive no longer takes an amount: it returns an any-amount invoice and drops the amount field and the invoice debounce constant. The QR holds the invoice in uppercase so it encodes in the denser alphanumeric mode; copy and share keep the lowercase invoice.
Spark stops its background tasks when the wallet drops, so the extra cancel channel, Drop impl and tokio dependency go. Pending sends derive expiry from the stored invoice, the transfer digest lives on the transfer, TransferId is parsed once, and LightningError::from_display replaces the repeated string conversions. Smaller cuts: inlined single-use helpers, one key-name helper, shared network row, unused mocks and test lines.
# Conflicts: # docs/FEATURES.md
# Conflicts: # docs/FEATURES.md
The Lightning asset is now symbol sats with 0 decimals, so balances, activity and amounts read in whole sats. The backend shares Bitcoin's price rows and divides by AssetId::price_scale, derived from the two chains' decimals, wherever it serves a price, past price, chart, portfolio chart or candle for the asset. Scanned invoices route to confirm with their amount, or to the amount screen when amountless.
The asset chart and the portfolio chart divide by AssetId::price_scale inside the multiplications they already do, so the scaled_charts helper is gone. Candles reuse the app's candle rate conversion, moved into ChartCandleStick::with_rate, in place of a second scaling copy. Past prices are no longer scaled, since Lightning activity never comes from the backend.
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.
Adds Bitcoin Lightning as a coin on iOS and Android, through Spark with Breez's
spark-walletcrate called directly from Core. Design: docs/LIGHTNING.md.Chain::Lightning(assetlightning), phrase wallets only, off by default and turned on in Manage. The account address is the Spark address of the identity keym/8797555'/1'/0'.sats, 0 decimals). It shares Bitcoin's price rows, divided byAssetId::price_scalewherever a price, chart or candle is served for it.lightning:prefix optional). The signer signs the Spark address, invoice, amount and quoted fee as typed JSON with the keystore key, so a send asks for authentication like any other coin, and broadcast pays only when that signature verifies against the open wallet's identity key.m/8797555'/1'in Core and stores it inGemSecureStore(Keychain, when unlocked, this device only; the Tink-encrypted store on Android) without biometrics. Balance, receive, claiming and sync open the wallet from that key without prompting, and a session stays open until the wallet is deleted. The key is removed with the wallet's secrets and is never exported.Trade-off to review: the stored account key is a hot key. Anyone who can read the app's secure storage on an unlocked device can spend the Lightning balance, even when the secret phrase is protected by biometrics. It cannot reach the phrase or any other coin.
Cost for a wallet that never turns Lightning on:
Tested: CI lint, schema check and unit tests. Mainnet receive and send were verified on an earlier storage design; they have not been repeated since the stored key, preferences store and sats changes, and Android has not been run on an emulator.
Size: 4,259 added lines in 145 files, of which 1,287 are
Cargo.lock(the dependencies ofspark-wallet). Hand-written code, tests and docs are about 2,970 lines.Not in this PR: Lightning Address, LNURL,
bitcoin:?lightning=URIs, Spark-address sends, on-chain deposit and withdrawal.