@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/background.cjs
CHANGED
|
@@ -155,6 +155,8 @@ var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
|
155
155
|
return `Boltz quote ${options.quotedAmount} is below acceptable floor ${options.floor}`;
|
|
156
156
|
case "non_positive":
|
|
157
157
|
return `Boltz quote ${options.quotedAmount} is not positive`;
|
|
158
|
+
case "non_safe_integer":
|
|
159
|
+
return `Boltz quote ${options.quotedAmount} is not a safe positive satoshi integer`;
|
|
158
160
|
case "no_baseline":
|
|
159
161
|
return "Cannot accept quote: no minAcceptableAmount and no stored pending swap";
|
|
160
162
|
}
|
|
@@ -208,6 +210,7 @@ var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
|
208
210
|
message
|
|
209
211
|
});
|
|
210
212
|
case "non_positive":
|
|
213
|
+
case "non_safe_integer":
|
|
211
214
|
if (quotedAmount === null) return null;
|
|
212
215
|
return new _QuoteRejectedError({
|
|
213
216
|
reason,
|
|
@@ -223,6 +226,7 @@ var QUOTE_REJECTION_TRANSPORT_PREFIX = "QUOTE_REJECTED::";
|
|
|
223
226
|
var QUOTE_REJECTION_REASONS = /* @__PURE__ */ new Set([
|
|
224
227
|
"below_floor",
|
|
225
228
|
"non_positive",
|
|
229
|
+
"non_safe_integer",
|
|
226
230
|
"no_baseline"
|
|
227
231
|
]);
|
|
228
232
|
var BoltzRefundError = class extends Error {
|
|
@@ -1369,6 +1373,20 @@ function extractInvoiceAmount(amountSats, fees) {
|
|
|
1369
1373
|
if (miner >= amountSats) return 0;
|
|
1370
1374
|
return Math.ceil((amountSats - miner) / (1 - percentage / 100));
|
|
1371
1375
|
}
|
|
1376
|
+
function resolveVhtlcTimeouts(tree, timeoutBlockHeights) {
|
|
1377
|
+
const resolved = timeoutBlockHeights ?? {
|
|
1378
|
+
refund: extractTimeLockFromLeafOutput(tree.refundWithoutBoltzLeaf?.output ?? ""),
|
|
1379
|
+
unilateralClaim: extractTimeLockFromLeafOutput(tree.unilateralClaimLeaf?.output ?? ""),
|
|
1380
|
+
unilateralRefund: extractTimeLockFromLeafOutput(tree.unilateralRefundLeaf?.output ?? ""),
|
|
1381
|
+
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
1382
|
+
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
1383
|
+
)
|
|
1384
|
+
};
|
|
1385
|
+
if (!resolved.refund || !resolved.unilateralClaim || !resolved.unilateralRefund || !resolved.unilateralRefundWithoutReceiver) {
|
|
1386
|
+
return void 0;
|
|
1387
|
+
}
|
|
1388
|
+
return resolved;
|
|
1389
|
+
}
|
|
1372
1390
|
|
|
1373
1391
|
// src/logger.ts
|
|
1374
1392
|
var logger = console;
|
|
@@ -1383,6 +1401,13 @@ var SwapManager = class _SwapManager {
|
|
|
1383
1401
|
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1384
1402
|
*/
|
|
1385
1403
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1404
|
+
/**
|
|
1405
|
+
* Delay between re-attempts of a chain refund that left VTXOs deferred
|
|
1406
|
+
* (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
|
|
1407
|
+
* has elapsed). Boltz won't send another status update once the swap
|
|
1408
|
+
* is `swap.expired`, so the manager owns the local retry cadence.
|
|
1409
|
+
*/
|
|
1410
|
+
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1386
1411
|
swapProvider;
|
|
1387
1412
|
config;
|
|
1388
1413
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1401,6 +1426,11 @@ var SwapManager = class _SwapManager {
|
|
|
1401
1426
|
reconnectTimer = null;
|
|
1402
1427
|
initialPollTimer = null;
|
|
1403
1428
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1429
|
+
// Per-swap retry timers for chain refunds that left work undone
|
|
1430
|
+
// (refundArk returned `skipped > 0`). The swap is held in
|
|
1431
|
+
// `monitoredSwaps` past its terminal Boltz status until the local
|
|
1432
|
+
// refund completes or the manager stops.
|
|
1433
|
+
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1404
1434
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1405
1435
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1406
1436
|
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
@@ -1597,6 +1627,10 @@ var SwapManager = class _SwapManager {
|
|
|
1597
1627
|
clearTimeout(timer);
|
|
1598
1628
|
}
|
|
1599
1629
|
this.pollRetryTimers.clear();
|
|
1630
|
+
for (const timer of this.refundRetryTimers.values()) {
|
|
1631
|
+
clearTimeout(timer);
|
|
1632
|
+
}
|
|
1633
|
+
this.refundRetryTimers.clear();
|
|
1600
1634
|
this.notFoundCounts.clear();
|
|
1601
1635
|
}
|
|
1602
1636
|
/**
|
|
@@ -1653,6 +1687,11 @@ var SwapManager = class _SwapManager {
|
|
|
1653
1687
|
clearTimeout(retryTimer);
|
|
1654
1688
|
this.pollRetryTimers.delete(swapId);
|
|
1655
1689
|
}
|
|
1690
|
+
const refundRetryTimer = this.refundRetryTimers.get(swapId);
|
|
1691
|
+
if (refundRetryTimer) {
|
|
1692
|
+
clearTimeout(refundRetryTimer);
|
|
1693
|
+
this.refundRetryTimers.delete(swapId);
|
|
1694
|
+
}
|
|
1656
1695
|
this.notFoundCounts.delete(swapId);
|
|
1657
1696
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1658
1697
|
}
|
|
@@ -1924,15 +1963,57 @@ var SwapManager = class _SwapManager {
|
|
|
1924
1963
|
await this.executeAutonomousAction(swap);
|
|
1925
1964
|
}
|
|
1926
1965
|
if (this.isFinalStatus(swap)) {
|
|
1927
|
-
this.
|
|
1928
|
-
|
|
1929
|
-
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1930
|
-
if (retryTimer) {
|
|
1931
|
-
clearTimeout(retryTimer);
|
|
1932
|
-
this.pollRetryTimers.delete(swap.id);
|
|
1966
|
+
if (this.refundRetryTimers.has(swap.id)) {
|
|
1967
|
+
return;
|
|
1933
1968
|
}
|
|
1934
|
-
this.
|
|
1969
|
+
this.finalizeMonitoredSwap(swap);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Drop a swap from monitoring and emit the terminal completion event.
|
|
1974
|
+
* Shared between the on-status-update finalization path and the
|
|
1975
|
+
* refund-retry finalization path (used when a previously-deferred
|
|
1976
|
+
* chain refund has finished its remaining work).
|
|
1977
|
+
*/
|
|
1978
|
+
finalizeMonitoredSwap(swap) {
|
|
1979
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1980
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1981
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1982
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1983
|
+
if (retryTimer) {
|
|
1984
|
+
clearTimeout(retryTimer);
|
|
1985
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1935
1986
|
}
|
|
1987
|
+
const refundRetry = this.refundRetryTimers.get(swap.id);
|
|
1988
|
+
if (refundRetry) {
|
|
1989
|
+
clearTimeout(refundRetry);
|
|
1990
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1991
|
+
}
|
|
1992
|
+
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Schedule another `executeAutonomousAction` run for a chain swap whose
|
|
1996
|
+
* refund left VTXOs deferred. After the retry completes, if no further
|
|
1997
|
+
* deferral was reported, finalize monitoring cleanup.
|
|
1998
|
+
*/
|
|
1999
|
+
scheduleRefundRetry(swap, delayMs) {
|
|
2000
|
+
const existing = this.refundRetryTimers.get(swap.id);
|
|
2001
|
+
if (existing) clearTimeout(existing);
|
|
2002
|
+
this.refundRetryTimers.set(
|
|
2003
|
+
swap.id,
|
|
2004
|
+
setTimeout(async () => {
|
|
2005
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2006
|
+
if (!this.isRunning) return;
|
|
2007
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
2008
|
+
try {
|
|
2009
|
+
await this.executeAutonomousAction(swap);
|
|
2010
|
+
} finally {
|
|
2011
|
+
if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
|
|
2012
|
+
this.finalizeMonitoredSwap(swap);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}, delayMs)
|
|
2016
|
+
);
|
|
1936
2017
|
}
|
|
1937
2018
|
/**
|
|
1938
2019
|
* Execute autonomous action based on swap status
|
|
@@ -1987,10 +2068,27 @@ var SwapManager = class _SwapManager {
|
|
|
1987
2068
|
} else if (isChainRefundableStatus(swap.status)) {
|
|
1988
2069
|
if (swap.request.from === "ARK") {
|
|
1989
2070
|
logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
(
|
|
1993
|
-
|
|
2071
|
+
try {
|
|
2072
|
+
const outcome = await this.executeRefundArkAction(swap);
|
|
2073
|
+
if (outcome && outcome.skipped > 0) {
|
|
2074
|
+
logger.log(
|
|
2075
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
2076
|
+
);
|
|
2077
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2078
|
+
}
|
|
2079
|
+
this.actionExecutedListeners.forEach(
|
|
2080
|
+
(listener) => listener(swap, "refundArk")
|
|
2081
|
+
);
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
logger.error(
|
|
2084
|
+
`Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
|
|
2085
|
+
error
|
|
2086
|
+
);
|
|
2087
|
+
this.swapFailedListeners.forEach(
|
|
2088
|
+
(listener) => listener(swap, error)
|
|
2089
|
+
);
|
|
2090
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2091
|
+
}
|
|
1994
2092
|
}
|
|
1995
2093
|
if (swap.request.from === "BTC") {
|
|
1996
2094
|
logger.warn(
|
|
@@ -2071,7 +2169,7 @@ var SwapManager = class _SwapManager {
|
|
|
2071
2169
|
logger.error("refundArk callback not set");
|
|
2072
2170
|
return;
|
|
2073
2171
|
}
|
|
2074
|
-
|
|
2172
|
+
return this.refundArkCallback(swap);
|
|
2075
2173
|
}
|
|
2076
2174
|
/**
|
|
2077
2175
|
* Execute sign server claim action for chain swap.
|
|
@@ -2171,9 +2269,7 @@ var SwapManager = class _SwapManager {
|
|
|
2171
2269
|
*/
|
|
2172
2270
|
async pollAllSwaps() {
|
|
2173
2271
|
if (this.monitoredSwaps.size === 0) return;
|
|
2174
|
-
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2175
|
-
(swap) => this.pollSingleSwap(swap)
|
|
2176
|
-
);
|
|
2272
|
+
const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
|
|
2177
2273
|
await Promise.allSettled(pollPromises);
|
|
2178
2274
|
}
|
|
2179
2275
|
async pollSingleSwap(swap) {
|
|
@@ -2226,6 +2322,7 @@ var SwapManager = class _SwapManager {
|
|
|
2226
2322
|
* Boltz endpoint).
|
|
2227
2323
|
*/
|
|
2228
2324
|
async handleSwapNotFound(swap) {
|
|
2325
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2229
2326
|
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2230
2327
|
this.notFoundCounts.set(swap.id, count);
|
|
2231
2328
|
logger.warn(
|
|
@@ -2246,6 +2343,7 @@ var SwapManager = class _SwapManager {
|
|
|
2246
2343
|
* 404s without recovering anything.
|
|
2247
2344
|
*/
|
|
2248
2345
|
async markSwapAsUnknownToProvider(swap) {
|
|
2346
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2249
2347
|
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2250
2348
|
this.notFoundCounts.delete(swap.id);
|
|
2251
2349
|
return;
|
|
@@ -2258,6 +2356,11 @@ var SwapManager = class _SwapManager {
|
|
|
2258
2356
|
clearTimeout(retryTimer);
|
|
2259
2357
|
this.pollRetryTimers.delete(swap.id);
|
|
2260
2358
|
}
|
|
2359
|
+
const refundRetryTimer = this.refundRetryTimers.get(swap.id);
|
|
2360
|
+
if (refundRetryTimer) {
|
|
2361
|
+
clearTimeout(refundRetryTimer);
|
|
2362
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2363
|
+
}
|
|
2261
2364
|
this.notFoundCounts.delete(swap.id);
|
|
2262
2365
|
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
2263
2366
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
@@ -3008,7 +3111,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3008
3111
|
await this.claimBtc(swap);
|
|
3009
3112
|
},
|
|
3010
3113
|
refundArk: async (swap) => {
|
|
3011
|
-
|
|
3114
|
+
return this.refundArk(swap);
|
|
3012
3115
|
},
|
|
3013
3116
|
signServerClaim: async (swap) => {
|
|
3014
3117
|
await this.signCooperativeClaimForServer(swap);
|
|
@@ -3221,51 +3324,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3221
3324
|
throw new Error(
|
|
3222
3325
|
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3223
3326
|
);
|
|
3224
|
-
let
|
|
3327
|
+
let unspentVtxos = [];
|
|
3328
|
+
let rawVtxos = [];
|
|
3225
3329
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
3226
|
-
const
|
|
3330
|
+
const result = await this.indexerProvider.getVtxos({
|
|
3227
3331
|
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
3228
3332
|
});
|
|
3229
|
-
|
|
3230
|
-
|
|
3333
|
+
rawVtxos = result.vtxos;
|
|
3334
|
+
unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
3335
|
+
if (unspentVtxos.length > 0) {
|
|
3231
3336
|
break;
|
|
3232
3337
|
}
|
|
3233
3338
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
3234
3339
|
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
3235
3340
|
}
|
|
3236
3341
|
}
|
|
3237
|
-
if (
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3342
|
+
if (unspentVtxos.length === 0) {
|
|
3343
|
+
if (rawVtxos.length === 0) {
|
|
3344
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
3345
|
+
}
|
|
3241
3346
|
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3242
3347
|
}
|
|
3243
|
-
const input = {
|
|
3244
|
-
...vtxo,
|
|
3245
|
-
tapLeafScript: vhtlcScript.claim(),
|
|
3246
|
-
tapTree: vhtlcScript.encode()
|
|
3247
|
-
};
|
|
3248
|
-
const output = {
|
|
3249
|
-
amount: BigInt(vtxo.value),
|
|
3250
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
3251
|
-
};
|
|
3252
3348
|
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
vhtlcScript
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3349
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3350
|
+
const claimErrors = [];
|
|
3351
|
+
let usedOffchainClaim = false;
|
|
3352
|
+
for (const vtxo of unspentVtxos) {
|
|
3353
|
+
const input = {
|
|
3354
|
+
...vtxo,
|
|
3355
|
+
tapLeafScript: vhtlcScript.claim(),
|
|
3356
|
+
tapTree: vhtlcScript.encode()
|
|
3357
|
+
};
|
|
3358
|
+
const output = {
|
|
3359
|
+
amount: BigInt(vtxo.value),
|
|
3360
|
+
script: outputScript
|
|
3361
|
+
};
|
|
3362
|
+
try {
|
|
3363
|
+
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
3364
|
+
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
3365
|
+
} else {
|
|
3366
|
+
await claimVHTLCwithOffchainTx(
|
|
3367
|
+
vhtlcIdentity,
|
|
3368
|
+
vhtlcScript,
|
|
3369
|
+
serverXOnly,
|
|
3370
|
+
input,
|
|
3371
|
+
output,
|
|
3372
|
+
arkInfo,
|
|
3373
|
+
this.arkProvider
|
|
3374
|
+
);
|
|
3375
|
+
usedOffchainClaim = true;
|
|
3376
|
+
}
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
claimErrors.push({ vtxo, error });
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
if (claimErrors.length > 0) {
|
|
3382
|
+
const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
|
|
3383
|
+
throw new Error(
|
|
3384
|
+
`Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
|
|
3266
3385
|
);
|
|
3267
|
-
finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
|
|
3268
3386
|
}
|
|
3387
|
+
const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
|
|
3269
3388
|
await updateReverseSwapStatus(
|
|
3270
3389
|
pendingSwap,
|
|
3271
3390
|
finalStatus,
|
|
@@ -3640,6 +3759,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3640
3759
|
if (boltzCallCount > 0) {
|
|
3641
3760
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3642
3761
|
}
|
|
3762
|
+
boltzCallCount++;
|
|
3643
3763
|
await refundVHTLCwithOffchainTx(
|
|
3644
3764
|
pendingSwap.id,
|
|
3645
3765
|
this.wallet.identity,
|
|
@@ -3652,7 +3772,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3652
3772
|
arkInfo,
|
|
3653
3773
|
this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3654
3774
|
);
|
|
3655
|
-
boltzCallCount++;
|
|
3656
3775
|
sweptCount++;
|
|
3657
3776
|
} catch (error) {
|
|
3658
3777
|
if (!(error instanceof BoltzRefundError)) {
|
|
@@ -4150,8 +4269,17 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4150
4269
|
await this.swapProvider.postBtcTransaction(claimTx.hex);
|
|
4151
4270
|
}
|
|
4152
4271
|
/**
|
|
4153
|
-
* When an ARK to BTC swap fails, refund
|
|
4272
|
+
* When an ARK to BTC swap fails, refund every unspent VTXO at the chain
|
|
4273
|
+
* swap's ARK lockup address.
|
|
4274
|
+
*
|
|
4275
|
+
* Path selection per VTXO:
|
|
4276
|
+
* - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
|
|
4277
|
+
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
|
|
4278
|
+
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
|
|
4279
|
+
*
|
|
4154
4280
|
* @param pendingSwap - The pending chain swap to refund.
|
|
4281
|
+
* @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
|
|
4282
|
+
* the call was a no-op — callers should retry after CLTV.
|
|
4155
4283
|
*/
|
|
4156
4284
|
async refundArk(pendingSwap) {
|
|
4157
4285
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
@@ -4175,21 +4303,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4175
4303
|
"boltz",
|
|
4176
4304
|
pendingSwap.id
|
|
4177
4305
|
);
|
|
4178
|
-
const vhtlcPkScript = import_sdk8.ArkAddress.decode(
|
|
4179
|
-
pendingSwap.response.lockupDetails.lockupAddress
|
|
4180
|
-
).pkScript;
|
|
4181
|
-
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4182
|
-
scripts: [import_base9.hex.encode(vhtlcPkScript)]
|
|
4183
|
-
});
|
|
4184
|
-
if (vtxos.length === 0) {
|
|
4185
|
-
throw new Error(
|
|
4186
|
-
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4187
|
-
);
|
|
4188
|
-
}
|
|
4189
|
-
const vtxo = vtxos[0];
|
|
4190
|
-
if (vtxo.isSpent) {
|
|
4191
|
-
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4192
|
-
}
|
|
4193
4306
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4194
4307
|
network: arkInfo.network,
|
|
4195
4308
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4205,37 +4318,105 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4205
4318
|
message: "Unable to claim: invalid VHTLC address"
|
|
4206
4319
|
});
|
|
4207
4320
|
}
|
|
4208
|
-
const
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
const output = {
|
|
4215
|
-
amount: BigInt(vtxo.value),
|
|
4216
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4217
|
-
};
|
|
4218
|
-
if (isRecoverableVtxo) {
|
|
4219
|
-
await this.joinBatch(this.wallet.identity, input, output, arkInfo);
|
|
4220
|
-
} else {
|
|
4221
|
-
await refundVHTLCwithOffchainTx(
|
|
4222
|
-
pendingSwap.id,
|
|
4223
|
-
this.wallet.identity,
|
|
4224
|
-
this.arkProvider,
|
|
4225
|
-
boltzXOnlyPublicKey,
|
|
4226
|
-
ourXOnlyPublicKey,
|
|
4227
|
-
serverXOnlyPublicKey,
|
|
4228
|
-
input,
|
|
4229
|
-
output,
|
|
4230
|
-
arkInfo,
|
|
4231
|
-
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4321
|
+
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4322
|
+
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
4323
|
+
});
|
|
4324
|
+
if (vtxos.length === 0) {
|
|
4325
|
+
throw new Error(
|
|
4326
|
+
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4232
4327
|
);
|
|
4233
4328
|
}
|
|
4329
|
+
const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
4330
|
+
if (unspentVtxos.length === 0) {
|
|
4331
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4332
|
+
}
|
|
4333
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
4334
|
+
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
4335
|
+
const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
|
|
4336
|
+
let boltzCallCount = 0;
|
|
4337
|
+
let sweptCount = 0;
|
|
4338
|
+
let skippedCount = 0;
|
|
4339
|
+
for (const vtxo of unspentVtxos) {
|
|
4340
|
+
const isRecoverableVtxo = (0, import_sdk8.isRecoverable)(vtxo);
|
|
4341
|
+
const output = {
|
|
4342
|
+
amount: BigInt(vtxo.value),
|
|
4343
|
+
script: outputScript
|
|
4344
|
+
};
|
|
4345
|
+
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4346
|
+
const input2 = {
|
|
4347
|
+
...vtxo,
|
|
4348
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4349
|
+
tapTree: vhtlcScript.encode()
|
|
4350
|
+
};
|
|
4351
|
+
await this.joinBatch(
|
|
4352
|
+
this.wallet.identity,
|
|
4353
|
+
input2,
|
|
4354
|
+
output,
|
|
4355
|
+
arkInfo,
|
|
4356
|
+
isRecoverableVtxo
|
|
4357
|
+
);
|
|
4358
|
+
sweptCount++;
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4361
|
+
if (isRecoverableVtxo) {
|
|
4362
|
+
logger.error(
|
|
4363
|
+
`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.`
|
|
4364
|
+
);
|
|
4365
|
+
skippedCount++;
|
|
4366
|
+
continue;
|
|
4367
|
+
}
|
|
4368
|
+
const input = {
|
|
4369
|
+
...vtxo,
|
|
4370
|
+
tapLeafScript: vhtlcScript.refund(),
|
|
4371
|
+
tapTree: vhtlcScript.encode()
|
|
4372
|
+
};
|
|
4373
|
+
try {
|
|
4374
|
+
if (boltzCallCount > 0) {
|
|
4375
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4376
|
+
}
|
|
4377
|
+
boltzCallCount++;
|
|
4378
|
+
await refundVHTLCwithOffchainTx(
|
|
4379
|
+
pendingSwap.id,
|
|
4380
|
+
this.wallet.identity,
|
|
4381
|
+
this.arkProvider,
|
|
4382
|
+
boltzXOnlyPublicKey,
|
|
4383
|
+
ourXOnlyPublicKey,
|
|
4384
|
+
serverXOnlyPublicKey,
|
|
4385
|
+
input,
|
|
4386
|
+
output,
|
|
4387
|
+
arkInfo,
|
|
4388
|
+
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4389
|
+
);
|
|
4390
|
+
sweptCount++;
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
if (!(error instanceof BoltzRefundError)) {
|
|
4393
|
+
throw error;
|
|
4394
|
+
}
|
|
4395
|
+
if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4396
|
+
logger.error(
|
|
4397
|
+
`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.`
|
|
4398
|
+
);
|
|
4399
|
+
skippedCount++;
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
logger.warn(
|
|
4403
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
|
|
4404
|
+
);
|
|
4405
|
+
const fallbackInput = {
|
|
4406
|
+
...vtxo,
|
|
4407
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4408
|
+
tapTree: vhtlcScript.encode()
|
|
4409
|
+
};
|
|
4410
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
4411
|
+
sweptCount++;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4234
4414
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4235
4415
|
await this.savePendingChainSwap({
|
|
4236
4416
|
...pendingSwap,
|
|
4237
4417
|
status: finalStatus.status
|
|
4238
4418
|
});
|
|
4419
|
+
return { swept: sweptCount, skipped: skippedCount };
|
|
4239
4420
|
}
|
|
4240
4421
|
// =========================================================================
|
|
4241
4422
|
// Chain swaps: BTC -> ARK
|
|
@@ -4656,7 +4837,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4656
4837
|
this.validateQuoteOptions(options);
|
|
4657
4838
|
const floor = await this.resolveQuoteFloor(swapId, options);
|
|
4658
4839
|
const slippageBps = options?.maxSlippageBps ?? 0;
|
|
4659
|
-
|
|
4840
|
+
const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
|
|
4841
|
+
if (effectiveFloor < 1) {
|
|
4842
|
+
throw new TypeError(
|
|
4843
|
+
`Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
|
|
4844
|
+
);
|
|
4845
|
+
}
|
|
4846
|
+
return effectiveFloor;
|
|
4660
4847
|
}
|
|
4661
4848
|
async resolveQuoteFloor(swapId, options) {
|
|
4662
4849
|
if (options?.minAcceptableAmount !== void 0) {
|
|
@@ -4692,7 +4879,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4692
4879
|
}
|
|
4693
4880
|
}
|
|
4694
4881
|
validateQuote(amount, effectiveFloor) {
|
|
4695
|
-
if (!(amount
|
|
4882
|
+
if (!Number.isSafeInteger(amount)) {
|
|
4883
|
+
throw new QuoteRejectedError({
|
|
4884
|
+
reason: "non_safe_integer",
|
|
4885
|
+
quotedAmount: amount
|
|
4886
|
+
});
|
|
4887
|
+
}
|
|
4888
|
+
if (amount <= 0) {
|
|
4696
4889
|
throw new QuoteRejectedError({
|
|
4697
4890
|
reason: "non_positive",
|
|
4698
4891
|
quotedAmount: amount
|
|
@@ -4875,20 +5068,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4875
5068
|
onchainAmount: amount,
|
|
4876
5069
|
lockupAddress,
|
|
4877
5070
|
refundPublicKey: serverPublicKey,
|
|
4878
|
-
timeoutBlockHeights: timeoutBlockHeights
|
|
4879
|
-
refund: extractTimeLockFromLeafOutput(
|
|
4880
|
-
tree.refundWithoutBoltzLeaf?.output ?? ""
|
|
4881
|
-
),
|
|
4882
|
-
unilateralClaim: extractTimeLockFromLeafOutput(
|
|
4883
|
-
tree.unilateralClaimLeaf?.output ?? ""
|
|
4884
|
-
),
|
|
4885
|
-
unilateralRefund: extractTimeLockFromLeafOutput(
|
|
4886
|
-
tree.unilateralRefundLeaf?.output ?? ""
|
|
4887
|
-
),
|
|
4888
|
-
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
4889
|
-
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
4890
|
-
)
|
|
4891
|
-
}
|
|
5071
|
+
timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4892
5072
|
},
|
|
4893
5073
|
status,
|
|
4894
5074
|
type: "reverse",
|
|
@@ -4921,26 +5101,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4921
5101
|
address: lockupAddress,
|
|
4922
5102
|
expectedAmount: amount,
|
|
4923
5103
|
claimPublicKey: serverPublicKey,
|
|
4924
|
-
timeoutBlockHeights: timeoutBlockHeights
|
|
4925
|
-
refund: extractTimeLockFromLeafOutput(
|
|
4926
|
-
tree.refundWithoutBoltzLeaf?.output ?? ""
|
|
4927
|
-
),
|
|
4928
|
-
unilateralClaim: extractTimeLockFromLeafOutput(
|
|
4929
|
-
tree.unilateralClaimLeaf?.output ?? ""
|
|
4930
|
-
),
|
|
4931
|
-
unilateralRefund: extractTimeLockFromLeafOutput(
|
|
4932
|
-
tree.unilateralRefundLeaf?.output ?? ""
|
|
4933
|
-
),
|
|
4934
|
-
unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
|
|
4935
|
-
tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
|
|
4936
|
-
)
|
|
4937
|
-
}
|
|
5104
|
+
timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4938
5105
|
}
|
|
4939
5106
|
});
|
|
4940
5107
|
} else if (isRestoredChainSwap(swap)) {
|
|
4941
5108
|
const refundDetails = swap.refundDetails;
|
|
4942
5109
|
if (!refundDetails) continue;
|
|
4943
|
-
const {
|
|
5110
|
+
const {
|
|
5111
|
+
amount,
|
|
5112
|
+
lockupAddress,
|
|
5113
|
+
serverPublicKey,
|
|
5114
|
+
timeoutBlockHeight,
|
|
5115
|
+
tree,
|
|
5116
|
+
timeoutBlockHeights
|
|
5117
|
+
} = refundDetails;
|
|
4944
5118
|
chainSwaps.push({
|
|
4945
5119
|
id,
|
|
4946
5120
|
type: "chain",
|
|
@@ -4966,7 +5140,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4966
5140
|
amount,
|
|
4967
5141
|
lockupAddress,
|
|
4968
5142
|
serverPublicKey,
|
|
4969
|
-
timeoutBlockHeight
|
|
5143
|
+
timeoutBlockHeight,
|
|
5144
|
+
timeouts: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
|
|
4970
5145
|
}
|
|
4971
5146
|
}
|
|
4972
5147
|
});
|
|
@@ -5087,6 +5262,7 @@ function createBackgroundWalletShim(args) {
|
|
|
5087
5262
|
getBoardingUtxos: async () => notImplemented("getBoardingUtxos"),
|
|
5088
5263
|
getTransactionHistory: async () => notImplemented("getTransactionHistory"),
|
|
5089
5264
|
getContractManager: async () => notImplemented("getContractManager"),
|
|
5265
|
+
getDelegateManager: async () => notImplemented("getDelegateManager"),
|
|
5090
5266
|
getDelegatorManager: async () => notImplemented("getDelegatorManager"),
|
|
5091
5267
|
sendBitcoin: async () => notImplemented("sendBitcoin"),
|
|
5092
5268
|
send: async () => notImplemented("send"),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-
|
|
2
|
-
export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-
|
|
1
|
+
import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-BpAqG0V6.cjs';
|
|
2
|
+
export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-BpAqG0V6.cjs';
|
|
3
3
|
import '@arkade-os/sdk/worker/expo';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../types
|
|
5
|
+
import '../types--axEWA8c.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Define the Expo background task handler for swap polling.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-
|
|
2
|
-
export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-
|
|
1
|
+
import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-DFVOAy_-.js';
|
|
2
|
+
export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-DFVOAy_-.js';
|
|
3
3
|
import '@arkade-os/sdk/worker/expo';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../types
|
|
5
|
+
import '../types--axEWA8c.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Define the Expo background task handler for swap polling.
|
package/dist/expo/background.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SWAP_POLL_TASK_TYPE,
|
|
3
3
|
swapsPollProcessor
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-H6F67K2A.js";
|
|
5
5
|
import {
|
|
6
6
|
BoltzSwapProvider
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-B4CYBKFJ.js";
|
|
8
8
|
import "../chunk-SJQJQO7P.js";
|
|
9
9
|
|
|
10
10
|
// src/expo/background.ts
|
|
@@ -28,6 +28,7 @@ function createBackgroundWalletShim(args) {
|
|
|
28
28
|
getBoardingUtxos: async () => notImplemented("getBoardingUtxos"),
|
|
29
29
|
getTransactionHistory: async () => notImplemented("getTransactionHistory"),
|
|
30
30
|
getContractManager: async () => notImplemented("getContractManager"),
|
|
31
|
+
getDelegateManager: async () => notImplemented("getDelegateManager"),
|
|
31
32
|
getDelegatorManager: async () => notImplemented("getDelegatorManager"),
|
|
32
33
|
sendBitcoin: async () => notImplemented("sendBitcoin"),
|
|
33
34
|
send: async () => notImplemented("send"),
|