aboutsummaryrefslogtreecommitdiffstats
path: root/tools/swap-vectors.test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-27 08:41:08 -0700
committerAdam Soltys <[email protected]>2026-06-27 08:41:08 -0700
commit10f9ebc0d047f1599f4665fda0b073c449bb8f64 (patch)
treeb5e68e8044cabd9d878b8ed8e3715d46e5cc9d0c /tools/swap-vectors.test.js
parenta9a903e7a1f80ed4a6f4163419b25d4e6de5db01 (diff)
downloadcoinosv3-10f9ebc0d047f1599f4665fda0b073c449bb8f64.tar.gz
coinosv3-10f9ebc0d047f1599f4665fda0b073c449bb8f64.zip
Add Boltz submarine + reverse swaps (Lightning <-> on-chain)
Non-custodial swaps via Boltz, fully client-side: hal holds the swap key + preimage and claims (reverse) / refunds (submarine) on-chain via the taproot script path, so Boltz can never strand funds. - src/swap.js: tapleaf/tree construction (byte-exact vs boltz-core vectors), swap P2TR address derivation (MuSig2 internal key + taptweak), script-path claim/refund tx builder, bolt11 decode, Boltz api-v2 client, and a polling SwapManager (reverse auto-claim; submarine fund + status + refund). - wallet.js: deterministic swap-key derivation (dedicated chain) + persistence. - api.js: configurable Boltz REST/WS endpoints. - app.js: ⚡ Swap tab — Receive over LN (reverse) + Pay an invoice (submarine). - tools/swap-*.test.js: vectors, live-address, and end-to-end regtest tests. Verified end-to-end on regtest against a local Boltz wired to coinos's bc + a dedicated CLN/hold node. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat (limited to 'tools/swap-vectors.test.js')
-rw-r--r--tools/swap-vectors.test.js42
1 files changed, 42 insertions, 0 deletions
diff --git a/tools/swap-vectors.test.js b/tools/swap-vectors.test.js
new file mode 100644
index 0000000..147b114
--- /dev/null
+++ b/tools/swap-vectors.test.js
@@ -0,0 +1,42 @@
+// Verify Hal's swap tapscript/tree construction byte-for-byte against
+// boltz-core's published test vectors (boltz-core/src/bitcoin/scripts/*.rs).
+// Run: bun tools/swap-vectors.test.js
+import {
+ submarineClaimLeaf, reverseClaimLeaf, refundLeaf, swapMerkleRoot,
+ hexToBytes, bytesToHex,
+} from '../src/swap.js';
+import { p2tr, taprootListToTree } from '@scure/btc-signer/payment';
+
+let pass = 0, fail = 0;
+const eq = (name, got, want) => {
+ if (got === want) { pass++; console.log(` ok ${name}`); }
+ else { fail++; console.log(`FAIL ${name}\n got: ${got}\n want: ${want}`); }
+};
+
+// --- swap_tree.rs vector ---
+const claimPk = hexToBytes('814fe2b5ce0e3fe5787fade9a4357a743c4ce6de473e5f29daf42112e47db25a');
+const refundPk = hexToBytes('0095b23ac89f523cdbcd791d3325ada7ddb30fd6134bb7fff22ba1e00414cb7d');
+const ph = hexToBytes('34e64e5b1373a872019ee2c7791cf4264d1079de'); // hash160(preimage)
+
+eq('submarine claim leaf',
+ bytesToHex(submarineClaimLeaf(ph, claimPk)),
+ 'a91434e64e5b1373a872019ee2c7791cf4264d1079de8820814fe2b5ce0e3fe5787fade9a4357a743c4ce6de473e5f29daf42112e47db25aac');
+
+// Cross-check our two-leaf merkle root against @scure's own taproot tree
+// builder (BIP341) for the same leaves — proves the TapBranch/sort logic.
+const claim = submarineClaimLeaf(ph, claimPk);
+const refund = refundLeaf(refundPk, 123);
+const scureRoot = bytesToHex(
+ p2tr(claimPk, taprootListToTree([{ script: claim }, { script: refund }]), undefined, true).tapMerkleRoot,
+);
+eq('merkle root matches @scure tree builder', bytesToHex(swapMerkleRoot(claim, refund)), scureRoot);
+
+// --- reverse_tree.rs vector ---
+const rClaimPk = hexToBytes('97f2ea5402afd2e90130e84d97e455f67493a956c20faedce32086516ef9f12e');
+const rPh = hexToBytes('761de3d1f1f54cc8b3beec0a1ad03820a2e12b90');
+eq('reverse claim leaf',
+ bytesToHex(reverseClaimLeaf(rPh, rClaimPk)),
+ '82012088a914761de3d1f1f54cc8b3beec0a1ad03820a2e12b90882097f2ea5402afd2e90130e84d97e455f67493a956c20faedce32086516ef9f12eac');
+
+console.log(`\n${pass} passed, ${fail} failed`);
+process.exit(fail ? 1 : 0);