@satora/swap 0.3.2 → 1.1.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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __esm = (fn, res) => function __init() {
7
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -19,6 +21,14 @@ var __copyProps = (to, from, except, desc) => {
19
21
  return to;
20
22
  };
21
23
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
22
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
33
 
24
34
  // src/contracts/bitcoin-reader-esplora.ts
@@ -41,11 +51,17 @@ function htlcFactsFromEsploraTxs(txs, address, opts) {
41
51
  spendWitness: spend.witness ?? []
42
52
  };
43
53
  }
44
- const funding = txs.find(
45
- (tx) => tx.vout.some((vout) => vout.scriptpubkey_address === address)
46
- );
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
+ }
47
64
  if (funding) {
48
- const fundedSats = funding.vout.filter((vout) => vout.scriptpubkey_address === address).reduce((sum, vout) => sum + (vout.value ?? 0), 0);
49
65
  const minConf = opts?.minConfirmations ?? 0;
50
66
  let deepEnough;
51
67
  if (funding.status?.confirmed) {
@@ -108,6 +124,164 @@ var init_bitcoin_reader_esplora = __esm({
108
124
  }
109
125
  });
110
126
 
127
+ // src/contracts/bitcoin-reader-electrum.ts
128
+ var bitcoin_reader_electrum_exports = {};
129
+ __export(bitcoin_reader_electrum_exports, {
130
+ electrumReader: () => electrumReader
131
+ });
132
+ function toBtcSignerNetwork(network) {
133
+ switch (network) {
134
+ case "mainnet":
135
+ return btc.NETWORK;
136
+ case "testnet":
137
+ case "signet":
138
+ return btc.TEST_NETWORK;
139
+ case "regtest":
140
+ return REGTEST_NETWORK;
141
+ }
142
+ }
143
+ function scriptHashOf(script) {
144
+ return import_base5.hex.encode((0, import_sha22.sha256)(script).reverse());
145
+ }
146
+ function electrumReader(client, opts) {
147
+ const network = opts?.network ?? "mainnet";
148
+ const defaultMinConf = opts?.minConfirmations ?? 0;
149
+ const fallback2 = opts?.fallback;
150
+ const scriptFor = (address) => btc.OutScript.encode(
151
+ btc.Address(toBtcSignerNetwork(network)).decode(address)
152
+ );
153
+ async function getHtlcFactsElectrum(address, required) {
154
+ const script = scriptFor(address);
155
+ const scripthash = scriptHashOf(script);
156
+ const history = await client.request(
157
+ "blockchain.scripthash.get_history",
158
+ [scripthash]
159
+ );
160
+ if (!history || history.length === 0)
161
+ return { funding: "absent", fundedSats: 0 };
162
+ const txs = await Promise.all(
163
+ history.map(async (entry) => {
164
+ const raw = await client.request("blockchain.transaction.get", [
165
+ entry.tx_hash
166
+ ]);
167
+ return {
168
+ entry,
169
+ tx: btc.Transaction.fromRaw(import_base5.hex.decode(raw), {
170
+ allowUnknownOutputs: true,
171
+ allowUnknownInputs: true,
172
+ disableScriptCheck: true
173
+ })
174
+ };
175
+ })
176
+ );
177
+ const scriptHex = import_base5.hex.encode(script);
178
+ const outputIndicesPaying = (tx) => {
179
+ const indices = [];
180
+ for (let i = 0; i < tx.outputsLength; i++) {
181
+ const out = tx.getOutput(i);
182
+ if (out.script && import_base5.hex.encode(out.script) === scriptHex) indices.push(i);
183
+ }
184
+ return indices;
185
+ };
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
+ }
197
+ if (!funding) return { funding: "absent", fundedSats: 0 };
198
+ const fundedVouts = outputIndicesPaying(funding.tx);
199
+ for (const { tx } of txs) {
200
+ for (let i = 0; i < tx.inputsLength; i++) {
201
+ const input = tx.getInput(i);
202
+ if (input.txid && import_base5.hex.encode(input.txid) === funding.entry.tx_hash && input.index !== void 0 && fundedVouts.includes(input.index)) {
203
+ return {
204
+ funding: "confirmed",
205
+ fundedSats: 0,
206
+ // already spent — the amount no longer matters
207
+ spendWitness: (input.finalScriptWitness ?? []).map(
208
+ (item) => import_base5.hex.encode(item)
209
+ )
210
+ };
211
+ }
212
+ }
213
+ }
214
+ const height = funding.entry.height;
215
+ const isConfirmed = height > 0;
216
+ let deepEnough;
217
+ if (isConfirmed) {
218
+ if (required <= 1) {
219
+ deepEnough = true;
220
+ } else {
221
+ const tip = await client.request(
222
+ "blockchain.headers.subscribe",
223
+ []
224
+ );
225
+ deepEnough = tip.height - height + 1 >= required;
226
+ }
227
+ } else {
228
+ deepEnough = required === 0;
229
+ }
230
+ return { funding: deepEnough ? "confirmed" : "mempool", fundedSats };
231
+ }
232
+ return {
233
+ async getHtlcFacts(address, minConfirmations) {
234
+ const required = minConfirmations ?? defaultMinConf;
235
+ try {
236
+ return await getHtlcFactsElectrum(address, required);
237
+ } catch (error) {
238
+ if (!fallback2) throw error;
239
+ console.warn(
240
+ "electrumReader: falling back to secondary reader:",
241
+ error instanceof Error ? error.message : error
242
+ );
243
+ return fallback2.getHtlcFacts(address, minConfirmations);
244
+ }
245
+ },
246
+ subscribe(address, onChange) {
247
+ let scripthash;
248
+ try {
249
+ scripthash = scriptHashOf(scriptFor(address));
250
+ } catch (error) {
251
+ console.warn(
252
+ "electrumReader: subscribe skipped (address decode failed):",
253
+ error instanceof Error ? error.message : error
254
+ );
255
+ return () => {
256
+ };
257
+ }
258
+ const handler = () => onChange();
259
+ client.subscribeScriptHash(scripthash, handler).catch((error) => {
260
+ console.warn(
261
+ "electrumReader: scripthash subscribe failed:",
262
+ error instanceof Error ? error.message : error
263
+ );
264
+ });
265
+ return () => client.unsubscribeScriptHash(scripthash, handler);
266
+ }
267
+ };
268
+ }
269
+ var import_sha22, import_base5, btc, REGTEST_NETWORK;
270
+ var init_bitcoin_reader_electrum = __esm({
271
+ "src/contracts/bitcoin-reader-electrum.ts"() {
272
+ "use strict";
273
+ import_sha22 = require("@noble/hashes/sha2.js");
274
+ import_base5 = require("@scure/base");
275
+ btc = __toESM(require("@scure/btc-signer"), 1);
276
+ REGTEST_NETWORK = {
277
+ bech32: "bcrt",
278
+ pubKeyHash: 111,
279
+ scriptHash: 196,
280
+ wif: 239
281
+ };
282
+ }
283
+ });
284
+
111
285
  // src/index.ts
