Skip to content

Stablecoin swap#6

Merged
Jeezman merged 6 commits into
mainfrom
stablecoin-swap
May 12, 2026
Merged

Stablecoin swap#6
Jeezman merged 6 commits into
mainfrom
stablecoin-swap

Conversation

@Jeezman

@Jeezman Jeezman commented May 12, 2026

Copy link
Copy Markdown
Owner

BTC → stablecoin swaps + WalletConnect + LN recovery

Summary

Adds BTC → stablecoin (USDC/USDT on Ethereum) swaps via LendaSwap, WalletConnect for destination addresses, and a recovery screen for stuck Lightning submarine swaps. Architecturally pivots LendaSwap integration from Rust-side lendaswap-core to the TypeScript @lendasat/lendaswap-sdk-pure; Rust keeps a DB-only persistence layer (lendaswap.db) so swap history survives restarts and the reconciler catches up on app boot.

What's new

User-facing

  • New Swap tab in the bottom nav → /swap (quote, token picker, destination via paste or WalletConnect)
  • Checkout screen at /swap/checkout/$id — pay from avark or via external Lightning wallet (QR + invoice copy), in-app claim button once swap reaches processing, status pulse + polling
  • History at /swap/history (paginated, newest-first)
  • Settings → Recovery → Stuck Lightning payments (/recover/ln) — list pending submarine swaps with refund actions (cooperative refund via Boltz, or unilateral when timelock expires)
  • Send flow now reports Routing… instead of hanging when a Lightning payment's settlement takes longer than 30s — VHTLC is funded, settlement is in flight, recovery screen handles the rest

Backend (Rust)

  • lendaswap.db (SQLite) with lendaswap_swaps table; opened at tauri::Builder::setup, cleared on wallet delete
  • DB-only Tauri commands: insert_lendaswap_swap, update_lendaswap_swap_status, dismiss_lendaswap_swap, get_lendaswap_swap, list_lendaswap_swaps
  • get_lendaswap_xprv — derives a purpose-isolated xprv at m/887'/0' and hands it to the WebView. The BIP39 mnemonic stays Rust-only; a WebView compromise can drain LendaSwap-purpose keys but not the main wallet
  • list_pending_submarine_swaps + refund_submarine_swap for the recovery flow
  • tauri-plugin-http registered so the frontend fetch-proxy can reach API hosts whose CORS doesn't whitelist mobile WebView origins

Frontend (TS)

  • src/lib/lendaswap/client.ts — SDK wrapper, IDB legacy-purge (scrubs pre-pivot lendaswap-v2/v3 DBs that stored the mnemonic), Boltz-Arkade preflight, fee scaling, status mapping
  • src/lib/lendaswap/reconcile.ts — once-per-session catch-up on non-terminal swaps
  • src/lib/tauriFetch.ts — installs a globalThis.fetch proxy at app boot, routes only the explicitly-allowed hosts through Tauri's HTTP plugin
  • WalletConnect v2 via @walletconnect/sign-client (session restore, mobile deep-link → QR fallback)

Security model

  • LendaSwap signer is initialized with a purpose-derived xprv at m/887'/0', not the wallet's root mnemonic. The SDK's withXprv(...) is documented as ephemeral (never written to IndexedDB), so the xprv only lives in WebView session memory
  • 887' is unused by the standard BIP registry; the trailing 0' is a generation counter (bump to 1' to invalidate the swap subtree without rotating the mnemonic)
  • Capabilities http:default is scoped to an explicit URL allowlist — no wildcard *

Configuration

  • New env var VITE_WALLETCONNECT_PROJECT_ID (required for swaps; missing → "WalletConnect not configured" screen). Add as a GitHub Actions Variable; release.yml is wired
  • New capabilities in src-tauri/capabilities/default.json: HTTP allowlist for LendaSwap, Boltz, ASPs, WalletConnect relay/verify; opener allow-rule for wc:*
  • lendaswap.db lives alongside wallet.db / swaps.db / boarding.db in the app data dir and is cleared by delete_wallet

Migration / data handling

  • Idempotent SQLite migration (rebuild_fee_columns_if_legacy) rebuilds the table to INTEGER fee columns if a dev install has the original TEXT shape. Safe pre-release because the only rows present are dev test data
  • One-shot IDB purge of legacy lendaswap-v2 / lendaswap-v3 databases, flagged via localStorage so it runs at most once per install

Known limitations / follow-ups

  • swap-checkout-route.tsx is 1,089 lines — sub-components can be extracted; tracked as cleanup
  • TS terminal-status set is duplicated in three places (Rust enum, reconcile.ts, two route files) — should converge on one source of truth
  • Boltz-Arkade and "duplicate invoice" errors are matched by regex on the message string; should be tagged structurally before the bridge wording rotates
  • WalletConnect SDK currently loads on app boot for every user; opportunity to lazy-load when the swap subtree mounts

Test plan

  • Onboard a fresh wallet, top up via Lightning
  • Quote flow: enter amount, switch tokens (USDC/USDT), confirm rate refreshes on token change and at 30s expiry
  • Pay from avark: swap with sufficient balance, confirm checkout flips to processing, claim, confirm completed + Etherscan tx hash renders
  • Pay externally: swap with insufficient balance, scan QR from another Lightning wallet, confirm checkout advances
  • Slow LN settlement: pay an invoice where Boltz takes >30s to route; confirm Send sheet shows "Routing…" and checkout shows the BoltzInFlight panel; no double-spend on retap
  • WalletConnect: pair from MetaMask Mobile (deep link on mobile, QR on desktop), disconnect, reconnect; confirm address pre-fills swap destination
  • History: create multiple swaps, paginate via "Load more", deep-link from history into checkout
  • Dismiss: leave an awaiting_payment swap to age past 10 min, confirm dismiss footer appears and works
  • Recovery: deliberately interrupt a Send during routing; visit /recover/ln; confirm the stuck swap is listed and refund succeeds
  • Cold restart: kill app mid-swap, relaunch, confirm reconciler updates status
  • Delete wallet: confirm lendaswap.db is removed and a subsequent restore doesn't hit MnemonicMismatch
  • Build sanity: cargo fmt --check, cargo clippy -- -D warnings, cargo test, pnpm tsc, pnpm lint
  • Mobile builds: pnpm tauri android build -- --features vendored-openssl, pnpm tauri ios build

Jeezman added 6 commits May 12, 2026 15:47
Add a Lightning → USDC/USDT-on-Ethereum swap flow so users can off-ramp to stablecoins without leaving avark.
@Jeezman Jeezman merged commit f9432bc into main May 12, 2026
2 checks passed
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