@arkade-os/boltz-swap 0.3.33 → 0.3.35
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/{arkade-swaps-9M7FRuq1.d.cts → arkade-swaps-BXAD1s8j.d.ts} +13 -4
- package/dist/{arkade-swaps-DNsyWeFr.d.ts → arkade-swaps-CfMets16.d.cts} +13 -4
- package/dist/{chunk-SJ5SYSMK.js → chunk-B4CYBKFJ.js} +295 -120
- package/dist/{chunk-HNQDJOLM.js → chunk-H6F67K2A.js} +1 -1
- package/dist/expo/background.cjs +296 -120
- package/dist/expo/background.d.cts +3 -3
- package/dist/expo/background.d.ts +3 -3
- package/dist/expo/background.js +3 -2
- package/dist/expo/index.cjs +295 -120
- package/dist/expo/index.d.cts +5 -5
- package/dist/expo/index.d.ts +5 -5
- package/dist/expo/index.js +2 -2
- package/dist/index.cjs +331 -124
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +40 -6
- package/dist/repositories/realm/index.d.cts +1 -1
- package/dist/repositories/realm/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/{swapsPollProcessor-CuITxZie.d.cts → swapsPollProcessor-BpAqG0V6.d.cts} +1 -1
- package/dist/{swapsPollProcessor-CEgeGlbP.d.ts → swapsPollProcessor-DFVOAy_-.d.ts} +1 -1
- package/dist/{types-CrKkVzBB.d.cts → types--axEWA8c.d.cts} +34 -2
- package/dist/{types-CrKkVzBB.d.ts → types--axEWA8c.d.ts} +34 -2
- package/package.json +3 -3
package/dist/expo/index.cjs
CHANGED
|
@@ -145,6 +145,8 @@ var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
|
145
145
|
return `Boltz quote ${options.quotedAmount} is below acceptable floor ${options.floor}`;
|
|
146
146
|
case "non_positive":
|
|
147
147
|
return `Boltz quote ${options.quotedAmount} is not positive`;
|
|
148
|
+
case "non_safe_integer":
|
|
149
|
+
return `Boltz quote ${options.quotedAmount} is not a safe positive satoshi integer`;
|
|
148
150
|
case "no_baseline":
|
|
149
151
|
return "Cannot accept quote: no minAcceptableAmount and no stored pending swap";
|
|
150
152
|
}
|
|
@@ -198,6 +200,7 @@ var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
|
198
200
|
message
|
|
199
201
|
});
|
|
200
202
|
case "non_positive":
|
|
203
|
+
case "non_safe_integer":
|
|
201
204
|
if (quotedAmount === null) return null;
|
|
202
205
|
return new _QuoteRejectedError({
|
|
203
206
|
reason,
|
|
@@ -213,6 +216,7 @@ var QUOTE_REJECTION_TRANSPORT_PREFIX = "QUOTE_REJECTED::";
|
|
|
213
216
|
var QUOTE_REJECTION_REASONS = /* @__PURE__ */ new Set([
|
|
214
217
|
"below_floor",
|
|
215
218
|
"non_positive",
|
|
219
|
+
"non_safe_integer",
|
|
216
220
|
"no_baseline"
|
|
217
221
|
]);
|
|
218
222
|
var BoltzRefundError = class extends Error {
|
|
@@ -1359,6 +1363,20 @@ function extractInvoiceAmount(amountSats, fees) {
|
|
|
1359
1363
|
if (miner >= amountSats) return 0;
|
|
1360
1364
|
return Math.ceil((amountSats - miner) / (1 - percentage / 100));
|
|
1361
1365
|
}
|
|
1366
|
+
function resolveVhtlcTimeouts(tree, timeoutBlockHeights) {
|
|
1367
|
+
const resolved = timeoutBlockHeights ?? {
|
|
1368
|
+
refund: extractTimeLockFromLeafOutput(tree.refundWithoutBoltzLeaf?.output ?? ""),
|
|
1369
|
+
unilateralClaim: extractTimeLockFromLeafOutput(tree.unilateralClaimLeaf?.output ?? ""),
|
|
1370
|
+
unilateralRefund: extractTimeLockFromLeafOutput(tree.unilateralRefundLeaf?.output ?? ""),
|
|
1371
|
+
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
1372
|
+
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
1373
|
+
)
|
|
1374
|
+
};
|
|
1375
|
+
if (!resolved.refund || !resolved.unilateralClaim || !resolved.unilateralRefund || !resolved.unilateralRefundWithoutReceiver) {
|
|
1376
|
+
return void 0;
|
|
1377
|
+
}
|
|
1378
|
+
return resolved;
|
|
1379
|
+
}
|
|
1362
1380
|
|
|
1363
1381
|
// src/logger.ts
|
|
1364
1382
|
var logger = console;
|
|
@@ -1373,6 +1391,13 @@ var SwapManager = class _SwapManager {
|
|
|
1373
1391
|
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1374
1392
|
*/
|
|
1375
1393
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1394
|
+
/**
|
|
1395
|
+
* Delay between re-attempts of a chain refund that left VTXOs deferred
|
|
1396
|
+
* (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
|
|
1397
|
+
* has elapsed). Boltz won't send another status update once the swap
|
|
1398
|
+
* is `swap.expired`, so the manager owns the local retry cadence.
|
|
1399
|
+
*/
|
|
1400
|
+
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1376
1401
|
swapProvider;
|
|
1377
1402
|
config;
|
|
1378
1403
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1391,6 +1416,11 @@ var SwapManager = class _SwapManager {
|
|
|
1391
1416
|
reconnectTimer = null;
|
|
1392
1417
|
initialPollTimer = null;
|
|
1393
1418
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1419
|
+
// Per-swap retry timers for chain refunds that left work undone
|
|
1420
|
+
// (refundArk returned `skipped > 0`). The swap is held in
|
|
1421
|
+
// `monitoredSwaps` past its terminal Boltz status until the local
|
|
1422
|
+
// refund completes or the manager stops.
|
|
1423
|
+
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1394
1424
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1395
1425
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1396
1426
|
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
@@ -1587,6 +1617,10 @@ var SwapManager = class _SwapManager {
|
|
|
1587
1617
|
clearTimeout(timer);
|
|
1588
1618
|
}
|
|
1589
1619
|
this.pollRetryTimers.clear();
|
|
1620
|
+
for (const timer of this.refundRetryTimers.values()) {
|
|
1621
|
+
clearTimeout(timer);
|
|
1622
|
+
}
|
|
1623
|
+
this.refundRetryTimers.clear();
|
|
1590
1624
|
this.notFoundCounts.clear();
|
|
1591
1625
|
}
|
|
1592
1626
|
/**
|
|
@@ -1643,6 +1677,11 @@ var SwapManager = class _SwapManager {
|
|
|
1643
1677
|
clearTimeout(retryTimer);
|
|
1644
1678
|
this.pollRetryTimers.delete(swapId);
|
|
1645
1679
|
}
|
|
1680
|
+
const refundRetryTimer = this.refundRetryTimers.get(swapId);
|
|
1681
|
+
if (refundRetryTimer) {
|
|
1682
|
+
clearTimeout(refundRetryTimer);
|
|
1683
|
+
this.refundRetryTimers.delete(swapId);
|
|
1684
|
+
}
|
|
1646
1685
|
this.notFoundCounts.delete(swapId);
|
|
1647
1686
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1648
1687
|
}
|
|
@@ -1914,15 +1953,57 @@ var SwapManager = class _SwapManager {
|
|
|
1914
1953
|
await this.executeAutonomousAction(swap);
|
|
1915
1954
|
}
|
|
1916
1955
|
if (this.isFinalStatus(swap)) {
|
|
1917
|
-
this.
|
|
1918
|
-
|
|
1919
|
-
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1920
|
-
if (retryTimer) {
|
|
1921
|
-
clearTimeout(retryTimer);
|
|
1922
|
-
this.pollRetryTimers.delete(swap.id);
|
|
1956
|
+
if (this.refundRetryTimers.has(swap.id)) {
|
|
1957
|
+
return;
|
|
1923
1958
|
}
|
|
1924
|
-
this.
|
|
1959
|
+
this.finalizeMonitoredSwap(swap);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Drop a swap from monitoring and emit the terminal completion event.
|
|
1964
|
+
* Shared between the on-status-update finalization path and the
|
|
1965
|
+
* refund-retry finalization path (used when a previously-deferred
|
|
1966
|
+
* chain refund has finished its remaining work).
|
|
1967
|
+
*/
|
|
1968
|
+
finalizeMonitoredSwap(swap) {
|
|
1969
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1970
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1971
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1972
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1973
|
+
if (retryTimer) {
|
|
1974
|
+
clearTimeout(retryTimer);
|
|
1975
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1925
1976
|
}
|
|
1977
|
+
const refundRetry = this.refundRetryTimers.get(swap.id);
|
|
1978
|
+
if (refundRetry) {
|
|
1979
|
+
clearTimeout(refundRetry);
|
|
1980
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1981
|
+
}
|
|
1982
|
+
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Schedule another `executeAutonomousAction` run for a chain swap whose
|
|
1986
|
+
* refund left VTXOs deferred. After the retry completes, if no further
|
|
1987
|
+
* deferral was reported, finalize monitoring cleanup.
|
|
1988
|
+
*/
|
|
1989
|
+
scheduleRefundRetry(swap, delayMs) {
|
|
1990
|
+
const existing = this.refundRetryTimers.get(swap.id);
|
|
1991
|
+
if (existing) clearTimeout(existing);
|
|
1992
|
+
this.refundRetryTimers.set(
|
|
1993
|
+
swap.id,
|
|
1994
|
+
setTimeout(async () => {
|
|
1995
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1996
|
+
if (!this.isRunning) return;
|
|
1997
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1998
|
+
try {
|
|
1999
|
+
await this.executeAutonomousAction(swap);
|
|
2000
|
+
} finally {
|
|
2001
|
+
if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
|
|
2002
|
+
this.finalizeMonitoredSwap(swap);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}, delayMs)
|
|
2006
|
+
);
|
|
1926
2007
|
}
|
|
1927
2008
|
/**
|
|
1928
2009
|
* Execute autonomous action based on swap status
|
|
@@ -1977,10 +2058,27 @@ var SwapManager = class _SwapManager {
|
|
|
1977
2058
|
} else if (isChainRefundableStatus(swap.status)) {
|
|
1978
2059
|
if (swap.request.from === "ARK") {
|
|
1979
2060
|
logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
(
|
|
1983
|
-
|
|
2061
|
+
try {
|
|
2062
|
+
const outcome = await this.executeRefundArkAction(swap);
|
|
2063
|
+
if (outcome && outcome.skipped > 0) {
|
|
2064
|
+
logger.log(
|
|
2065
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
2066
|
+
);
|
|
2067
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2068
|
+
}
|
|
2069
|
+
this.actionExecutedListeners.forEach(
|
|
2070
|
+
(listener) => listener(swap, "refundArk")
|
|
2071
|
+
);
|
|
2072
|
+
} catch (error) {
|
|
2073
|
+
logger.error(
|
|
2074
|
+
`Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
|
|
2075
|
+
error
|
|
2076
|
+
);
|
|
2077
|
+
this.swapFailedListeners.forEach(
|
|
2078
|
+
(listener) => listener(swap, error)
|
|
2079
|
+
);
|
|
2080
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2081
|
+
}
|
|
1984
2082
|
}
|
|
1985
2083
|
if (swap.request.from === "BTC") {
|
|
1986
2084
|
logger.warn(
|
|
@@ -2061,7 +2159,7 @@ var SwapManager = class _SwapManager {
|
|
|
2061
2159
|
logger.error("refundArk callback not set");
|
|
2062
2160
|
return;
|
|
2063
2161
|
}
|
|
2064
|
-
|
|
2162
|
+
return this.refundArkCallback(swap);
|
|
2065
2163
|
}
|
|
2066
2164
|
/**
|
|
2067
2165
|
* Execute sign server claim action for chain swap.
|
|
@@ -2161,9 +2259,7 @@ var SwapManager = class _SwapManager {
|
|
|
2161
2259
|
*/
|
|
2162
2260
|
async pollAllSwaps() {
|
|
2163
2261
|
if (this.monitoredSwaps.size === 0) return;
|
|
2164
|
-
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2165
|
-
(swap) => this.pollSingleSwap(swap)
|
|
2166
|
-
);
|
|
2262
|
+
const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
|
|
2167
2263
|
await Promise.allSettled(pollPromises);
|
|
2168
2264
|
}
|
|
2169
2265
|
async pollSingleSwap(swap) {
|
|
@@ -2216,6 +2312,7 @@ var SwapManager = class _SwapManager {
|
|
|
2216
2312
|
* Boltz endpoint).
|
|
2217
2313
|
*/
|
|
2218
2314
|
async handleSwapNotFound(swap) {
|
|
2315
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2219
2316
|
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2220
2317
|
this.notFoundCounts.set(swap.id, count);
|
|
2221
2318
|
logger.warn(
|
|
@@ -2236,6 +2333,7 @@ var SwapManager = class _SwapManager {
|
|
|
2236
2333
|
* 404s without recovering anything.
|
|
2237
2334
|
*/
|
|
2238
2335
|
async markSwapAsUnknownToProvider(swap) {
|
|
2336
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2239
2337
|
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2240
2338
|
this.notFoundCounts.delete(swap.id);
|
|
2241
2339
|
return;
|
|
@@ -2248,6 +2346,11 @@ var SwapManager = class _SwapManager {
|
|
|
2248
2346
|
clearTimeout(retryTimer);
|
|
2249
2347
|
this.pollRetryTimers.delete(swap.id);
|
|
2250
2348
|
}
|
|
2349
|
+
const refundRetryTimer = this.refundRetryTimers.get(swap.id);
|
|
2350
|
+
if (refundRetryTimer) {
|
|
2351
|
+
clearTimeout(refundRetryTimer);
|
|
2352
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2353
|
+
}
|
|
2251
2354
|
this.notFoundCounts.delete(swap.id);
|
|
2252
2355
|
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
2253
2356
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
@@ -2998,7 +3101,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
2998
3101
|
await this.claimBtc(swap);
|
|
2999
3102
|
},
|
|
3000
3103
|
refundArk: async (swap) => {
|
|
3001
|
-
|
|
3104
|
+
return this.refundArk(swap);
|
|
3002
3105
|
},
|
|
3003
3106
|
signServerClaim: async (swap) => {
|
|
3004
3107
|
await this.signCooperativeClaimForServer(swap);
|
|
@@ -3211,51 +3314,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3211
3314
|
throw new Error(
|
|
3212
3315
|
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3213
3316
|
);
|
|
3214
|
-
let
|
|
3317
|
+
let unspentVtxos = [];
|
|
3318
|
+
let rawVtxos = [];
|
|
3215
3319
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
3216
|
-
const
|
|
3320
|
+
const result = await this.indexerProvider.getVtxos({
|
|
3217
3321
|
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
3218
3322
|
});
|
|
3219
|
-
|
|
3220
|
-
|
|
3323
|
+
rawVtxos = result.vtxos;
|
|
3324
|
+
unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
3325
|
+
if (unspentVtxos.length > 0) {
|
|
3221
3326
|
break;
|
|
3222
3327
|
}
|
|
3223
3328
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
3224
3329
|
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
3225
3330
|
}
|
|
3226
3331
|
}
|
|
3227
|
-
if (
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3332
|
+
if (unspentVtxos.length === 0) {
|
|
3333
|
+
if (rawVtxos.length === 0) {
|
|
3334
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
3335
|
+
}
|
|
3231
3336
|
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3232
3337
|
}
|
|
3233
|
-
const input = {
|
|
3234
|
-
...vtxo,
|
|
3235
|
-
tapLeafScript: vhtlcScript.claim(),
|
|
3236
|
-
tapTree: vhtlcScript.encode()
|
|
3237
|
-
};
|
|
3238
|
-
const output = {
|
|
3239
|
-
amount: BigInt(vtxo.value),
|
|
3240
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
3241
|
-
};
|
|
3242
3338
|
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
vhtlcScript
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3339
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3340
|
+
const claimErrors = [];
|
|
3341
|
+
let usedOffchainClaim = false;
|
|
3342
|
+
for (const vtxo of unspentVtxos) {
|
|
3343
|
+
const input = {
|
|
3344
|
+
...vtxo,
|
|
3345
|
+
tapLeafScript: vhtlcScript.claim(),
|
|
3346
|
+
tapTree: vhtlcScript.encode()
|
|
3347
|
+
};
|
|
3348
|
+
const output = {
|
|
3349
|
+
amount: BigInt(vtxo.value),
|
|
3350
|
+
script: outputScript
|
|
3351
|
+
};
|
|
3352
|
+
try {
|
|
3353
|
+
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
3354
|
+
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
3355
|
+
} else {
|
|
3356
|
+
await claimVHTLCwithOffchainTx(
|
|
3357
|
+
vhtlcIdentity,
|
|
3358
|
+
vhtlcScript,
|
|
3359
|
+
serverXOnly,
|
|
3360
|
+
input,
|
|
3361
|
+
output,
|
|
3362
|
+
arkInfo,
|
|
3363
|
+
this.arkProvider
|
|
3364
|
+
);
|
|
3365
|
+
usedOffchainClaim = true;
|
|
3366
|
+
}
|
|
3367
|
+
} catch (error) {
|
|
3368
|
+
claimErrors.push({ vtxo, error });
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
if (claimErrors.length > 0) {
|
|
3372
|
+
const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
|
|
3373
|
+
throw new Error(
|
|
3374
|
+
`Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
|
|
3256
3375
|
);
|
|
3257
|
-
finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
|
|
3258
3376
|
}
|
|
3377
|
+
const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
|
|
3259
3378
|
await updateReverseSwapStatus(
|
|
3260
3379
|
pendingSwap,
|
|
3261
3380
|
finalStatus,
|
|
@@ -3630,6 +3749,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3630
3749
|
if (boltzCallCount > 0) {
|
|
3631
3750
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3632
3751
|
}
|
|
3752
|
+
boltzCallCount++;
|
|
3633
3753
|
await refundVHTLCwithOffchainTx(
|
|
3634
3754
|
pendingSwap.id,
|
|
3635
3755
|
this.wallet.identity,
|
|
@@ -3642,7 +3762,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3642
3762
|
arkInfo,
|
|
3643
3763
|
this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3644
3764
|
);
|
|
3645
|
-
boltzCallCount++;
|
|
3646
3765
|
sweptCount++;
|
|
3647
3766
|
} catch (error) {
|
|
3648
3767
|
if (!(error instanceof BoltzRefundError)) {
|
|
@@ -4140,8 +4259,17 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4140
4259
|
await this.swapProvider.postBtcTransaction(claimTx.hex);
|
|
4141
4260
|
}
|
|
4142
4261
|
/**
|
|
4143
|
-
* When an ARK to BTC swap fails, refund
|
|
4262
|
+
* When an ARK to BTC swap fails, refund every unspent VTXO at the chain
|
|
4263
|
+
* swap's ARK lockup address.
|
|
4264
|
+
*
|
|
4265
|
+
* Path selection per VTXO:
|
|
4266
|
+
* - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
|
|
4267
|
+
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
|
|
4268
|
+
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
|
|
4269
|
+
*
|
|
4144
4270
|
* @param pendingSwap - The pending chain swap to refund.
|
|
4271
|
+
* @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
|
|
4272
|
+
* the call was a no-op — callers should retry after CLTV.
|
|
4145
4273
|
*/
|
|
4146
4274
|
async refundArk(pendingSwap) {
|
|
4147
4275
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
@@ -4165,21 +4293,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4165
4293
|
"boltz",
|
|
4166
4294
|
pendingSwap.id
|
|
4167
4295
|
);
|
|
4168
|
-
const vhtlcPkScript = import_sdk8.ArkAddress.decode(
|
|
4169
|
-
pendingSwap.response.lockupDetails.lockupAddress
|
|
4170
|
-
).pkScript;
|
|
4171
|
-
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4172
|
-
scripts: [import_base9.hex.encode(vhtlcPkScript)]
|
|
4173
|
-
});
|
|
4174
|
-
if (vtxos.length === 0) {
|
|
4175
|
-
throw new Error(
|
|
4176
|
-
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4177
|
-
);
|
|
4178
|
-
}
|
|
4179
|
-
const vtxo = vtxos[0];
|
|
4180
|
-
if (vtxo.isSpent) {
|
|
4181
|
-
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4182
|
-
}
|
|
4183
4296
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4184
4297
|
network: arkInfo.network,
|
|
4185
4298
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4195,37 +4308,105 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4195
4308
|
message: "Unable to claim: invalid VHTLC address"
|
|
4196
4309
|
});
|
|
4197
4310
|
}
|
|
4198
|
-
const
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
const output = {
|
|
4205
|
-
amount: BigInt(vtxo.value),
|
|
4206
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4207
|
-
};
|
|
4208
|
-
if (isRecoverableVtxo) {
|
|
4209
|
-
await this.joinBatch(this.wallet.identity, input, output, arkInfo);
|
|
4210
|
-
} else {
|
|
4211
|
-
await refundVHTLCwithOffchainTx(
|
|
4212
|
-
pendingSwap.id,
|
|
4213
|
-
this.wallet.identity,
|
|
4214
|
-
this.arkProvider,
|
|
4215
|
-
boltzXOnlyPublicKey,
|
|
4216
|
-
ourXOnlyPublicKey,
|
|
4217
|
-
serverXOnlyPublicKey,
|
|
4218
|
-
input,
|
|
4219
|
-
output,
|
|
4220
|
-
arkInfo,
|
|
4221
|
-
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4311
|
+
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4312
|
+
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
4313
|
+
});
|
|
4314
|
+
if (vtxos.length === 0) {
|
|
4315
|
+
throw new Error(
|
|
4316
|
+
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4222
4317
|
);
|
|
4223
4318
|
}
|
|
4319
|
+
const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
4320
|
+
if (unspentVtxos.length === 0) {
|
|
4321
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4322
|
+
}
|
|
4323
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
4324
|
+
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
4325
|
+
const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
|
|
4326
|
+
let boltzCallCount = 0;
|
|
4327
|
+
let sweptCount = 0;
|
|
4328
|
+
let skippedCount = 0;
|
|
4329
|
+
for (const vtxo of unspentVtxos) {
|
|
4330
|
+
const isRecoverableVtxo = (0, import_sdk8.isRecoverable)(vtxo);
|
|
4331
|
+
const output = {
|
|
4332
|
+
amount: BigInt(vtxo.value),
|
|
4333
|
+
script: outputScript
|
|
4334
|
+
};
|
|
4335
|
+
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4336
|
+
const input2 = {
|
|
4337
|
+
...vtxo,
|
|
4338
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4339
|
+
tapTree: vhtlcScript.encode()
|
|
4340
|
+
};
|
|
4341
|
+
await this.joinBatch(
|
|
4342
|
+
this.wallet.identity,
|
|
4343
|
+
input2,
|
|
4344
|
+
output,
|
|
4345
|
+
arkInfo,
|
|
4346
|
+
isRecoverableVtxo
|
|
4347
|
+
);
|
|
4348
|
+
sweptCount++;
|
|
4349
|
+
continue;
|
|
4350
|
+
}
|
|
4351
|
+
if (isRecoverableVtxo) {
|
|
4352
|
+
logger.error(
|
|
4353
|
+
`Swap ${pendingSwap.id}: recoverable VTXO ${vtxo.txid}:${vtxo.vout} cannot be refunded yet \u2014 refundWithoutReceiver locktime has not passed (refundLocktime=${refundLocktime}, currentTimestamp=${Math.floor(Date.now() / 1e3)}). Refund will be retried after locktime.`
|
|
4354
|
+
);
|
|
4355
|
+
skippedCount++;
|
|
4356
|
+
continue;
|
|
4357
|
+
}
|
|
4358
|
+
const input = {
|
|
4359
|
+
...vtxo,
|
|
4360
|
+
tapLeafScript: vhtlcScript.refund(),
|
|
4361
|
+
tapTree: vhtlcScript.encode()
|
|
4362
|
+
};
|
|
4363
|
+
try {
|
|
4364
|
+
if (boltzCallCount > 0) {
|
|
4365
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4366
|
+
}
|
|
4367
|
+
boltzCallCount++;
|
|
4368
|
+
await refundVHTLCwithOffchainTx(
|
|
4369
|
+
pendingSwap.id,
|
|
4370
|
+
this.wallet.identity,
|
|
4371
|
+
this.arkProvider,
|
|
4372
|
+
boltzXOnlyPublicKey,
|
|
4373
|
+
ourXOnlyPublicKey,
|
|
4374
|
+
serverXOnlyPublicKey,
|
|
4375
|
+
input,
|
|
4376
|
+
output,
|
|
4377
|
+
arkInfo,
|
|
4378
|
+
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4379
|
+
);
|
|
4380
|
+
sweptCount++;
|
|
4381
|
+
} catch (error) {
|
|
4382
|
+
if (!(error instanceof BoltzRefundError)) {
|
|
4383
|
+
throw error;
|
|
4384
|
+
}
|
|
4385
|
+
if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4386
|
+
logger.error(
|
|
4387
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint and refundWithoutReceiver locktime has not passed yet (currentTimestamp=${Math.floor(Date.now() / 1e3)}, locktime=${refundLocktime}). Refund will be retried after locktime.`
|
|
4388
|
+
);
|
|
4389
|
+
skippedCount++;
|
|
4390
|
+
continue;
|
|
4391
|
+
}
|
|
4392
|
+
logger.warn(
|
|
4393
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
|
|
4394
|
+
);
|
|
4395
|
+
const fallbackInput = {
|
|
4396
|
+
...vtxo,
|
|
4397
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4398
|
+
tapTree: vhtlcScript.encode()
|
|
4399
|
+
};
|
|
4400
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
4401
|
+
sweptCount++;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4224
4404
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4225
4405
|
await this.savePendingChainSwap({
|
|
4226
4406
|
...pendingSwap,
|
|
4227
4407
|
status: finalStatus.status
|
|
4228
4408
|
});
|
|
4409
|
+
return { swept: sweptCount, skipped: skippedCount };
|
|
4229
4410
|
}
|
|
4230
4411
|
// =========================================================================
|
|
4231
4412
|
// Chain swaps: BTC -> ARK
|
|
@@ -4646,7 +4827,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4646
4827
|
this.validateQuoteOptions(options);
|
|
4647
4828
|
const floor = await this.resolveQuoteFloor(swapId, options);
|
|
4648
4829
|
const slippageBps = options?.maxSlippageBps ?? 0;
|
|
4649
|
-
|
|
4830
|
+
const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
|
|
4831
|
+
if (effectiveFloor < 1) {
|
|
4832
|
+
throw new TypeError(
|
|
4833
|
+
`Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
return effectiveFloor;
|
|
4650
4837
|
}
|
|
4651
4838
|
async resolveQuoteFloor(swapId, options) {
|
|
4652
4839
|
if (options?.minAcceptableAmount !== void 0) {
|
|
@@ -4682,7 +4869,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4682
4869
|
}
|
|
4683
4870
|
}
|
|
4684
4871
|
validateQuote(amount, effectiveFloor) {
|
|
4685
|
-
if (!(amount
|
|
4872
|
+
if (!Number.isSafeInteger(amount)) {
|
|
4873
|
+
throw new QuoteRejectedError({
|
|
4874
|
+
reason: "non_safe_integer",
|
|
4875
|
+
quotedAmount: amount
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4878
|
+
if (amount <= 0) {
|
|
4686
4879
|
throw new QuoteRejectedError({
|
|
4687
4880
|
reason: "non_positive",
|
|
4688
4881
|
quotedAmount: amount
|
|
@@ -4865,20 +5058,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4865
5058
|
onchainAmount: amount,
|
|
4866
5059
|
lockupAddress,
|
|
4867
5060
|
refundPublicKey: serverPublicKey,
|
|
4868
|
-
timeoutBlockHeights: timeoutBlockHeights
|
|
4869
|
-
refund: extractTimeLockFromLeafOutput(
|
|
4870
|
-
tree.refundWithoutBoltzLeaf?.output ?? ""
|
|
4871
|
-
),
|
|
4872
|
-
unilateralClaim: extractTimeLockFromLeafOutput(
|
|
4873
|
-
tree.unilateralClaimLeaf?.output ?? ""
|
|
4874
|
-
),
|
|
4875
|
-
unilateralRefund: extractTimeLockFromLeafOutput(
|
|
4876
|
-
tree.unilateralRefundLeaf?.output ?? ""
|
|
4877
|
-
),
|
|
4878
|
-
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
4879
|
-
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
4880
|
-
)
|
|
4881
|
-
}
|
|
5061
|
+
timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4882
5062
|
},
|
|
4883
5063
|
status,
|
|
4884
5064
|
type: "reverse",
|
|
@@ -4911,26 +5091,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4911
5091
|
address: lockupAddress,
|
|
4912
5092
|
expectedAmount: amount,
|
|
4913
5093
|
claimPublicKey: serverPublicKey,
|
|
4914
|
-
timeoutBlockHeights: timeoutBlockHeights
|
|
4915
|
-
refund: extractTimeLockFromLeafOutput(
|
|
4916
|
-
tree.refundWithoutBoltzLeaf?.output ?? ""
|
|
4917
|
-
),
|
|
4918
|
-
unilateralClaim: extractTimeLockFromLeafOutput(
|
|
4919
|
-
tree.unilateralClaimLeaf?.output ?? ""
|
|
4920
|
-
),
|
|
4921
|
-
unilateralRefund: extractTimeLockFromLeafOutput(
|
|
4922
|
-
tree.unilateralRefundLeaf?.output ?? ""
|
|
4923
|
-
),
|
|
4924
|
-
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
4925
|
-
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
4926
|
-
)
|
|
4927
|
-
}
|
|
5094
|
+
timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4928
5095
|
}
|
|
4929
5096
|
});
|
|
4930
5097
|
} else if (isRestoredChainSwap(swap)) {
|
|
4931
5098
|
const refundDetails = swap.refundDetails;
|
|
4932
5099
|
if (!refundDetails) continue;
|
|
4933
|
-
const {
|
|
5100
|
+
const {
|
|
5101
|
+
amount,
|
|
5102
|
+
lockupAddress,
|
|
5103
|
+
serverPublicKey,
|
|
5104
|
+
timeoutBlockHeight,
|
|
5105
|
+
tree,
|
|
5106
|
+
timeoutBlockHeights
|
|
5107
|
+
} = refundDetails;
|
|
4934
5108
|
chainSwaps.push({
|
|
4935
5109
|
id,
|
|
4936
5110
|
type: "chain",
|
|
@@ -4956,7 +5130,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4956
5130
|
amount,
|
|
4957
5131
|
lockupAddress,
|
|
4958
5132
|
serverPublicKey,
|
|
4959
|
-
timeoutBlockHeight
|
|
5133
|
+
timeoutBlockHeight,
|
|
5134
|
+
timeouts: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4960
5135
|
}
|
|
4961
5136
|
}
|
|
4962
5137
|
});
|
package/dist/expo/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-
|
|
2
|
-
import {
|
|
3
|
-
import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-
|
|
4
|
-
export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-
|
|
1
|
+
import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-CfMets16.cjs';
|
|
2
|
+
import { o as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types--axEWA8c.cjs';
|
|
3
|
+
import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-BpAqG0V6.cjs';
|
|
4
|
+
export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-BpAqG0V6.cjs';
|
|
5
5
|
import { ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
6
6
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
7
7
|
import '@arkade-os/sdk/worker/expo';
|
|
@@ -116,7 +116,7 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
|
|
|
116
116
|
txid: string;
|
|
117
117
|
}>;
|
|
118
118
|
claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
|
|
119
|
-
refundArk(pendingSwap: BoltzChainSwap): Promise<
|
|
119
|
+
refundArk(pendingSwap: BoltzChainSwap): Promise<ChainArkRefundOutcome>;
|
|
120
120
|
btcToArk(args: {
|
|
121
121
|
feeSatsPerByte?: number;
|
|
122
122
|
senderLockAmount?: number;
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-
|
|
2
|
-
import {
|
|
3
|
-
import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-
|
|
4
|
-
export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-
|
|
1
|
+
import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-BXAD1s8j.js';
|
|
2
|
+
import { o as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types--axEWA8c.js';
|
|
3
|
+
import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-DFVOAy_-.js';
|
|
4
|
+
export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-DFVOAy_-.js';
|
|
5
5
|
import { ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
6
6
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
7
7
|
import '@arkade-os/sdk/worker/expo';
|
|
@@ -116,7 +116,7 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
|
|
|
116
116
|
txid: string;
|
|
117
117
|
}>;
|
|
118
118
|
claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
|
|
119
|
-
refundArk(pendingSwap: BoltzChainSwap): Promise<
|
|
119
|
+
refundArk(pendingSwap: BoltzChainSwap): Promise<ChainArkRefundOutcome>;
|
|
120
120
|
btcToArk(args: {
|
|
121
121
|
feeSatsPerByte?: number;
|
|
122
122
|
senderLockAmount?: number;
|
package/dist/expo/index.js
CHANGED