diff options
| author | Adam Soltys <[email protected]> | 2026-07-04 09:33:52 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-04 09:33:52 -0700 |
| commit | b7c5abd93868b97c25f52610510544764a3e44eb (patch) | |
| tree | ab2ae94ab10bb88d32dec24c9987f81ef3c7faf0 /src/swap.js | |
| parent | 11b2a56a320bbd66b98991fd08478d154ff6c717 (diff) | |
| download | coinosv3-b7c5abd93868b97c25f52610510544764a3e44eb.tar.gz coinosv3-b7c5abd93868b97c25f52610510544764a3e44eb.zip | |
Features phase 2: gift/swap/ark primitives leave the core wallet
wallet.js drops from 2,762 to 2,374 lines and now exposes two generic
seams instead of feature code:
- registerCoinLock()/lockedCoinIds(): features contribute coin ids that
spending and the balance treat as unavailable (gift reservations).
- loadFeatureState()/saveFeatureState(): namespaced per-wallet persisted
state.
src/features/gifts-wallet.js takes the whole gift engine — reservation
sets, gift records, coin selection, the SIGHASH_SINGLE/NONE PSBT
builder, the compact codec, claim building, nostr-locked gifts — as an
installGiftWallet(wallet) instance install plus standalone exports.
Swap storage/key helpers (loadSwaps/saveSwaps/swapNode/nextSwapIndex)
install from swap.js; ark state storage installs from the ark feature.
The silent-payment scan worker only embeds when the sp feature ships
(wallet.js already tolerated its absence).
Minimal profile: 412KB, down from 476KB (full: 578KB). All suites pass:
ark UI e2e, ArkManager crash-resume, gift lifecycle, minimal smoke.
SP's engine (scanning, BIP-352 signing) stays in wallet.js — extracting
it safely wants an SP e2e harness on this stack first.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
Diffstat (limited to 'src/swap.js')
| -rw-r--r-- | src/swap.js | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/src/swap.js b/src/swap.js index 9afe7e7..d7a1e9c 100644 --- a/src/swap.js +++ b/src/swap.js @@ -231,6 +231,44 @@ export function decodeBolt11(invoice) { // Orchestrates non-custodial swaps: creates them via Boltz, verifies the lockup // address locally before any funds move, then polls the chain (via the wallet's // esplora api) to claim (reverse) or detect success/refund (submarine). DOM-free. +// Wallet storage/key helpers for this feature, installed onto the core +// wallet instance so a build without the feature ships none of it. +export function installSwapWallet(wallet) { + if (wallet.loadSwaps) return; // already installed + Object.assign(wallet, { + // ---- Boltz swap support ---------------------------------------------- + // Persisted swap records (public metadata only — keys/preimage are re-derived + // deterministically from the seed via swapNode, so nothing secret is stored). + _swapKey() { return this._cacheKey() + ':swaps'; }, + loadSwaps() { + try { return JSON.parse(localStorage.getItem(this._swapKey()) || '[]'); } catch { return []; } + }, + saveSwaps(list) { + try { localStorage.setItem(this._swapKey(), JSON.stringify(list)); } catch {} + }, + _swapIdxKey() { return this._cacheKey() + ':swapidx'; }, + // Reserve the next swap index: monotonic + persisted on every call. A reverse/ + // submarine attempt that fails AFTER Boltz created the swap (a later validation + // throw, a dropped response) still burns its index, so a retry can't reuse the + // same deterministic preimage (Boltz rejects "swap with this preimage exists"). + // Seeded from saved swaps so existing wallets don't regress. + nextSwapIndex() { + const fromSaved = this.loadSwaps().reduce((m, s) => Math.max(m, (s.swapIndex ?? -1) + 1), 0); + let stored = 0; + try { stored = parseInt(localStorage.getItem(this._swapIdxKey()) || '0', 10) || 0; } catch {} + const idx = Math.max(fromSaved, stored); + try { localStorage.setItem(this._swapIdxKey(), String(idx + 1)); } catch {} + return idx; + }, + // Deterministic swap keypair on a dedicated chain (2) so swap keys never + // collide with receive (0) / change (1). Returns the HDKey node (has + // privateKey + publicKey, 33-byte compressed). + swapNode(index) { + return this.account().deriveChild(2).deriveChild(index); + }, + }); +} + export class SwapManager { constructor({ wallet, network = 'regtest', getApi, feeRate = 2, onUpdate }) { this.wallet = wallet; |
