@satora/swap 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bitcoin-reader-electrum-PY7DHD6T.js +7 -0
- package/dist/{bitcoin-reader-esplora-HV6CANAA.js → bitcoin-reader-esplora-ZLPMM5FF.js} +2 -2
- package/dist/{chunk-VHXEU534.js → chunk-FTPIQSRW.js} +12 -5
- package/dist/chunk-FTPIQSRW.js.map +1 -0
- package/dist/{chunk-EIWELJJK.js → chunk-THI4UO6R.js} +11 -5
- package/dist/chunk-THI4UO6R.js.map +1 -0
- package/dist/index.cjs +187 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -4
- package/dist/index.d.ts +119 -4
- package/dist/index.js +169 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/bitcoin-reader-electrum-4DOJ7NGH.js +0 -7
- package/dist/chunk-EIWELJJK.js.map +0 -1
- package/dist/chunk-VHXEU534.js.map +0 -1
- /package/dist/{bitcoin-reader-electrum-4DOJ7NGH.js.map → bitcoin-reader-electrum-PY7DHD6T.js.map} +0 -0
- /package/dist/{bitcoin-reader-esplora-HV6CANAA.js.map → bitcoin-reader-esplora-ZLPMM5FF.js.map} +0 -0
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ await client.claim(swapId);
|
|
|
94
94
|
|
|
95
95
|
// Refund after timeout
|
|
96
96
|
await client.refundSwap(swapId, {destinationAddress: "bc1q…"}); // or "ark1…"
|
|
97
|
-
await client.collabRefundEvmWithSigner(swapId, signer
|
|
97
|
+
await client.collabRefundEvmWithSigner(swapId, signer); // EVM, instant/gasless
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
## License
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
DEFAULT_ESPLORA_URLS,
|
|
3
3
|
esploraReader,
|
|
4
4
|
htlcFactsFromEsploraTxs
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-THI4UO6R.js";
|
|
6
6
|
export {
|
|
7
7
|
DEFAULT_ESPLORA_URLS,
|
|
8
8
|
esploraReader,
|
|
9
9
|
htlcFactsFromEsploraTxs
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=bitcoin-reader-esplora-
|
|
11
|
+
//# sourceMappingURL=bitcoin-reader-esplora-ZLPMM5FF.js.map
|
|
@@ -62,7 +62,17 @@ function electrumReader(client, opts) {
|
|
|
62
62
|
}
|
|
63
63
|
return indices;
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
let funding;
|
|
66
|
+
let fundedSats = 0;
|
|
67
|
+
for (const candidate of txs) {
|
|
68
|
+
let paid = 0;
|
|
69
|
+
for (const i of outputIndicesPaying(candidate.tx))
|
|
70
|
+
paid += Number(candidate.tx.getOutput(i).amount ?? 0n);
|
|
71
|
+
if (paid > fundedSats) {
|
|
72
|
+
funding = candidate;
|
|
73
|
+
fundedSats = paid;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
66
76
|
if (!funding) return { funding: "absent", fundedSats: 0 };
|
|
67
77
|
const fundedVouts = outputIndicesPaying(funding.tx);
|
|
68
78
|
for (const { tx } of txs) {
|
|
@@ -80,9 +90,6 @@ function electrumReader(client, opts) {
|
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
|
-
let fundedSats = 0;
|
|
84
|
-
for (const i of fundedVouts)
|
|
85
|
-
fundedSats += Number(funding.tx.getOutput(i).amount ?? 0n);
|
|
86
93
|
const height = funding.entry.height;
|
|
87
94
|
const isConfirmed = height > 0;
|
|
88
95
|
let deepEnough;
|
|
@@ -142,4 +149,4 @@ function electrumReader(client, opts) {
|
|
|
142
149
|
export {
|
|
143
150
|
electrumReader
|
|
144
151
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
152
|
+
//# sourceMappingURL=chunk-FTPIQSRW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contracts/bitcoin-reader-electrum.ts"],"sourcesContent":["/**\n * An Electrum-backed {@link BitcoinChainReader} — reads HTLC facts from our own\n * Fulcrum over WebSocket instead of public esplora REST endpoints.\n *\n * Two advantages over the esplora reader:\n *\n * - **Freshness**: our Fulcrum sees a transaction the moment its bitcoind\n * accepts it, while public explorers index with a lag — the exact lag that\n * made a server-funding hint's targeted verify come back \"absent\" and park\n * the claim on the 60s at-risk poller.\n * - **Push**: implements the reader's optional `subscribe` capability via\n * `blockchain.scripthash.subscribe`, so the manager reconciles the moment\n * an HTLC address's history changes instead of waiting for a poll tick.\n *\n * Reads fall back to a wrapped reader (typically the esplora one) whenever the\n * Electrum server errors, so a Fulcrum outage degrades to today's behavior\n * rather than blinding the tracker.\n */\n\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { hex } from \"@scure/base\";\nimport * as btc from \"@scure/btc-signer\";\nimport type { BitcoinHtlcFacts } from \"./bitcoin.js\";\nimport type { BitcoinChainReader } from \"./bitcoin-manager.js\";\nimport type { BitcoinConfirmationPolicy } from \"./bitcoin-reader-esplora.js\";\n\n/** One entry of `blockchain.scripthash.get_history`. */\ntype ElectrumHistoryEntry = {\n tx_hash: string;\n /** Block height; 0 for mempool, -1 for mempool with unconfirmed parents. */\n height: number;\n};\n\n/**\n * The Electrum client surface this reader needs. The pure SDK's\n * `ElectrumWsClient` satisfies it; tests inject a fake.\n */\nexport type ElectrumRpc = {\n request<T>(method: string, params: unknown[]): Promise<T>;\n subscribeScriptHash(\n scripthash: string,\n onChange: (status: string | null) => void,\n ): Promise<string | null>;\n unsubscribeScriptHash(\n scripthash: string,\n onChange: (status: string | null) => void,\n ): void;\n};\n\nexport type ElectrumReaderNetwork =\n | \"mainnet\"\n | \"testnet\"\n | \"signet\"\n | \"regtest\";\n\n/** btc-signer only ships mainnet/testnet; regtest differs in bech32 prefix. */\nconst REGTEST_NETWORK = {\n bech32: \"bcrt\",\n pubKeyHash: 0x6f,\n scriptHash: 0xc4,\n wif: 0xef,\n} as const;\n\nfunction toBtcSignerNetwork(network: ElectrumReaderNetwork) {\n switch (network) {\n case \"mainnet\":\n return btc.NETWORK;\n case \"testnet\":\n case \"signet\":\n return btc.TEST_NETWORK;\n case \"regtest\":\n return REGTEST_NETWORK;\n }\n}\n\n/** Electrum scripthash: sha256 of the output script, reversed, hex. */\nfunction scriptHashOf(script: Uint8Array): string {\n return hex.encode(sha256(script).reverse());\n}\n\n/**\n * Build a {@link BitcoinChainReader} over an Electrum connection, with push\n * support and an optional fallback reader for outages.\n */\nexport function electrumReader(\n client: ElectrumRpc,\n opts?: BitcoinConfirmationPolicy & {\n /** Network the HTLC addresses live on; defaults to mainnet. */\n network?: ElectrumReaderNetwork;\n /** Reader consulted when the Electrum server errors (e.g. esplora). */\n fallback?: BitcoinChainReader;\n },\n): BitcoinChainReader {\n const network = opts?.network ?? \"mainnet\";\n const defaultMinConf = opts?.minConfirmations ?? 0;\n const fallback = opts?.fallback;\n\n const scriptFor = (address: string): Uint8Array =>\n btc.OutScript.encode(\n btc.Address(toBtcSignerNetwork(network)).decode(address),\n );\n\n async function getHtlcFactsElectrum(\n address: string,\n required: number,\n ): Promise<BitcoinHtlcFacts> {\n const script = scriptFor(address);\n const scripthash = scriptHashOf(script);\n const history = await client.request<ElectrumHistoryEntry[]>(\n \"blockchain.scripthash.get_history\",\n [scripthash],\n );\n if (!history || history.length === 0)\n return { funding: \"absent\", fundedSats: 0 };\n\n // HTLC addresses see at most a handful of txs (fund + resolve), so\n // fetching each raw tx is cheap.\n const txs = await Promise.all(\n history.map(async (entry) => {\n const raw = await client.request<string>(\"blockchain.transaction.get\", [\n entry.tx_hash,\n ]);\n return {\n entry,\n tx: btc.Transaction.fromRaw(hex.decode(raw), {\n allowUnknownOutputs: true,\n allowUnknownInputs: true,\n disableScriptCheck: true,\n }),\n };\n }),\n );\n\n const scriptHex = hex.encode(script);\n const outputIndicesPaying = (tx: btc.Transaction): number[] => {\n const indices: number[] = [];\n for (let i = 0; i < tx.outputsLength; i++) {\n const out = tx.getOutput(i);\n if (out.script && hex.encode(out.script) === scriptHex) indices.push(i);\n }\n return indices;\n };\n\n // Among all txs paying the address, the one paying the MOST is the\n // funding candidate — same rule as the pure SDK's UTXO lookup. The\n // address is public, so a stray dust payment (whether it landed before\n // or after the real funding) must not be mistaken for the funding and\n // observe the swap as underfunded/invalid.\n let funding: (typeof txs)[number] | undefined;\n let fundedSats = 0;\n for (const candidate of txs) {\n let paid = 0;\n for (const i of outputIndicesPaying(candidate.tx))\n paid += Number(candidate.tx.getOutput(i).amount ?? 0n);\n if (paid > fundedSats) {\n funding = candidate;\n fundedSats = paid;\n }\n }\n if (!funding) return { funding: \"absent\", fundedSats: 0 };\n const fundedVouts = outputIndicesPaying(funding.tx);\n\n // A tx spending one of the funding outputs is the resolving spend; its\n // witness classifies claim vs refund.\n for (const { tx } of txs) {\n for (let i = 0; i < tx.inputsLength; i++) {\n const input = tx.getInput(i);\n if (\n input.txid &&\n hex.encode(input.txid) === funding.entry.tx_hash &&\n input.index !== undefined &&\n fundedVouts.includes(input.index)\n ) {\n return {\n funding: \"confirmed\",\n fundedSats: 0, // already spent — the amount no longer matters\n spendWitness: (input.finalScriptWitness ?? []).map((item) =>\n hex.encode(item),\n ),\n };\n }\n }\n }\n\n // Same depth policy as the esplora reader: 0/1 read straight off the\n // confirmed flag; deeper policies need the tip to compute depth.\n const height = funding.entry.height;\n const isConfirmed = height > 0;\n let deepEnough: boolean;\n if (isConfirmed) {\n if (required <= 1) {\n deepEnough = true;\n } else {\n const tip = await client.request<{ height: number }>(\n \"blockchain.headers.subscribe\",\n [],\n );\n deepEnough = tip.height - height + 1 >= required;\n }\n } else {\n deepEnough = required === 0;\n }\n return { funding: deepEnough ? \"confirmed\" : \"mempool\", fundedSats };\n }\n\n return {\n async getHtlcFacts(address, minConfirmations) {\n const required = minConfirmations ?? defaultMinConf;\n try {\n return await getHtlcFactsElectrum(address, required);\n } catch (error) {\n if (!fallback) throw error;\n console.warn(\n \"electrumReader: falling back to secondary reader:\",\n error instanceof Error ? error.message : error,\n );\n return fallback.getHtlcFacts(address, minConfirmations);\n }\n },\n\n subscribe(address, onChange) {\n // An address the configured network can't decode (e.g. a signet address\n // under a mainnet-configured reader) must not throw out of register()\n // and abort tracking — degrade to the poll cadence instead.\n let scripthash: string;\n try {\n scripthash = scriptHashOf(scriptFor(address));\n } catch (error) {\n console.warn(\n \"electrumReader: subscribe skipped (address decode failed):\",\n error instanceof Error ? error.message : error,\n );\n return () => {};\n }\n const handler = () => onChange();\n // Best-effort: if the subscription fails the tracker's poll cadence\n // remains the backstop, so the error is logged, not thrown.\n client.subscribeScriptHash(scripthash, handler).catch((error) => {\n console.warn(\n \"electrumReader: scripthash subscribe failed:\",\n error instanceof Error ? error.message : error,\n );\n });\n return () => client.unsubscribeScriptHash(scripthash, handler);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -12,11 +12,17 @@ function htlcFactsFromEsploraTxs(txs, address, opts) {
|
|
|
12
12
|
spendWitness: spend.witness ?? []
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const paidSats = (tx) => tx.vout.filter((vout) => vout.scriptpubkey_address === address).reduce((sum, vout) => sum + (vout.value ?? 0), 0);
|
|
16
|
+
let funding;
|
|
17
|
+
let fundedSats = 0;
|
|
18
|
+
for (const tx of txs) {
|
|
19
|
+
const paid = paidSats(tx);
|
|
20
|
+
if (paid > fundedSats) {
|
|
21
|
+
funding = tx;
|
|
22
|
+
fundedSats = paid;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
if (funding) {
|
|
19
|
-
const fundedSats = funding.vout.filter((vout) => vout.scriptpubkey_address === address).reduce((sum, vout) => sum + (vout.value ?? 0), 0);
|
|
20
26
|
const minConf = opts?.minConfirmations ?? 0;
|
|
21
27
|
let deepEnough;
|
|
22
28
|
if (funding.status?.confirmed) {
|
|
@@ -78,4 +84,4 @@ export {
|
|
|
78
84
|
DEFAULT_ESPLORA_URLS,
|
|
79
85
|
esploraReader
|
|
80
86
|
};
|
|
81
|
-
//# sourceMappingURL=chunk-
|
|
87
|
+
//# sourceMappingURL=chunk-THI4UO6R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contracts/bitcoin-reader-esplora.ts"],"sourcesContent":["/**\n * A esplora-backed {@link BitcoinChainReader} — the concrete chain source the\n * {@link BitcoinContractManager} uses in production.\n *\n * Kept separate from the manager so the manager stays free of any HTTP/esplora\n * dependency (and unit-testable against a fake reader). A single\n * `GET /address/{addr}/txs` yields both the funding state and the spending\n * input's witness, so no extra round-trips are needed.\n */\n\nimport type { BitcoinHtlcFacts } from \"./bitcoin.js\";\nimport type { BitcoinChainReader } from \"./bitcoin-manager.js\";\n\n/** The slice of esplora's tx JSON we read. */\ntype EsploraTx = {\n vin: Array<{\n witness?: string[];\n prevout?: { scriptpubkey_address?: string } | null;\n }>;\n vout: Array<{ scriptpubkey_address?: string; value?: number }>;\n status?: { confirmed?: boolean; block_height?: number };\n};\n\n/**\n * When a funding tx counts as `confirmed` for observation purposes.\n *\n * `minConfirmations: 0` (the default) accepts an UNCONFIRMED funding, so a swap\n * claims as soon as the funding hits the mempool instead of waiting ~10min for\n * a block. This TRUSTS the funder not to double-spend: claiming publishes the\n * preimage, so a funder who then replaces its funding tx could take both legs.\n * Our own server therefore broadcasts HTLC fundings without signalling RBF.\n * Note that a non-signalling tx is still replaceable by full-RBF miners — set\n * `1` (or more) for a policy that doesn't rely on the funder's good behaviour;\n * values above 1 make the reader fetch the tip height to compute depth.\n */\nexport type BitcoinConfirmationPolicy = {\n minConfirmations?: number;\n};\n\n/**\n * Reduce an address's esplora tx history to HTLC facts. If a tx spends an output\n * at the address, that's the resolving spend (its witness classifies claim vs\n * refund); otherwise a tx paying the address is the funding, gated by the\n * confirmation policy (see {@link BitcoinConfirmationPolicy}).\n */\nexport function htlcFactsFromEsploraTxs(\n txs: EsploraTx[],\n address: string,\n opts?: BitcoinConfirmationPolicy & { tipHeight?: number },\n): BitcoinHtlcFacts {\n for (const tx of txs) {\n const spend = tx.vin.find(\n (vin) => vin.prevout?.scriptpubkey_address === address,\n );\n if (spend)\n return {\n funding: \"confirmed\",\n fundedSats: 0, // already spent — the amount no longer matters\n spendWitness: spend.witness ?? [],\n };\n }\n // Among all txs paying the address, the one paying the MOST is the funding\n // candidate — matching the pure SDK's UTXO lookup. The address is public,\n // so a stray dust payment must not be mistaken for the funding and observe\n // the swap as underfunded/invalid.\n const paidSats = (tx: EsploraTx): number =>\n tx.vout\n .filter((vout) => vout.scriptpubkey_address === address)\n .reduce((sum, vout) => sum + (vout.value ?? 0), 0);\n let funding: EsploraTx | undefined;\n let fundedSats = 0;\n for (const tx of txs) {\n const paid = paidSats(tx);\n if (paid > fundedSats) {\n funding = tx;\n fundedSats = paid;\n }\n }\n if (funding) {\n const minConf = opts?.minConfirmations ?? 0;\n let deepEnough: boolean;\n if (funding.status?.confirmed) {\n deepEnough =\n minConf <= 1 ||\n (opts?.tipHeight !== undefined &&\n funding.status.block_height !== undefined &&\n opts.tipHeight - funding.status.block_height + 1 >= minConf);\n } else {\n deepEnough = minConf === 0;\n }\n return {\n funding: deepEnough ? \"confirmed\" : \"mempool\",\n fundedSats,\n };\n }\n return { funding: \"absent\", fundedSats: 0 };\n}\n\n/**\n * Public esplora endpoints tried in rotation (mainnet). blockstream.info runs the\n * reference esplora, so it's API-compatible with mempool.space; spreading calls\n * across both halves the per-provider load and survives one being throttled.\n */\nexport const DEFAULT_ESPLORA_URLS = [\n \"https://mempool.space/api\",\n \"https://blockstream.info/api\",\n];\n\n/**\n * Build a {@link BitcoinChainReader} over one or more esplora REST endpoints.\n * With several, each call starts at a rotating endpoint (to spread load across\n * providers) and fails over to the rest on error — so a throttled or failing\n * provider doesn't stall tracking.\n */\nexport function esploraReader(\n esploraUrls: string | string[],\n fetchImpl: typeof fetch = fetch,\n policy?: BitcoinConfirmationPolicy,\n): BitcoinChainReader {\n const bases = (Array.isArray(esploraUrls) ? esploraUrls : [esploraUrls]).map(\n (url) => url.replace(/\\/+$/, \"\"),\n );\n const minConf = policy?.minConfirmations ?? 0;\n let start = 0;\n return {\n async getHtlcFacts(address, minConfirmations) {\n const required = minConfirmations ?? minConf;\n const from = start++ % bases.length; // rotate the primary to spread load\n let lastError: unknown;\n for (let i = 0; i < bases.length; i++) {\n const base = bases[(from + i) % bases.length];\n try {\n const res = await fetchImpl(\n `${base}/address/${encodeURIComponent(address)}/txs`,\n );\n if (!res.ok) throw new Error(`esplora ${res.status} at ${base}`);\n const txs = (await res.json()) as EsploraTx[];\n // Depth policies beyond 1 need the tip to compute confirmations;\n // 0 and 1 read straight off the tx's confirmed flag.\n let tipHeight: number | undefined;\n if (required > 1) {\n const tip = await fetchImpl(`${base}/blocks/tip/height`);\n if (!tip.ok) throw new Error(`esplora ${tip.status} at ${base}`);\n tipHeight = Number(await tip.text());\n }\n return htlcFactsFromEsploraTxs(txs, address, {\n minConfirmations: required,\n tipHeight,\n });\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError ?? new Error(\"no esplora endpoints configured\");\n },\n };\n}\n"],"mappings":";AA6CO,SAAS,wBACd,KACA,SACA,MACkB;AAClB,aAAW,MAAM,KAAK;AACpB,UAAM,QAAQ,GAAG,IAAI;AAAA,MACnB,CAAC,QAAQ,IAAI,SAAS,yBAAyB;AAAA,IACjD;AACA,QAAI;AACF,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA;AAAA,QACZ,cAAc,MAAM,WAAW,CAAC;AAAA,MAClC;AAAA,EACJ;AAKA,QAAM,WAAW,CAAC,OAChB,GAAG,KACA,OAAO,CAAC,SAAS,KAAK,yBAAyB,OAAO,EACtD,OAAO,CAAC,KAAK,SAAS,OAAO,KAAK,SAAS,IAAI,CAAC;AACrD,MAAI;AACJ,MAAI,aAAa;AACjB,aAAW,MAAM,KAAK;AACpB,UAAM,OAAO,SAAS,EAAE;AACxB,QAAI,OAAO,YAAY;AACrB,gBAAU;AACV,mBAAa;AAAA,IACf;AAAA,EACF;AACA,MAAI,SAAS;AACX,UAAM,UAAU,MAAM,oBAAoB;AAC1C,QAAI;AACJ,QAAI,QAAQ,QAAQ,WAAW;AAC7B,mBACE,WAAW,KACV,MAAM,cAAc,UACnB,QAAQ,OAAO,iBAAiB,UAChC,KAAK,YAAY,QAAQ,OAAO,eAAe,KAAK;AAAA,IAC1D,OAAO;AACL,mBAAa,YAAY;AAAA,IAC3B;AACA,WAAO;AAAA,MACL,SAAS,aAAa,cAAc;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,SAAS,UAAU,YAAY,EAAE;AAC5C;AAOO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AACF;AAQO,SAAS,cACd,aACA,YAA0B,OAC1B,QACoB;AACpB,QAAM,SAAS,MAAM,QAAQ,WAAW,IAAI,cAAc,CAAC,WAAW,GAAG;AAAA,IACvE,CAAC,QAAQ,IAAI,QAAQ,QAAQ,EAAE;AAAA,EACjC;AACA,QAAM,UAAU,QAAQ,oBAAoB;AAC5C,MAAI,QAAQ;AACZ,SAAO;AAAA,IACL,MAAM,aAAa,SAAS,kBAAkB;AAC5C,YAAM,WAAW,oBAAoB;AACrC,YAAM,OAAO,UAAU,MAAM;AAC7B,UAAI;AACJ,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,OAAO,OAAO,OAAO,KAAK,MAAM,MAAM;AAC5C,YAAI;AACF,gBAAM,MAAM,MAAM;AAAA,YAChB,GAAG,IAAI,YAAY,mBAAmB,OAAO,CAAC;AAAA,UAChD;AACA,cAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,WAAW,IAAI,MAAM,OAAO,IAAI,EAAE;AAC/D,gBAAM,MAAO,MAAM,IAAI,KAAK;AAG5B,cAAI;AACJ,cAAI,WAAW,GAAG;AAChB,kBAAM,MAAM,MAAM,UAAU,GAAG,IAAI,oBAAoB;AACvD,gBAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,WAAW,IAAI,MAAM,OAAO,IAAI,EAAE;AAC/D,wBAAY,OAAO,MAAM,IAAI,KAAK,CAAC;AAAA,UACrC;AACA,iBAAO,wBAAwB,KAAK,SAAS;AAAA,YAC3C,kBAAkB;AAAA,YAClB;AAAA,UACF,CAAC;AAAA,QACH,SAAS,OAAO;AACd,sBAAY;AAAA,QACd;AAAA,MACF;AACA,YAAM,aAAa,IAAI,MAAM,iCAAiC;AAAA,IAChE;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -51,11 +51,17 @@ function htlcFactsFromEsploraTxs(txs, address, opts) {
|
|
|
51
51
|
spendWitness: spend.witness ?? []
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const paidSats = (tx) => tx.vout.filter((vout) => vout.scriptpubkey_address === address).reduce((sum, vout) => sum + (vout.value ?? 0), 0);
|
|
55
|
+
let funding;
|
|
56
|
+
let fundedSats = 0;
|
|
57
|
+
for (const tx of txs) {
|
|
58
|
+
const paid = paidSats(tx);
|
|
59
|
+
if (paid > fundedSats) {
|
|
60
|
+
funding = tx;
|
|
61
|
+
fundedSats = paid;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
57
64
|
if (funding) {
|
|
58
|
-
const fundedSats = funding.vout.filter((vout) => vout.scriptpubkey_address === address).reduce((sum, vout) => sum + (vout.value ?? 0), 0);
|
|
59
65
|
const minConf = opts?.minConfirmations ?? 0;
|
|
60
66
|
let deepEnough;
|
|
61
67
|
if (funding.status?.confirmed) {
|
|
@@ -177,7 +183,17 @@ function electrumReader(client, opts) {
|
|
|
177
183
|
}
|
|
178
184
|
return indices;
|
|
179
185
|
};
|
|
180
|
-
|
|
186
|
+
let funding;
|
|
187
|
+
let fundedSats = 0;
|
|
188
|
+
for (const candidate of txs) {
|
|
189
|
+
let paid = 0;
|
|
190
|
+
for (const i of outputIndicesPaying(candidate.tx))
|
|
191
|
+
paid += Number(candidate.tx.getOutput(i).amount ?? 0n);
|
|
192
|
+
if (paid > fundedSats) {
|
|
193
|
+
funding = candidate;
|
|
194
|
+
fundedSats = paid;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
181
197
|
if (!funding) return { funding: "absent", fundedSats: 0 };
|
|
182
198
|
const fundedVouts = outputIndicesPaying(funding.tx);
|
|
183
199
|
for (const { tx } of txs) {
|
|
@@ -195,9 +211,6 @@ function electrumReader(client, opts) {
|
|
|
195
211
|
}
|
|
196
212
|
}
|
|
197
213
|
}
|
|
198
|
-
let fundedSats = 0;
|
|
199
|
-
for (const i of fundedVouts)
|
|
200
|
-
fundedSats += Number(funding.tx.getOutput(i).amount ?? 0n);
|
|
201
214
|
const height = funding.entry.height;
|
|
202
215
|
const isConfirmed = height > 0;
|
|
203
216
|
let deepEnough;
|
|
@@ -279,6 +292,7 @@ __export(src_exports, {
|
|
|
279
292
|
DEFAULT_ESPLORA_URLS: () => DEFAULT_ESPLORA_URLS,
|
|
280
293
|
DEFAULT_EVM_RPCS: () => DEFAULT_EVM_RPCS,
|
|
281
294
|
EvmContractManager: () => EvmContractManager,
|
|
295
|
+
HintTracker: () => HintTracker,
|
|
282
296
|
SwapTracker: () => SwapTracker,
|
|
283
297
|
SwapWorker: () => SwapWorker,
|
|
284
298
|
WsStatusSource: () => WsStatusSource,
|
|
@@ -1491,6 +1505,113 @@ function defaultEvmReaders(overrides) {
|
|
|
1491
1505
|
return readers;
|
|
1492
1506
|
}
|
|
1493
1507
|
|
|
1508
|
+
// src/hints/hint-tracker.ts
|
|
1509
|
+
var HintTracker = class {
|
|
1510
|
+
#fetchStatus;
|
|
1511
|
+
#refreshIntervalMs;
|
|
1512
|
+
#swaps = /* @__PURE__ */ new Map();
|
|
1513
|
+
/** Last server-reported status per swap (seeded from storage, updated by hints). */
|
|
1514
|
+
#statuses = /* @__PURE__ */ new Map();
|
|
1515
|
+
/** Last actions emitted per swap — for dedupe and the subscribe-time snapshot. */
|
|
1516
|
+
#lastActions = /* @__PURE__ */ new Map();
|
|
1517
|
+
#subscribers = /* @__PURE__ */ new Set();
|
|
1518
|
+
#timer;
|
|
1519
|
+
constructor(options) {
|
|
1520
|
+
this.#fetchStatus = options.fetchStatus;
|
|
1521
|
+
this.#refreshIntervalMs = options.refreshIntervalMs ?? 5e3;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Always `true`: with no chain readers there is no reachability constraint.
|
|
1525
|
+
* (Claim/refund execution has its own chain configuration, independent of
|
|
1526
|
+
* tracking.)
|
|
1527
|
+
*/
|
|
1528
|
+
canObserve(_swap) {
|
|
1529
|
+
return true;
|
|
1530
|
+
}
|
|
1531
|
+
/** Register the swaps, derive each one's first action from its stored status. */
|
|
1532
|
+
async startTracking(swaps) {
|
|
1533
|
+
for (const swap of swaps) this.#admit(swap);
|
|
1534
|
+
this.#recomputeAll();
|
|
1535
|
+
if (this.#refreshIntervalMs > 0)
|
|
1536
|
+
this.#timer = setInterval(
|
|
1537
|
+
() => this.#recomputeAll(),
|
|
1538
|
+
this.#refreshIntervalMs
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Track one more swap after {@link startTracking}. Idempotent: a swap already
|
|
1543
|
+
* tracked, or one already seen through to a terminal action, is ignored.
|
|
1544
|
+
*/
|
|
1545
|
+
async track(swap) {
|
|
1546
|
+
if (this.#swaps.has(swap.swapId) || this.#lastActions.has(swap.swapId))
|
|
1547
|
+
return;
|
|
1548
|
+
this.#admit(swap);
|
|
1549
|
+
this.#recompute(swap);
|
|
1550
|
+
}
|
|
1551
|
+
#admit(swap) {
|
|
1552
|
+
this.#swaps.set(swap.swapId, swap);
|
|
1553
|
+
if (swap.storedStatus !== void 0 && !this.#statuses.has(swap.swapId))
|
|
1554
|
+
this.#statuses.set(swap.swapId, swap.storedStatus);
|
|
1555
|
+
}
|
|
1556
|
+
/** The swap ids currently tracked — e.g. to subscribe them to the hint feed. */
|
|
1557
|
+
trackedSwapIds() {
|
|
1558
|
+
return [...this.#swaps.keys()];
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Apply a server status to one swap and recompute its action. A hint that
|
|
1562
|
+
* carries the pushed `status` is applied as-is; without one (the worker's
|
|
1563
|
+
* retry and post-reconnect paths) the status is re-fetched from the server.
|
|
1564
|
+
* A no-op for an untracked swap.
|
|
1565
|
+
*/
|
|
1566
|
+
async applyHint(swapId, opts) {
|
|
1567
|
+
const swap = this.#swaps.get(swapId);
|
|
1568
|
+
if (!swap) return;
|
|
1569
|
+
const status = opts?.status ?? await this.#fetchStatus(swapId);
|
|
1570
|
+
if (!this.#swaps.has(swapId)) return;
|
|
1571
|
+
this.#statuses.set(swapId, status);
|
|
1572
|
+
this.#recompute(swap, opts?.force ?? false);
|
|
1573
|
+
}
|
|
1574
|
+
/** Notify `cb` of the current action for each tracked swap, then on every change. */
|
|
1575
|
+
subscribeToActions(cb) {
|
|
1576
|
+
this.#subscribers.add(cb);
|
|
1577
|
+
for (const [swapId, actions] of this.#lastActions) cb(swapId, actions);
|
|
1578
|
+
return () => this.#subscribers.delete(cb);
|
|
1579
|
+
}
|
|
1580
|
+
/** Stop tracking: drop the tick timer, tracked swaps, and subscribers. */
|
|
1581
|
+
stop() {
|
|
1582
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
1583
|
+
this.#timer = void 0;
|
|
1584
|
+
this.#swaps.clear();
|
|
1585
|
+
this.#subscribers.clear();
|
|
1586
|
+
}
|
|
1587
|
+
#recomputeAll() {
|
|
1588
|
+
for (const swap of this.#swaps.values()) this.#recompute(swap);
|
|
1589
|
+
}
|
|
1590
|
+
#recompute(swap, force = false) {
|
|
1591
|
+
const status = this.#statuses.get(swap.swapId);
|
|
1592
|
+
if (status === void 0) return;
|
|
1593
|
+
const now = Date.now();
|
|
1594
|
+
const actions = deriveSwapActions({
|
|
1595
|
+
status,
|
|
1596
|
+
clientChainNow: now,
|
|
1597
|
+
serverChainNow: now,
|
|
1598
|
+
clientRefundLocktime: swap.clientRefundLocktime,
|
|
1599
|
+
serverRefundLocktime: swap.serverRefundLocktime,
|
|
1600
|
+
// Pay-on-Lightning swaps have no client-funded on-chain leg.
|
|
1601
|
+
clientFunds: swap.clientHtlc !== void 0
|
|
1602
|
+
});
|
|
1603
|
+
const previous = this.#lastActions.get(swap.swapId);
|
|
1604
|
+
if (!force && previous && JSON.stringify(previous) === JSON.stringify(actions))
|
|
1605
|
+
return;
|
|
1606
|
+
this.#lastActions.set(swap.swapId, actions);
|
|
1607
|
+
for (const cb of this.#subscribers) cb(swap.swapId, actions);
|
|
1608
|
+
if (actions.recommended === "none") {
|
|
1609
|
+
this.#swaps.delete(swap.swapId);
|
|
1610
|
+
this.#statuses.delete(swap.swapId);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1494
1615
|
// src/hints/swap-worker.ts
|
|
1495
1616
|
var AUTO_EXECUTABLE = /* @__PURE__ */ new Set([
|
|
1496
1617
|
"claim"
|
|
@@ -1524,8 +1645,8 @@ var SwapWorker = class {
|
|
|
1524
1645
|
this.#started = true;
|
|
1525
1646
|
this.#hintSource.subscribe(this.#tracker.trackedSwapIds());
|
|
1526
1647
|
this.#unsubs.push(
|
|
1527
|
-
this.#hintSource.onStatus(({ swapId }) => {
|
|
1528
|
-
void this.#tracker.applyHint(swapId).catch((error) => {
|
|
1648
|
+
this.#hintSource.onStatus(({ swapId, status }) => {
|
|
1649
|
+
void this.#tracker.applyHint(swapId, { status }).catch((error) => {
|
|
1529
1650
|
console.warn(`SwapWorker: applyHint(${swapId}) failed:`, error);
|
|
1530
1651
|
});
|
|
1531
1652
|
})
|
|
@@ -2079,6 +2200,21 @@ function swapToTracked(stored) {
|
|
|
2079
2200
|
// no on-chain client leg
|
|
2080
2201
|
serverRefundLocktime: ms(r.vhtlc_refund_locktime)
|
|
2081
2202
|
};
|
|
2203
|
+
// Client funds the Arkade VHTLC (source_amount); the server pays the
|
|
2204
|
+
// Lightning invoice off-chain and claims the VHTLC — no server leg to
|
|
2205
|
+
// watch.
|
|
2206
|
+
case "arkade_to_lightning":
|
|
2207
|
+
return {
|
|
2208
|
+
swapId: r.id,
|
|
2209
|
+
clientHtlc: arkadeLeg(
|
|
2210
|
+
r,
|
|
2211
|
+
r.arkade_vhtlc_address,
|
|
2212
|
+
Number(r.source_amount)
|
|
2213
|
+
),
|
|
2214
|
+
clientRefundLocktime: ms(r.vhtlc_refund_locktime),
|
|
2215
|
+
serverRefundLocktime: 0
|
|
2216
|
+
// no on-chain server leg
|
|
2217
|
+
};
|
|
2082
2218
|
default:
|
|
2083
2219
|
return void 0;
|
|
2084
2220
|
}
|
|
@@ -2605,6 +2741,12 @@ var Client = class {
|
|
|
2605
2741
|
);
|
|
2606
2742
|
}
|
|
2607
2743
|
/** Delegated to the legacy client (migration checkpoint). */
|
|
2744
|
+
createArkadeToLightningSwap(...args) {
|
|
2745
|
+
return this.#trackAfterCreate(
|
|
2746
|
+
this.#legacy.createArkadeToLightningSwap(...args)
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
/** Delegated to the legacy client (migration checkpoint). */
|
|
2608
2750
|
createEvmToArkadeSwapGeneric(...args) {
|
|
2609
2751
|
return this.#trackAfterCreate(
|
|
2610
2752
|
this.#legacy.createEvmToArkadeSwapGeneric(...args)
|
|
@@ -2666,8 +2808,11 @@ var Client = class {
|
|
|
2666
2808
|
/**
|
|
2667
2809
|
* Start observing the user's active swaps and deriving each one's next action.
|
|
2668
2810
|
*
|
|
2669
|
-
* Loads stored swaps, maps
|
|
2670
|
-
*
|
|
2811
|
+
* Loads stored swaps, maps them to {@link TrackedSwap}s, and derives each
|
|
2812
|
+
* one's actions — by default from the server's status hints alone (TEMP while
|
|
2813
|
+
* the chain monitors are buggy; see
|
|
2814
|
+
* {@link ClientBuilder.withChainVerifiedTracking} to re-enable the per-ledger
|
|
2815
|
+
* chain monitors). Idempotent. Requires
|
|
2671
2816
|
* the client to have been built with {@link ClientBuilder.withTracking}.
|
|
2672
2817
|
* Subscribe with {@link subscribeToActions}; release with
|
|
2673
2818
|
* {@link stopTracking}.
|
|
@@ -2687,7 +2832,11 @@ var Client = class {
|
|
|
2687
2832
|
}
|
|
2688
2833
|
async #doStartTracking() {
|
|
2689
2834
|
try {
|
|
2690
|
-
const
|
|
2835
|
+
const chainVerified = this.#tracking.chainVerified || this.#tracking.managers !== void 0;
|
|
2836
|
+
const tracker = chainVerified ? new SwapTracker(await this.#ensureManagers(), {
|
|
2837
|
+
refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
|
|
2838
|
+
}) : new HintTracker({
|
|
2839
|
+
fetchStatus: async (swapId) => (await this.getSwap(swapId, { updateStorage: true })).status,
|
|
2691
2840
|
refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
|
|
2692
2841
|
});
|
|
2693
2842
|
this.#tracker = tracker;
|
|
@@ -2736,7 +2885,9 @@ var Client = class {
|
|
|
2736
2885
|
const swaps = await this.listAllSwaps();
|
|
2737
2886
|
return swaps.filter((s) => !SETTLED_STORED_STATUSES.has(s.response.status)).map((s) => {
|
|
2738
2887
|
try {
|
|
2739
|
-
|
|
2888
|
+
const tracked = swapToTracked(s);
|
|
2889
|
+
if (!tracked) return void 0;
|
|
2890
|
+
return { ...tracked, storedStatus: s.response.status };
|
|
2740
2891
|
} catch (error) {
|
|
2741
2892
|
console.warn(
|
|
2742
2893
|
`Client: skipping untrackable stored swap ${s.response.id}:`,
|
|
@@ -2814,7 +2965,7 @@ var Client = class {
|
|
|
2814
2965
|
* otherwise it runs observe-only, hints in, no spends.
|
|
2815
2966
|
*/
|
|
2816
2967
|
#startWorker(tracker) {
|
|
2817
|
-
const serverUrl = this.#tracking.serverUrl;
|
|
2968
|
+
const serverUrl = this.#tracking.serverUrl ?? legacyBaseUrl(this.#legacy);
|
|
2818
2969
|
if (!serverUrl) return;
|
|
2819
2970
|
const autoClaim = this.#tracking.autoClaim;
|
|
2820
2971
|
const hintSource = new WsStatusSource({ serverUrl });
|
|
@@ -2912,6 +3063,7 @@ var ClientBuilder = class {
|
|
|
2912
3063
|
#evmRpcUrls;
|
|
2913
3064
|
#managers;
|
|
2914
3065
|
#autoClaim;
|
|
3066
|
+
#chainVerified = false;
|
|
2915
3067
|
/**
|
|
2916
3068
|
* Override the EVM JSON-RPC endpoint per chainId. Optional — tracking uses
|
|
2917
3069
|
* tested public defaults otherwise; an override is tried first, with the
|
|
@@ -2938,6 +3090,16 @@ var ClientBuilder = class {
|
|
|
2938
3090
|
this.#trackingEnabled = false;
|
|
2939
3091
|
return this;
|
|
2940
3092
|
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Verify swap state against the chain (per-ledger contract managers) instead
|
|
3095
|
+
* of trusting the server's status hints. TEMPORARILY off by default while the
|
|
3096
|
+
* chain monitors are being fixed — without it, tracking derives actions from
|
|
3097
|
+
* the server's pushed status alone, with zero chain access.
|
|
3098
|
+
*/
|
|
3099
|
+
withChainVerifiedTracking() {
|
|
3100
|
+
this.#chainVerified = true;
|
|
3101
|
+
return this;
|
|
3102
|
+
}
|
|
2941
3103
|
/**
|
|
2942
3104
|
* Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
|
|
2943
3105
|
* letting the client auto-build them from config (useful for tests or custom
|
|
@@ -3055,10 +3217,18 @@ var ClientBuilder = class {
|
|
|
3055
3217
|
bitcoinNetwork: this.#bitcoinNetwork,
|
|
3056
3218
|
bitcoinMinConfirmations: this.#bitcoinMinConfirmations,
|
|
3057
3219
|
evmRpcUrls: this.#evmRpcUrls,
|
|
3058
|
-
autoClaim: this.#autoClaim
|
|
3220
|
+
autoClaim: this.#autoClaim,
|
|
3221
|
+
chainVerified: this.#chainVerified
|
|
3059
3222
|
});
|
|
3060
3223
|
}
|
|
3061
3224
|
};
|
|
3225
|
+
function legacyBaseUrl(legacy) {
|
|
3226
|
+
try {
|
|
3227
|
+
return legacy.baseUrl;
|
|
3228
|
+
} catch {
|
|
3229
|
+
return void 0;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3062
3232
|
var SETTLED_STORED_STATUSES = /* @__PURE__ */ new Set([
|
|
3063
3233
|
"serverredeemed",
|
|
3064
3234
|
"clientrefunded",
|
|
@@ -3084,6 +3254,7 @@ init_bitcoin_reader_esplora();
|
|
|
3084
3254
|
DEFAULT_ESPLORA_URLS,
|
|
3085
3255
|
DEFAULT_EVM_RPCS,
|
|
3086
3256
|
EvmContractManager,
|
|
3257
|
+
HintTracker,
|
|
3087
3258
|
SwapTracker,
|
|
3088
3259
|
SwapWorker,
|
|
3089
3260
|
WsStatusSource,
|