112
286
  var src_exports = {};
113
287
  __export(src_exports, {
@@ -127,6 +301,7 @@ __export(src_exports, {
127
301
  defaultEvmReaders: () => defaultEvmReaders,
128
302
  deriveSwapActions: () => deriveSwapActions,
129
303
  deriveSwapStatus: () => deriveSwapStatus,
304
+ electrumReader: () => electrumReader,
130
305
  esploraReader: () => esploraReader,
131
306
  evmReaderFromClient: () => evmReaderFromClient,
132
307
  htlcFactsFromEsploraTxs: () => htlcFactsFromEsploraTxs,
@@ -468,10 +643,12 @@ function arkadeObservation(facts) {
468
643
  }
469
644
  function classifyArkadeSpend(encodedSpendTx, paymentHash) {
470
645
  const tx = import_sdk.Transaction.fromPSBT(import_base2.base64.decode(encodedSpendTx));
471
- for (const witness of (0, import_sdk.getArkPsbtFields)(tx, 0, import_sdk.ConditionWitness)) {
472
- for (const element of witness) {
473
- if (element.length === 32 && preimageMatches(element, paymentHash)) {
474
- return { spend: "claim", preimage: element };
646
+ for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
647
+ for (const witness of (0, import_sdk.getArkPsbtFields)(tx, inputIndex, import_sdk.ConditionWitness)) {
648
+ for (const element of witness) {
649
+ if (element.length === 32 && preimageMatches(element, paymentHash)) {
650
+ return { spend: "claim", preimage: element };
651
+ }
475
652
  }
476
653
  }
477
654
  }
@@ -640,7 +817,7 @@ function defaultArkadeServerUrl(network) {
640
817
  }
641
818
 
642
819
  // src/contracts/bitcoin-manager.ts
643
- var import_base5 = require("@scure/base");
820
+ var import_base6 = require("@scure/base");
644
821
 
645
822
  // src/contracts/bitcoin.ts
646
823
  var import_base4 = require("@scure/base");
@@ -675,6 +852,8 @@ var BitcoinContractManager = class _BitcoinContractManager {
675
852
  #obs = /* @__PURE__ */ new Map();
676
853
  /** address → the preimage a claim revealed. */
677
854
  #preimages = /* @__PURE__ */ new Map();
855
+ /** address → push-subscription teardown (only when the reader supports push). */
856
+ #subscriptions = /* @__PURE__ */ new Map();
678
857
  #listeners = /* @__PURE__ */ new Set();
679
858
  /** Last MTP reading (ms) + when it was fetched, for extrapolation. */
680
859
  #now;
@@ -687,10 +866,24 @@ var BitcoinContractManager = class _BitcoinContractManager {
687
866
  }
688
867
  static async create(config) {
689
868
  const { esploraReader: esploraReader2 } = await Promise.resolve().then(() => (init_bitcoin_reader_esplora(), bitcoin_reader_esplora_exports));
869
+ let reader = esploraReader2(
870
+ config.esploraUrl,
871
+ void 0,
872
+ { minConfirmations: config.minConfirmations }
873
+ );
874
+ if (config.electrumWsUrl) {
875
+ const [{ electrumReader: electrumReader2 }, { ElectrumWsClient }] = await Promise.all([
876
+ Promise.resolve().then(() => (init_bitcoin_reader_electrum(), bitcoin_reader_electrum_exports)),
877
+ import("@lendasat/lendaswap-sdk-pure")
878
+ ]);
879
+ reader = electrumReader2(new ElectrumWsClient(config.electrumWsUrl), {
880
+ network: config.network,
881
+ minConfirmations: config.minConfirmations,
882
+ fallback: reader
883
+ });
884
+ }
690
885
  return _BitcoinContractManager.fromDeps({
691
- reader: esploraReader2(config.esploraUrl, void 0, {
692
- minConfirmations: config.minConfirmations
693
- }),
886
+ reader,
694
887
  chainTime: config.chainTime
695
888
  });
696
889
  }
@@ -703,10 +896,25 @@ var BitcoinContractManager = class _BitcoinContractManager {
703
896
  `BitcoinContractManager can't track a '${ref.ledger}' HTLC`
704
897
  );
705
898
  this.#refs.set(ref.address, ref);
899
+ if (this.#reader.subscribe && !this.#subscriptions.has(ref.address)) {
900
+ this.#subscriptions.set(
901
+ ref.address,
902
+ this.#reader.subscribe(ref.address, () => {
903
+ this.#reconcileRef(ref).catch((error) => {
904
+ console.warn(
905
+ "BitcoinContractManager: push reconcile failed:",
906
+ error instanceof Error ? error.message : error
907
+ );
908
+ });
909
+ })
910
+ );
911
+ }
706
912
  await this.#reconcileRef(ref);
707
913
  }
708
914
  async unregister(ref) {
709
915
  if (ref.ledger !== "bitcoin") return;
916
+ this.#subscriptions.get(ref.address)?.();
917
+ this.#subscriptions.delete(ref.address);
710
918
  this.#refs.delete(ref.address);
711
919
  this.#obs.delete(ref.address);
712
920
  this.#preimages.delete(ref.address);
@@ -741,6 +949,8 @@ var BitcoinContractManager = class _BitcoinContractManager {
741
949
  this.#now = { mtpMs: await this.#chainTime(), fetchedAtMs: Date.now() };
742
950
  }
743
951
  dispose() {
952
+ for (const unsubscribe of this.#subscriptions.values()) unsubscribe();
953
+ this.#subscriptions.clear();
744
954
  this.#listeners.clear();
745
955
  }
746
956
  /** The preimage a claim revealed on this HTLC, if one was seen. */
@@ -754,7 +964,7 @@ var BitcoinContractManager = class _BitcoinContractManager {
754
964
  );
755
965
  const { observation, preimage } = bitcoinObservation(
756
966
  facts,
757
- import_base5.hex.decode(ref.preimageHash),
967
+ import_base6.hex.decode(ref.preimageHash),
758
968
  ref.expectedSats
759
969
  );
760
970
  if (preimage) this.#preimages.set(ref.address, preimage);
@@ -1639,19 +1849,19 @@ function defaultWebSocketFactory(url) {
1639
1849
  var import_sdk3 = require("@arkade-os/sdk");
1640
1850
  var import_lendaswap_sdk_pure = require("@lendasat/lendaswap-sdk-pure");
1641
1851
  var import_legacy2 = require("@noble/hashes/legacy.js");
1642
- var import_base6 = require("@scure/base");
1852
+ var import_base7 = require("@scure/base");
1643
1853
  function strip0x(value) {
1644
1854
  return value.startsWith("0x") ? value.slice(2) : value;
1645
1855
  }
1646
1856
  function xOnly(pubKeyHex) {
1647
- const bytes = import_base6.hex.decode(strip0x(pubKeyHex));
1857
+ const bytes = import_base7.hex.decode(strip0x(pubKeyHex));
1648
1858
  if (bytes.length === 33) return bytes.slice(1);
1649
1859
  if (bytes.length === 32) return bytes;
1650
1860
  throw new Error(`invalid public key length: ${bytes.length}`);
1651
1861
  }
1652
1862
  var seconds = (value) => ({ type: "seconds", value: BigInt(value) });
1653
1863
  function buildArkadeVhtlcRef(input) {
1654
- const hashLockBytes = import_base6.hex.decode(strip0x(input.hashLock));
1864
+ const hashLockBytes = import_base7.hex.decode(strip0x(input.hashLock));
1655
1865
  const params = {
1656
1866
  sender: xOnly(input.senderPk),
1657
1867
  receiver: xOnly(input.receiverPk),
@@ -1664,21 +1874,15 @@ function buildArkadeVhtlcRef(input) {
1664
1874
  input.unilateralRefundWithoutReceiverDelay
1665
1875
  )
1666
1876
  };
1667
- const vhtlc = (() => {
1668
- switch (input.arkadeHtlcScriptVersion ?? import_lendaswap_sdk_pure.ARKADE_HTLC_SCRIPT_VERSION_LEGACY) {
1669
- case import_lendaswap_sdk_pure.ARKADE_HTLC_SCRIPT_VERSION_LEGACY:
1670
- return new import_sdk3.VHTLC.Script(params);
1671
- case import_lendaswap_sdk_pure.ARKADE_HTLC_SCRIPT_VERSION_STRICT:
1672
- return new import_lendaswap_sdk_pure.StrictVhtlcScript(params);
1673
- default:
1674
- throw new Error(
1675
- `Unsupported Arkade HTLC script version: ${input.arkadeHtlcScriptVersion}`
1676
- );
1677
- }
1678
- })();
1877
+ if (input.arkadeHtlcScriptVersion !== void 0 && input.arkadeHtlcScriptVersion !== import_lendaswap_sdk_pure.ARKADE_HTLC_SCRIPT_VERSION_STRICT) {
1878
+ throw new Error(
1879
+ `Unsupported Arkade HTLC script version: ${input.arkadeHtlcScriptVersion}`
1880
+ );
1881
+ }
1882
+ const vhtlc = new import_lendaswap_sdk_pure.StrictVhtlcScript(params);
1679
1883
  return {
1680
1884
  ledger: "arkade",
1681
- script: import_base6.hex.encode(vhtlc.pkScript),
1885
+ script: import_base7.hex.encode(vhtlc.pkScript),
1682
1886
  address: input.address,
1683
1887
  preimageHash: strip0x(input.hashLock),
1684
1888
  expectedSats: input.expectedSats,
@@ -1872,47 +2076,6 @@ function swapToTracked(stored) {
1872
2076
  serverRefundLocktime: ms(r.vhtlc_refund_locktime)
1873
2077
  };
1874
2078
  // ─── Lightning: one on-chain leg, one off-chain LN payment ───────────────
1875
- // Receive-on-Lightning: the client funds an on-chain HTLC and is paid over
1876
- // Lightning. No server leg to watch — the server sweeping the client's leg
1877
- // (with the preimage it got by paying the invoice) is the done signal.
1878
- // Client funds the Arkade VHTLC; Boltz pays the invoice. The deposit is
1879
- // `boltz_amount_sats` (source minus fees) — the amount actually locked, not
1880
- // `source_amount` — else the client's own funding reads as under-funded.
1881
- case "arkade_to_lightning":
1882
- return {
1883
- swapId: r.id,
1884
- clientHtlc: arkadeLeg(
1885
- r,
1886
- r.arkade_vhtlc_address,
1887
- Number(r.boltz_amount_sats)
1888
- ),
1889
- clientRefundLocktime: ms(r.vhtlc_refund_locktime),
1890
- serverRefundLocktime: 0
1891
- // no on-chain server leg
1892
- };
1893
- // Client funds the EVM HTLC (server claims it after paying the invoice).
1894
- case "evm_to_lightning":
1895
- return {
1896
- swapId: r.id,
1897
- clientHtlc: evmLeg({
1898
- chainId: r.evm_chain_id,
1899
- htlc: r.evm_htlc_address,
1900
- hashLock: r.hash_lock,
1901
- claimAddress: r.server_evm_address,
1902
- // the server claims the client's leg
1903
- expectedSats: r.evm_expected_sats,
1904
- // evm_to_lightning doesn't expose the locked token — fall back to the
1905
- // per-chain mainnet constant so the isActive tuple is complete.
1906
- token: lockedTokenFallback(r.evm_chain_id, r.network),
1907
- sender: r.client_evm_address,
1908
- // the client funded it
1909
- timelockSec: r.evm_refund_locktime,
1910
- createdAt: r.created_at
1911
- }),
1912
- clientRefundLocktime: ms(r.evm_refund_locktime),
1913
- serverRefundLocktime: 0
1914
- // no on-chain server leg
1915
- };
1916
2079
  // Pay-on-Lightning: the client pays a Lightning invoice (off-chain, nothing to
1917
2080
  // watch) and claims an on-chain HTLC. No client-funded leg — the client's
1918
2081
  // claim completes the swap; a hold invoice that never settles auto-unwinds.
@@ -1929,26 +2092,20 @@ function swapToTracked(stored) {
1929
2092
  // no on-chain client leg
1930
2093
  serverRefundLocktime: ms(r.vhtlc_refund_locktime)
1931
2094
  };
1932
- // Client pays the invoice, then claims the EVM HTLC.
1933
- case "lightning_to_evm":
2095
+ // Client funds the Arkade VHTLC (source_amount); the server pays the
2096
+ // Lightning invoice off-chain and claims the VHTLC — no server leg to
2097
+ // watch.
2098
+ case "arkade_to_lightning":
1934
2099
  return {
1935
2100
  swapId: r.id,
1936
- serverHtlc: evmLeg({
1937
- chainId: r.evm_chain_id,
1938
- htlc: r.evm_htlc_address,
1939
- hashLock: r.hash_lock,
1940
- claimAddress: r.client_evm_address,
1941
- // the client claims the server's leg
1942
- expectedSats: r.evm_expected_sats,
1943
- token: r.wbtc_address,
1944
- sender: r.server_evm_address,
1945
- // the server funded it
1946
- timelockSec: r.evm_refund_locktime,
1947
- createdAt: r.created_at
1948
- }),
1949
- clientRefundLocktime: 0,
1950
- // no on-chain client leg
1951
- serverRefundLocktime: ms(r.evm_refund_locktime)
2101
+ clientHtlc: arkadeLeg(
2102
+ r,
2103
+ r.arkade_vhtlc_address,
2104
+ Number(r.source_amount)
2105
+ ),
2106
+ clientRefundLocktime: ms(r.vhtlc_refund_locktime),
2107
+ serverRefundLocktime: 0
2108
+ // no on-chain server leg
1952
2109
  };
1953
2110
  default:
1954
2111
  return void 0;
@@ -2460,12 +2617,6 @@ var Client = class {
2460
2617
  );
2461
2618
  }
2462
2619
  /** Delegated to the legacy client (migration checkpoint). */
2463
- createLightningToEvmSwapGeneric(...args) {
2464
- return this.#trackAfterCreate(
2465
- this.#legacy.createLightningToEvmSwapGeneric(...args)
2466
- );
2467
- }
2468
- /** Delegated to the legacy client (migration checkpoint). */
2469
2620
  createBitcoinToEvmSwap(...args) {
2470
2621
  return this.#trackAfterCreate(this.#legacy.createBitcoinToEvmSwap(...args));
2471
2622
  }
@@ -2488,16 +2639,6 @@ var Client = class {
2488
2639
  );
2489
2640
  }
2490
2641
  /** Delegated to the legacy client (migration checkpoint). */
2491
- getArkadeToLightningQuote(...args) {
2492
- return this.#legacy.getArkadeToLightningQuote(...args);
2493
- }
2494
- /** Delegated to the legacy client (migration checkpoint). */
2495
- retryArkadeToLightningSwap(...args) {
2496
- return this.#trackAfterRetry(
2497
- this.#legacy.retryArkadeToLightningSwap(...args)
2498
- );
2499
- }
2500
- /** Delegated to the legacy client (migration checkpoint). */
2501
2642
  createEvmToArkadeSwapGeneric(...args) {
2502
2643
  return this.#trackAfterCreate(
2503
2644
  this.#legacy.createEvmToArkadeSwapGeneric(...args)
@@ -2508,12 +2649,6 @@ var Client = class {
2508
2649
  return this.#trackAfterCreate(this.#legacy.createEvmToBitcoinSwap(...args));
2509
2650
  }
2510
2651
  /** Delegated to the legacy client (migration checkpoint). */
2511
- createEvmToLightningSwapGeneric(...args) {
2512
- return this.#trackAfterCreate(
2513
- this.#legacy.createEvmToLightningSwapGeneric(...args)
2514
- );
2515
- }
2516
- /** Delegated to the legacy client (migration checkpoint). */
2517
2652
  getCoordinatorFundingCallDataPermit2(...args) {
2518
2653
  return this.#legacy.getCoordinatorFundingCallDataPermit2(...args);
2519
2654
  }
@@ -2633,7 +2768,17 @@ var Client = class {
2633
2768
  /** Every stored swap whose ledgers are observable, mapped to a {@link TrackedSwap}. */
2634
2769
  async #loadTrackedSwaps() {
2635
2770
  const swaps = await this.listAllSwaps();
2636
- return swaps.filter((s) => !SETTLED_STORED_STATUSES.has(s.response.status)).map(swapToTracked).filter((s) => s !== void 0);
2771
+ return swaps.filter((s) => !SETTLED_STORED_STATUSES.has(s.response.status)).map((s) => {
2772
+ try {
2773
+ return swapToTracked(s);
2774
+ } catch (error) {
2775
+ console.warn(
2776
+ `Client: skipping untrackable stored swap ${s.response.id}:`,
2777
+ error
2778
+ );
2779
+ return void 0;
2780
+ }
2781
+ }).filter((s) => s !== void 0);
2637
2782
  }
2638
2783
  /**
2639
2784
  * Run a create, then fold the new swap into tracking.
@@ -2753,7 +2898,14 @@ var Client = class {
2753
2898
  return this.#managers;
2754
2899
  }
2755
2900
  const managers = /* @__PURE__ */ new Map();
2756
- const { arkadeServerUrl, evmRpcUrls, esploraUrl, bitcoinMinConfirmations } = this.#tracking;
2901
+ const {
2902
+ arkadeServerUrl,
2903
+ evmRpcUrls,
2904
+ esploraUrl,
2905
+ electrumWsUrl,
2906
+ bitcoinNetwork,
2907
+ bitcoinMinConfirmations
2908
+ } = this.#tracking;
2757
2909
  const chainTime = async () => (await this.getMtp()).mtp * 1e3;
2758
2910
  const arkadeUrl = arkadeServerUrl ?? defaultArkadeServerUrl("bitcoin");
2759
2911
  if (arkadeUrl) {
@@ -2765,10 +2917,13 @@ var Client = class {
2765
2917
  const readers = defaultEvmReaders(evmRpcUrls);
2766
2918
  if (readers.size > 0)
2767
2919
  managers.set("evm", EvmContractManager.fromDeps({ readers }));
2920
+ const network = bitcoinNetwork ?? "mainnet";
2768
2921
  managers.set(
2769
2922
  "bitcoin",
2770
2923
  await BitcoinContractManager.create({
2771
2924
  esploraUrl: esploraUrl ? [esploraUrl] : DEFAULT_ESPLORA_URLS,
2925
+ electrumWsUrl: electrumWsUrl ?? import_lendaswap_sdk_pure2.DEFAULT_ELECTRUM_WS_URLS[network],
2926
+ network,
2772
2927
  chainTime,
2773
2928
  minConfirmations: bitcoinMinConfirmations
2774
2929
  })
@@ -2785,6 +2940,8 @@ var ClientBuilder = class {
2785
2940
  #serverUrl;
2786
2941
  #arkadeServerUrl;
2787
2942
  #esploraUrl;
2943
+ #electrumWsUrl;
2944
+ #bitcoinNetwork;
2788
2945
  #bitcoinMinConfirmations;
2789
2946
  #evmRpcUrls;
2790
2947
  #managers;
@@ -2850,6 +3007,28 @@ var ClientBuilder = class {
2850
3007
  this.#esploraUrl = url;
2851
3008
  return this;
2852
3009
  }
3010
+ /**
3011
+ * Set an Electrum server WebSocket URL (a Fulcrum `ws`/`wss` port) for
3012
+ * Bitcoin claim/refund chain access. When set, the underlying client
3013
+ * prefers it over Esplora for lookups and broadcasts, and waits for HTLC
3014
+ * fundings via `blockchain.scripthash.subscribe` push instead of polling.
3015
+ * Esplora remains the fallback whenever the Electrum server errors.
3016
+ */
3017
+ withElectrumWsUrl(url) {
3018
+ this.#inner.withElectrumWsUrl(url);
3019
+ this.#electrumWsUrl = url;
3020
+ return this;
3021
+ }
3022
+ /**
3023
+ * Declare the Bitcoin network of this deployment's HTLC addresses. Only
3024
+ * needed together with {@link withElectrumWsUrl} on a non-mainnet
3025
+ * deployment — the Electrum reader decodes addresses to scripthashes and
3026
+ * defaults to mainnet parameters.
3027
+ */
3028
+ withBitcoinNetwork(network) {
3029
+ this.#bitcoinNetwork = network;
3030
+ return this;
3031
+ }
2853
3032
  /**
2854
3033
  * Confirmations a Bitcoin funding tx needs before it observes as `confirmed`
2855
3034
  * (gating e.g. the evm→bitcoin claim). Default `0`: claim as soon as the
@@ -2906,6 +3085,8 @@ var ClientBuilder = class {
2906
3085
  managers: this.#managers,
2907
3086
  arkadeServerUrl: this.#arkadeServerUrl,
2908
3087
  esploraUrl: this.#esploraUrl,
3088
+ electrumWsUrl: this.#electrumWsUrl,
3089
+ bitcoinNetwork: this.#bitcoinNetwork,
2909
3090
  bitcoinMinConfirmations: this.#bitcoinMinConfirmations,
2910
3091
  evmRpcUrls: this.#evmRpcUrls,
2911
3092
  autoClaim: this.#autoClaim
@@ -2926,6 +3107,7 @@ function createdSwapId(result) {
2926
3107
  }
2927
3108
 
2928
3109
  // src/index.ts
3110
+ init_bitcoin_reader_electrum();
2929
3111
  init_bitcoin_reader_esplora();
2930
3112
  // Annotate the CommonJS export names for ESM import in node:
2931
3113
  0 && (module.exports = {
@@ -2945,6 +3127,7 @@ init_bitcoin_reader_esplora();
2945
3127
  defaultEvmReaders,
2946
3128
  deriveSwapActions,
2947
3129
  deriveSwapStatus,
3130
+ electrumReader,
2948
3131
  esploraReader,
2949
3132
  evmReaderFromClient,
2950
3133
  htlcFactsFromEsploraTxs,