@satora/swap 1.4.0 → 1.5.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/bitcoin-reader-electrum-Z2YCNDLB.js +8 -0
- package/dist/bitcoin-reader-esplora-B2BDEI6Z.js +13 -0
- package/dist/{chunk-FTPIQSRW.js → chunk-INWVLX4A.js} +6 -3
- package/dist/chunk-INWVLX4A.js.map +1 -0
- package/dist/{chunk-THI4UO6R.js → chunk-LSCRTQOS.js} +6 -3
- package/dist/chunk-LSCRTQOS.js.map +1 -0
- package/dist/index.cjs +197 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -71
- package/dist/index.d.ts +103 -71
- package/dist/index.js +196 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/bitcoin-reader-electrum-PY7DHD6T.js +0 -7
- package/dist/bitcoin-reader-esplora-ZLPMM5FF.js +0 -11
- package/dist/chunk-FTPIQSRW.js.map +0 -1
- package/dist/chunk-THI4UO6R.js.map +0 -1
- /package/dist/{bitcoin-reader-electrum-PY7DHD6T.js.map → bitcoin-reader-electrum-Z2YCNDLB.js.map} +0 -0
- /package/dist/{bitcoin-reader-esplora-ZLPMM5FF.js.map → bitcoin-reader-esplora-B2BDEI6Z.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
electrumReader
|
|
3
|
+
} from "./chunk-INWVLX4A.js";
|
|
1
4
|
import {
|
|
2
5
|
DEFAULT_ESPLORA_URLS,
|
|
3
6
|
esploraReader,
|
|
4
|
-
htlcFactsFromEsploraTxs
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
electrumReader
|
|
8
|
-
} from "./chunk-FTPIQSRW.js";
|
|
7
|
+
htlcFactsFromEsploraTxs,
|
|
8
|
+
resolveMinConfirmations
|
|
9
|
+
} from "./chunk-LSCRTQOS.js";
|
|
9
10
|
|
|
10
11
|
// src/index.ts
|
|
11
12
|
export * from "@lendasat/lendaswap-sdk-pure";
|
|
@@ -621,7 +622,7 @@ var BitcoinContractManager = class _BitcoinContractManager {
|
|
|
621
622
|
return new _BitcoinContractManager(deps);
|
|
622
623
|
}
|
|
623
624
|
static async create(config) {
|
|
624
|
-
const { esploraReader: esploraReader2 } = await import("./bitcoin-reader-esplora-
|
|
625
|
+
const { esploraReader: esploraReader2 } = await import("./bitcoin-reader-esplora-B2BDEI6Z.js");
|
|
625
626
|
let reader = esploraReader2(
|
|
626
627
|
config.esploraUrl,
|
|
627
628
|
void 0,
|
|
@@ -629,7 +630,7 @@ var BitcoinContractManager = class _BitcoinContractManager {
|
|
|
629
630
|
);
|
|
630
631
|
if (config.electrumWsUrl) {
|
|
631
632
|
const [{ electrumReader: electrumReader2 }, { ElectrumWsClient }] = await Promise.all([
|
|
632
|
-
import("./bitcoin-reader-electrum-
|
|
633
|
+
import("./bitcoin-reader-electrum-Z2YCNDLB.js"),
|
|
633
634
|
import("@lendasat/lendaswap-sdk-pure")
|
|
634
635
|
]);
|
|
635
636
|
reader = electrumReader2(new ElectrumWsClient(config.electrumWsUrl), {
|
|
@@ -813,7 +814,8 @@ function refQueryKey(ref) {
|
|
|
813
814
|
var AVG_BLOCK_MS = {
|
|
814
815
|
1: 12e3,
|
|
815
816
|
137: 2100,
|
|
816
|
-
42161: 250
|
|
817
|
+
42161: 250,
|
|
818
|
+
30: 3e4
|
|
817
819
|
};
|
|
818
820
|
var SCAN_MARGIN_BLOCKS = 1000n;
|
|
819
821
|
var EvmContractManager = class _EvmContractManager {
|
|
@@ -1079,7 +1081,9 @@ var DEFAULT_EVM_RPCS = {
|
|
|
1079
1081
|
"https://arb1.arbitrum.io/rpc",
|
|
1080
1082
|
"https://arbitrum.drpc.org",
|
|
1081
1083
|
"https://arbitrum-one-rpc.publicnode.com"
|
|
1082
|
-
]
|
|
1084
|
+
],
|
|
1085
|
+
// Rootstock: the public node, HTTP only (no WebSocket endpoint exists).
|
|
1086
|
+
30: ["https://public-node.rsk.co"]
|
|
1083
1087
|
};
|
|
1084
1088
|
var SWAP_CREATED = parseAbiItem(
|
|
1085
1089
|
"event SwapCreated(bytes32 indexed preimageHash, address indexed refundAddress, address indexed claimAddress, address token, uint256 amount, uint256 timelock, bytes32 key)"
|
|
@@ -1112,25 +1116,52 @@ function activeKey(q) {
|
|
|
1112
1116
|
}
|
|
1113
1117
|
});
|
|
1114
1118
|
}
|
|
1115
|
-
function evmReaderFromClient(client) {
|
|
1119
|
+
function evmReaderFromClient(client, options = {}) {
|
|
1120
|
+
const { maxBlockRange } = options;
|
|
1121
|
+
async function getLogs(filter, fromBlock) {
|
|
1122
|
+
if (maxBlockRange === void 0) {
|
|
1123
|
+
return client.request({
|
|
1124
|
+
method: "eth_getLogs",
|
|
1125
|
+
params: [{ ...filter, fromBlock: numberToHex(fromBlock) }]
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const latest = (await client.getBlock()).number;
|
|
1129
|
+
if (latest === null) {
|
|
1130
|
+
throw new Error("latest block has no number");
|
|
1131
|
+
}
|
|
1132
|
+
const logs = [];
|
|
1133
|
+
for (let from = fromBlock; from <= latest; from += maxBlockRange) {
|
|
1134
|
+
const to = from + maxBlockRange - 1n < latest ? from + maxBlockRange - 1n : latest;
|
|
1135
|
+
logs.push(
|
|
1136
|
+
...await client.request({
|
|
1137
|
+
method: "eth_getLogs",
|
|
1138
|
+
params: [
|
|
1139
|
+
{
|
|
1140
|
+
...filter,
|
|
1141
|
+
fromBlock: numberToHex(from),
|
|
1142
|
+
toBlock: numberToHex(to)
|
|
1143
|
+
}
|
|
1144
|
+
]
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return logs;
|
|
1149
|
+
}
|
|
1116
1150
|
return {
|
|
1117
1151
|
async getHtlcEventsBatch(queries, fromBlock = 0n) {
|
|
1118
1152
|
const results = /* @__PURE__ */ new Map();
|
|
1119
1153
|
if (queries.length === 0) return results;
|
|
1120
1154
|
for (const q of queries) results.set(htlcQueryKey(q), []);
|
|
1121
|
-
const logs = await
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
]
|
|
1133
|
-
});
|
|
1155
|
+
const logs = await getLogs(
|
|
1156
|
+
{
|
|
1157
|
+
address: unique(queries.map((q) => q.htlc)),
|
|
1158
|
+
topics: [
|
|
1159
|
+
HTLC_EVENT_TOPICS,
|
|
1160
|
+
unique(queries.map((q) => q.preimageHash))
|
|
1161
|
+
]
|
|
1162
|
+
},
|
|
1163
|
+
fromBlock
|
|
1164
|
+
);
|
|
1134
1165
|
const grouped = /* @__PURE__ */ new Map();
|
|
1135
1166
|
for (const log of logs) {
|
|
1136
1167
|
const decoded = tryDecode(log);
|
|
@@ -1246,12 +1277,15 @@ function toHtlcEvent(decoded, query) {
|
|
|
1246
1277
|
function unique(values) {
|
|
1247
1278
|
return [...new Set(values)];
|
|
1248
1279
|
}
|
|
1249
|
-
function createEvmRpcReader(rpcUrls) {
|
|
1280
|
+
function createEvmRpcReader(rpcUrls, options = {}) {
|
|
1250
1281
|
const urls = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
|
|
1251
1282
|
const transport = urls.length > 1 ? fallback(urls.map((url) => http(url))) : http(urls[0]);
|
|
1252
1283
|
const client = createPublicClient({ transport });
|
|
1253
|
-
return evmReaderFromClient(client);
|
|
1284
|
+
return evmReaderFromClient(client, options);
|
|
1254
1285
|
}
|
|
1286
|
+
var EVM_LOG_RANGE_LIMITS = {
|
|
1287
|
+
30: 2000n
|
|
1288
|
+
};
|
|
1255
1289
|
function defaultEvmReaders(overrides) {
|
|
1256
1290
|
const chainIds = /* @__PURE__ */ new Set([
|
|
1257
1291
|
...Object.keys(DEFAULT_EVM_RPCS).map(Number),
|
|
@@ -1262,7 +1296,13 @@ function defaultEvmReaders(overrides) {
|
|
|
1262
1296
|
const override = overrides?.[chainId];
|
|
1263
1297
|
const defaults = DEFAULT_EVM_RPCS[chainId] ?? [];
|
|
1264
1298
|
const urls = override ? [override, ...defaults] : defaults;
|
|
1265
|
-
if (urls.length > 0)
|
|
1299
|
+
if (urls.length > 0)
|
|
1300
|
+
readers.set(
|
|
1301
|
+
chainId,
|
|
1302
|
+
createEvmRpcReader(urls, {
|
|
1303
|
+
maxBlockRange: EVM_LOG_RANGE_LIMITS[chainId]
|
|
1304
|
+
})
|
|
1305
|
+
);
|
|
1266
1306
|
}
|
|
1267
1307
|
return readers;
|
|
1268
1308
|
}
|
|
@@ -2032,21 +2072,38 @@ function swapToTracked(stored) {
|
|
|
2032
2072
|
}
|
|
2033
2073
|
|
|
2034
2074
|
// src/tracker/swap-tracker.ts
|
|
2075
|
+
var CLAIM_SUBMITTED = /* @__PURE__ */ new Set([
|
|
2076
|
+
"clientredeeming",
|
|
2077
|
+
"clientredeemed",
|
|
2078
|
+
"serverredeemed"
|
|
2079
|
+
]);
|
|
2080
|
+
var DEFAULT_AT_RISK_RECONCILE_INTERVAL_MS = 6e4;
|
|
2081
|
+
var RETRY_UNREGISTERED_INTERVAL_MS = 6e4;
|
|
2035
2082
|
var SwapTracker = class {
|
|
2036
2083
|
#managers;
|
|
2037
2084
|
#swaps = /* @__PURE__ */ new Map();
|
|
2085
|
+
/**
|
|
2086
|
+
* Swaps whose startup registration failed (a chain read that errored).
|
|
2087
|
+
* Retried every minute; dropping them would leave an active swap
|
|
2088
|
+
* unwatched for the whole session with no claim or refund surfaced.
|
|
2089
|
+
*/
|
|
2090
|
+
#unregistered = /* @__PURE__ */ new Map();
|
|
2038
2091
|
/** Last actions emitted per swap — for dedupe and the subscribe-time snapshot. */
|
|
2039
2092
|
#lastActions = /* @__PURE__ */ new Map();
|
|
2093
|
+
/** The latest server status hint per swap, for {@link #withClaimHint}. */
|
|
2094
|
+
#hintStatuses = /* @__PURE__ */ new Map();
|
|
2040
2095
|
#subscribers = /* @__PURE__ */ new Set();
|
|
2041
2096
|
#refreshIntervalMs;
|
|
2042
2097
|
#atRiskReconcileIntervalMs;
|
|
2043
2098
|
#lastAtRiskReconcileAt = 0;
|
|
2099
|
+
#lastRetryAt = 0;
|
|
2044
2100
|
#eventUnsubs = [];
|
|
2045
2101
|
#timer;
|
|
2046
2102
|
constructor(managers, options) {
|
|
2047
2103
|
this.#managers = managers;
|
|
2048
2104
|
this.#refreshIntervalMs = options?.refreshIntervalMs ?? 0;
|
|
2049
|
-
|
|
2105
|
+
const atRisk = options?.atRiskReconcileIntervalMs ?? DEFAULT_AT_RISK_RECONCILE_INTERVAL_MS;
|
|
2106
|
+
this.#atRiskReconcileIntervalMs = typeof atRisk === "function" ? atRisk : () => atRisk;
|
|
2050
2107
|
}
|
|
2051
2108
|
/**
|
|
2052
2109
|
* Whether every on-chain leg of `swap` is observable — i.e. the tracker can
|
|
@@ -2069,7 +2126,22 @@ var SwapTracker = class {
|
|
|
2069
2126
|
continue;
|
|
2070
2127
|
}
|
|
2071
2128
|
this.#swaps.set(swap.swapId, swap);
|
|
2072
|
-
|
|
2129
|
+
const registered = [];
|
|
2130
|
+
try {
|
|
2131
|
+
for (const leg of legsOf(swap)) {
|
|
2132
|
+
await this.#managerFor(leg).register(leg);
|
|
2133
|
+
registered.push(leg);
|
|
2134
|
+
}
|
|
2135
|
+
} catch (error) {
|
|
2136
|
+
this.#swaps.delete(swap.swapId);
|
|
2137
|
+
for (const leg of registered)
|
|
2138
|
+
void this.#managerFor(leg).unregister(leg);
|
|
2139
|
+
this.#unregistered.set(swap.swapId, swap);
|
|
2140
|
+
console.warn(
|
|
2141
|
+
`SwapTracker: swap ${swap.swapId} not tracked yet, registering its legs failed (will retry):`,
|
|
2142
|
+
error
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2073
2145
|
}
|
|
2074
2146
|
for (const manager of new Set(this.#managers.values())) {
|
|
2075
2147
|
this.#eventUnsubs.push(manager.onEvent(() => this.#recomputeAll()));
|
|
@@ -2077,6 +2149,7 @@ var SwapTracker = class {
|
|
|
2077
2149
|
await this.#refreshManagers();
|
|
2078
2150
|
this.#recomputeAll();
|
|
2079
2151
|
this.#lastAtRiskReconcileAt = Date.now();
|
|
2152
|
+
this.#lastRetryAt = this.#lastAtRiskReconcileAt;
|
|
2080
2153
|
if (this.#refreshIntervalMs > 0)
|
|
2081
2154
|
this.#timer = setInterval(
|
|
2082
2155
|
() => void this.#tick(),
|
|
@@ -2126,7 +2199,11 @@ var SwapTracker = class {
|
|
|
2126
2199
|
*/
|
|
2127
2200
|
async #tick() {
|
|
2128
2201
|
const now = Date.now();
|
|
2129
|
-
if (now - this.#
|
|
2202
|
+
if (now - this.#lastRetryAt >= RETRY_UNREGISTERED_INTERVAL_MS) {
|
|
2203
|
+
this.#lastRetryAt = now;
|
|
2204
|
+
await this.#retryUnregistered();
|
|
2205
|
+
}
|
|
2206
|
+
if (now - this.#lastAtRiskReconcileAt >= this.#atRiskReconcileIntervalMs()) {
|
|
2130
2207
|
this.#lastAtRiskReconcileAt = now;
|
|
2131
2208
|
const legs = [...this.#swaps.values()].filter((swap) => this.#isAtRisk(swap)).flatMap((swap) => legsOf(swap));
|
|
2132
2209
|
await Promise.all(
|
|
@@ -2139,6 +2216,25 @@ var SwapTracker = class {
|
|
|
2139
2216
|
}
|
|
2140
2217
|
this.#recomputeAll();
|
|
2141
2218
|
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Second attempt at swaps whose startup registration failed, on its own
|
|
2221
|
+
* cadence so a slow at-risk interval can't delay it. `track` is idempotent
|
|
2222
|
+
* and rolls back on failure, so a chain that is still down just leaves the
|
|
2223
|
+
* swap here for the next round.
|
|
2224
|
+
*/
|
|
2225
|
+
async #retryUnregistered() {
|
|
2226
|
+
for (const [swapId, swap] of this.#unregistered) {
|
|
2227
|
+
try {
|
|
2228
|
+
await this.track(swap);
|
|
2229
|
+
this.#unregistered.delete(swapId);
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
console.warn(
|
|
2232
|
+
`SwapTracker: retrying registration of ${swapId} failed:`,
|
|
2233
|
+
error
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2142
2238
|
/**
|
|
2143
2239
|
* Whether a swap needs chain-verified monitoring independent of the server:
|
|
2144
2240
|
* its client leg holds (or held) funds on-chain, so knowing when a refund
|
|
@@ -2198,6 +2294,7 @@ var SwapTracker = class {
|
|
|
2198
2294
|
async applyHint(swapId, opts) {
|
|
2199
2295
|
const swap = this.#swaps.get(swapId);
|
|
2200
2296
|
if (!swap) return;
|
|
2297
|
+
if (opts?.status !== void 0) this.#hintStatuses.set(swapId, opts.status);
|
|
2201
2298
|
const legs = legsOf(swap);
|
|
2202
2299
|
const results = await Promise.allSettled(
|
|
2203
2300
|
legs.map((leg) => this.#managerFor(leg).reconcile(leg))
|
|
@@ -2234,6 +2331,7 @@ var SwapTracker = class {
|
|
|
2234
2331
|
for (const leg of legsOf(swap))
|
|
2235
2332
|
void this.#managerFor(leg).unregister(leg);
|
|
2236
2333
|
this.#swaps.clear();
|
|
2334
|
+
this.#unregistered.clear();
|
|
2237
2335
|
this.#subscribers.clear();
|
|
2238
2336
|
}
|
|
2239
2337
|
#managerFor(ref) {
|
|
@@ -2245,6 +2343,28 @@ var SwapTracker = class {
|
|
|
2245
2343
|
#recomputeAll() {
|
|
2246
2344
|
for (const swap of this.#swaps.values()) this.#recompute(swap);
|
|
2247
2345
|
}
|
|
2346
|
+
/**
|
|
2347
|
+
* A server hint that the claim was submitted overrides a chain view that
|
|
2348
|
+
* still says "claim now". The claim is relayed by the server, so once it
|
|
2349
|
+
* reports the redeem in flight, another claim can only be refused; and a
|
|
2350
|
+
* chain that cannot be read (no `eth_getLogs` on the public Rootstock RPCs)
|
|
2351
|
+
* keeps its stale pre-claim observation for good. The chain still wins once
|
|
2352
|
+
* it moves: a fresh `spent_claim` derives past the hint on its own, and a
|
|
2353
|
+
* refund observed on chain is not overridden.
|
|
2354
|
+
*
|
|
2355
|
+
* The override lasts only while the server's claim window is open. Once the
|
|
2356
|
+
* server can refund its leg, the chain view is what protects the client: the
|
|
2357
|
+
* `serverfunded` rules never claim then and lead to the client's own refund
|
|
2358
|
+
* at its timelock. A claim that really landed shows up on chain long before
|
|
2359
|
+
* that; a hint that never did must not hide the refund.
|
|
2360
|
+
*/
|
|
2361
|
+
#withClaimHint(swap, derived, serverChainNow) {
|
|
2362
|
+
const hint = this.#hintStatuses.get(swap.swapId);
|
|
2363
|
+
if (hint === void 0 || !CLAIM_SUBMITTED.has(hint)) return derived;
|
|
2364
|
+
if (derived !== "serverfunded") return derived;
|
|
2365
|
+
const claimWindowOpen = serverChainNow < swap.serverRefundLocktime;
|
|
2366
|
+
return claimWindowOpen ? hint : derived;
|
|
2367
|
+
}
|
|
2248
2368
|
#recompute(swap, force = false) {
|
|
2249
2369
|
let clientHtlc;
|
|
2250
2370
|
let serverHtlc;
|
|
@@ -2264,8 +2384,9 @@ var SwapTracker = class {
|
|
|
2264
2384
|
if (serverHtlc === void 0 || now === void 0) return;
|
|
2265
2385
|
serverChainNow = now;
|
|
2266
2386
|
}
|
|
2267
|
-
const
|
|
2268
|
-
if (
|
|
2387
|
+
const derived = deriveSwapStatus({ clientHtlc, serverHtlc });
|
|
2388
|
+
if (derived === void 0) return;
|
|
2389
|
+
const status = this.#withClaimHint(swap, derived, serverChainNow);
|
|
2269
2390
|
const actions = {
|
|
2270
2391
|
...deriveSwapActions({
|
|
2271
2392
|
status,
|
|
@@ -2316,12 +2437,16 @@ var Client = class {
|
|
|
2316
2437
|
#startPromise;
|
|
2317
2438
|
/** Swap ids whose settled status was already re-fetched this session. */
|
|
2318
2439
|
#settledSyncDone = /* @__PURE__ */ new Set();
|
|
2440
|
+
/** Read per chain read, so it can change without rebuilding the client. */
|
|
2441
|
+
#bitcoinMinConfirmations;
|
|
2319
2442
|
constructor(...args) {
|
|
2320
2443
|
if (args[0] instanceof LegacyClient) {
|
|
2321
2444
|
this.#legacy = args[0];
|
|
2322
2445
|
this.#tracking = args[1] ?? {
|
|
2323
2446
|
enabled: false
|
|
2324
2447
|
};
|
|
2448
|
+
assertMinConfirmations(this.#tracking.bitcoinMinConfirmations);
|
|
2449
|
+
this.#bitcoinMinConfirmations = this.#tracking.bitcoinMinConfirmations;
|
|
2325
2450
|
} else {
|
|
2326
2451
|
this.#legacy = new LegacyClient(
|
|
2327
2452
|
...args
|
|
@@ -2632,6 +2757,22 @@ var Client = class {
|
|
|
2632
2757
|
return this.#legacy.hasReceivedVtxo(...args);
|
|
2633
2758
|
}
|
|
2634
2759
|
// --- Satora-native features go below ---
|
|
2760
|
+
/** Confirmations a server-funded Bitcoin HTLC needs before it is claimable. */
|
|
2761
|
+
getBitcoinMinConfirmations() {
|
|
2762
|
+
return this.#bitcoinMinConfirmations;
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* At `0` the claim goes out against a mempool funding, trusting the funder
|
|
2766
|
+
* not to replace it once the claim reveals the preimage.
|
|
2767
|
+
*
|
|
2768
|
+
* Only the chain monitors read a depth, so this does nothing unless the
|
|
2769
|
+
* client was built with {@link ClientBuilder.withChainVerifiedTracking}.
|
|
2770
|
+
* Takes effect on subsequent reads, including swaps already tracked.
|
|
2771
|
+
*/
|
|
2772
|
+
setBitcoinMinConfirmations(minConfirmations) {
|
|
2773
|
+
assertMinConfirmations(minConfirmations);
|
|
2774
|
+
this.#bitcoinMinConfirmations = minConfirmations;
|
|
2775
|
+
}
|
|
2635
2776
|
/**
|
|
2636
2777
|
* Start observing the user's active swaps and deriving each one's next action.
|
|
2637
2778
|
*
|
|
@@ -2660,8 +2801,18 @@ var Client = class {
|
|
|
2660
2801
|
async #doStartTracking() {
|
|
2661
2802
|
try {
|
|
2662
2803
|
const chainVerified = this.#tracking.chainVerified || this.#tracking.managers !== void 0;
|
|
2804
|
+
if (!chainVerified && (this.#bitcoinMinConfirmations ?? 0) > 0)
|
|
2805
|
+
console.warn(
|
|
2806
|
+
"Client: bitcoinMinConfirmations is set but tracking follows server hints, which carry no depth. Build with ClientBuilder.withChainVerifiedTracking() for the depth to gate the claim."
|
|
2807
|
+
);
|
|
2663
2808
|
const tracker = chainVerified ? new SwapTracker(await this.#ensureManagers(), {
|
|
2664
|
-
refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
|
|
2809
|
+
refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3,
|
|
2810
|
+
// Depth is only re-derived on a chain read, and nothing pushes on a
|
|
2811
|
+
// new block (an Electrum scripthash status doesn't change once the
|
|
2812
|
+
// funding is in a block), so a deep policy would otherwise sit out
|
|
2813
|
+
// the default interval after the block that satisfied it. Read per
|
|
2814
|
+
// tick, like the depth itself, so a setter call after start counts.
|
|
2815
|
+
atRiskReconcileIntervalMs: () => (this.#bitcoinMinConfirmations ?? 0) > 1 ? 15e3 : DEFAULT_AT_RISK_RECONCILE_INTERVAL_MS
|
|
2665
2816
|
}) : new HintTracker({
|
|
2666
2817
|
fetchStatus: async (swapId) => (await this.getSwap(swapId, { updateStorage: true })).status,
|
|
2667
2818
|
refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
|
|
@@ -2847,8 +2998,7 @@ var Client = class {
|
|
|
2847
2998
|
evmRpcUrls,
|
|
2848
2999
|
esploraUrl,
|
|
2849
3000
|
electrumWsUrl,
|
|
2850
|
-
bitcoinNetwork
|
|
2851
|
-
bitcoinMinConfirmations
|
|
3001
|
+
bitcoinNetwork
|
|
2852
3002
|
} = this.#tracking;
|
|
2853
3003
|
const chainTime = async () => (await this.getMtp()).mtp * 1e3;
|
|
2854
3004
|
const arkadeUrl = arkadeServerUrl ?? defaultArkadeServerUrl("bitcoin");
|
|
@@ -2869,7 +3019,7 @@ var Client = class {
|
|
|
2869
3019
|
electrumWsUrl: electrumWsUrl ?? DEFAULT_ELECTRUM_WS_URLS[network],
|
|
2870
3020
|
network,
|
|
2871
3021
|
chainTime,
|
|
2872
|
-
minConfirmations: bitcoinMinConfirmations
|
|
3022
|
+
minConfirmations: () => this.#bitcoinMinConfirmations
|
|
2873
3023
|
})
|
|
2874
3024
|
);
|
|
2875
3025
|
this.#managers = managers;
|
|
@@ -2990,9 +3140,11 @@ var ClientBuilder = class {
|
|
|
2990
3140
|
* funding hits the mempool, which TRUSTS the funder not to double-spend it
|
|
2991
3141
|
* (claiming publishes the preimage, so a funder that then replaced its
|
|
2992
3142
|
* funding could take both legs). Set `1` (or more) for a block-depth policy
|
|
2993
|
-
* that doesn't rely on the funder's good behaviour.
|
|
3143
|
+
* that doesn't rely on the funder's good behaviour. Read only by the chain
|
|
3144
|
+
* monitors, so it needs {@link withChainVerifiedTracking} to have any effect.
|
|
2994
3145
|
*/
|
|
2995
3146
|
withBitcoinMinConfirmations(minConfirmations) {
|
|
3147
|
+
assertMinConfirmations(minConfirmations);
|
|
2996
3148
|
this.#bitcoinMinConfirmations = minConfirmations;
|
|
2997
3149
|
return this;
|
|
2998
3150
|
}
|
|
@@ -3049,6 +3201,13 @@ var ClientBuilder = class {
|
|
|
3049
3201
|
});
|
|
3050
3202
|
}
|
|
3051
3203
|
};
|
|
3204
|
+
function assertMinConfirmations(value) {
|
|
3205
|
+
if (value === void 0) return;
|
|
3206
|
+
if (!Number.isInteger(value) || value < 0)
|
|
3207
|
+
throw new Error(
|
|
3208
|
+
`bitcoinMinConfirmations must be a non-negative integer, got ${value}`
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3052
3211
|
function legacyBaseUrl(legacy) {
|
|
3053
3212
|
try {
|
|
3054
3213
|
return legacy.baseUrl;
|
|
@@ -3092,6 +3251,7 @@ export {
|
|
|
3092
3251
|
htlcFactsFromEsploraTxs,
|
|
3093
3252
|
htlcKey,
|
|
3094
3253
|
htlcQueryKey,
|
|
3254
|
+
resolveMinConfirmations,
|
|
3095
3255
|
swapToTracked
|
|
3096
3256
|
};
|
|
3097
3257
|
//# sourceMappingURL=index.js.map
|