Skip to content

feat(wallet): derive Lightning wallet from main mnemonic on create/import#496

Merged
biwasxyz merged 2 commits intomainfrom
feat/unified-lightning-mnemonic
May 2, 2026
Merged

feat(wallet): derive Lightning wallet from main mnemonic on create/import#496
biwasxyz merged 2 commits intomainfrom
feat/unified-lightning-mnemonic

Conversation

@biwasxyz
Copy link
Copy Markdown
Collaborator

@biwasxyz biwasxyz commented May 1, 2026

Closes #477 (initial-setup acceptance criterion — first AC of the issue).

Summary

  • wallet_create / wallet_import now auto-provision a Spark-backed Lightning wallet from the same mnemonic as the Stacks/BTC wallet on mainnet — users back up one phrase, get all four wallets (Stacks L2, BTC SegWit, BTC Taproot, Lightning).
  • Lightning deposit address is surfaced in the response so the wallet is immediately usable for lightning_fund_from_btc and L402 challenges.
  • Skips gracefully when network is testnet (Spark has no public Bitcoin testnet) or when a Lightning keystore already exists (never clobbers pre-existing standalone Lightning wallets created via lightning_create / lightning_import).
  • Spark init failures (e.g. connectivity) are rendered as a non-fatal warning — the main wallet is already saved by then, so users still get a working Stacks/BTC wallet.

Files

  • src/services/lightning-manager.ts — new setupFromMainMnemonic() returning LightningUnifiedSetupResult (setup vs skipped + reason).
  • src/tools/wallet-management.tools.ts — composes Lightning setup into wallet_create / wallet_import; never fails the parent flow.
  • CLAUDE.md — documents the unified flow and the concentrated-risk trade-off.

Out of scope (follow-ups for #477)

  • lightning_import_from_main for users with an existing main wallet who want to opt in
  • Surfacing Lightning identifiers in wallet_status
  • Migrating already-created Lightning wallets to the main mnemonic

Existing two-mnemonic users (created via PR #474 with lightning_create / lightning_import) keep working — the unified setup detects an existing keystore and leaves it alone.

Test plan

  • On mainnet, run wallet_create — response includes a Lightning (BTC L2) section with a deposit address; ~/.aibtc/lightning/keystore.json is created
  • On mainnet, run wallet_import with a known mnemonic — Lightning wallet is derived from the same mnemonic; deposit address matches lightning_status after lightning_unlock
  • On testnet, run wallet_create — Lightning section shows "Lightning is currently only supported on mainnet…"; no ~/.aibtc/lightning/keystore.json is created
  • With an existing ~/.aibtc/lightning/keystore.json, run wallet_create — Lightning section shows the "leaving it untouched" message; existing keystore is unchanged
  • Simulate Spark failure (e.g. offline) — wallet_create still succeeds and returns a lightningWarning rendered in the Lightning section
  • Existing Lightning users (created via lightning_create) can still lightning_unlock after this change

🤖 Generated with Claude Code

biwasxyz and others added 2 commits May 1, 2026 20:24
…port

Closes the UX gap from #477: users no longer need to back up two
mnemonics to use Lightning. wallet_create and wallet_import now
auto-provision a Spark-backed Lightning wallet from the same mnemonic
on mainnet, surfacing the deposit address in the response.

- lightning-manager: setupFromMainMnemonic() — skips on testnet (Spark
  has no public Bitcoin testnet) and when a Lightning keystore already
  exists (never clobber pre-existing standalone Lightning wallets).
- wallet-management.tools: render Lightning section in wallet_create /
  wallet_import responses; non-fatal if Spark init fails.
- CLAUDE.md: document the unified flow and the concentrated-risk
  trade-off of a single seed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 5 vitest cases for setupFromMainMnemonic (offline, mocks fs and
SparkLightningProvider):

- skips on testnet without touching Spark or writing a keystore
- preserves an existing Lightning keystore (no clobber)
- on mainnet, derives a Lightning wallet from the main mnemonic and
  persists an encrypted keystore (mnemonic plaintext is never written)
- rejects invalid mnemonics before calling Spark
- normalizes whitespace + case so messy user input still validates

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@biwasxyz biwasxyz requested review from arc0btc and whoabuddy May 1, 2026 14:55
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

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

Derives a Lightning wallet from the main mnemonic on wallet_create / wallet_import — clean implementation of the "one backup, four wallets" design.

What works well:

  • The LightningUnifiedSetupResult discriminated union (setup | skipped) is idiomatic TypeScript — exhaustive branches, clear intent.
  • Non-fatal error boundary in tryUnifiedLightningSetup is exactly right. The main wallet is already persisted by the time Spark runs, so any Lightning failure should be a warning, not a crash. The test for the Spark-offline scenario validates this path.
  • Keystore existence check before writing prevents any clobber of pre-existing wallets — the test for this case is solid.
  • Mnemonic normalization (trim + lowercase) before validateMnemonic avoids silent failures from user input.
  • Test coverage is thorough: testnet skip, existing keystore skip, happy path, invalid mnemonic rejection, and whitespace normalization all covered.
  • The security trade-off is clearly documented in both CLAUDE.md and the wallet response warning — no surprises for the user.

[question] Network consistency between main wallet and Lightning wallet (src/tools/wallet-management.tools.ts:97-111)

walletManager.createWallet(name, password, network) is called with the raw network param (possibly undefined). Then resolvedNetwork = network || NETWORK is computed and passed to Lightning setup. If walletManager.createWallet defaults differently when network is undefined — or doesn't default at all — the two wallets could theoretically use different effective networks. Worth confirming that createWallet also resolves to NETWORK when network is undefined.

[suggestion] Combine split imports from the same module (src/tools/wallet-management.tools.ts:6-7)

import { NETWORK, API_URL, type Network } from "../config/networks.js";

Two separate import statements from ../config/networks.js (one for values, one for the type) can be merged into one using import type inline syntax.

[nit] const resolvedNetwork = network || NETWORK is duplicated verbatim in both wallet_create and wallet_import. Not a bug now, but if a third wallet operation gets added it's easy to forget. The duplication is small enough to leave as-is, just flagging it.

Operational context: We use getLightningManager() in our own sensors (L402 challenge handling). The singleton pattern has been stable. The setupFromMainMnemonic path adds Spark initialization latency to wallet_create — for our use case that's a one-time cost during agent onboarding, so it's acceptable. The non-fatal boundary means our automation won't stall if the Spark endpoint is temporarily unreachable, which we've seen happen.

@biwasxyz biwasxyz merged commit d204620 into main May 2, 2026
5 checks passed
@biwasxyz biwasxyz deleted the feat/unified-lightning-mnemonic branch May 2, 2026 14:34
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.

Unify Lightning wallet setup with main wallet (single-mnemonic option)

2 participants