@arkade-os/boltz-swap 0.3.33 → 0.3.34
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-HNQDJOLM.js → chunk-5K2FS2FE.js} +1 -1
- package/dist/{chunk-SJ5SYSMK.js → chunk-TDBUZE4N.js} +269 -90
- package/dist/expo/background.cjs +270 -90
- 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 +269 -90
- 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 +305 -94
- 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 +2 -2
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 {
|
|
@@ -1373,6 +1377,13 @@ var SwapManager = class _SwapManager {
|
|
|
1373
1377
|
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1374
1378
|
*/
|
|
1375
1379
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1380
|
+
/**
|
|
1381
|
+
* Delay between re-attempts of a chain refund that left VTXOs deferred
|
|
1382
|
+
* (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
|
|
1383
|
+
* has elapsed). Boltz won't send another status update once the swap
|
|
1384
|
+
* is `swap.expired`, so the manager owns the local retry cadence.
|
|
1385
|
+
*/
|
|
1386
|
+
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1376
1387
|
swapProvider;
|
|
1377
1388
|
config;
|
|
1378
1389
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1391,6 +1402,11 @@ var SwapManager = class _SwapManager {
|
|
|
1391
1402
|
reconnectTimer = null;
|
|
1392
1403
|
initialPollTimer = null;
|
|
1393
1404
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1405
|
+
// Per-swap retry timers for chain refunds that left work undone
|
|
1406
|
+
// (refundArk returned `skipped > 0`). The swap is held in
|
|
1407
|
+
// `monitoredSwaps` past its terminal Boltz status until the local
|
|
1408
|
+
// refund completes or the manager stops.
|
|
1409
|
+
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1394
1410
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1395
1411
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1396
1412
|
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
@@ -1587,6 +1603,10 @@ var SwapManager = class _SwapManager {
|
|
|
1587
1603
|
clearTimeout(timer);
|
|
1588
1604
|
}
|
|
1589
1605
|
this.pollRetryTimers.clear();
|
|
1606
|
+
for (const timer of this.refundRetryTimers.values()) {
|
|
1607
|
+
clearTimeout(timer);
|
|
1608
|
+
}
|
|
1609
|
+
this.refundRetryTimers.clear();
|
|
1590
1610
|
this.notFoundCounts.clear();
|
|
1591
1611
|
}
|
|
1592
1612
|
/**
|
|
@@ -1643,6 +1663,11 @@ var SwapManager = class _SwapManager {
|
|
|
1643
1663
|
clearTimeout(retryTimer);
|
|
1644
1664
|
this.pollRetryTimers.delete(swapId);
|
|
1645
1665
|
}
|
|
1666
|
+
const refundRetryTimer = this.refundRetryTimers.get(swapId);
|
|
1667
|
+
if (refundRetryTimer) {
|
|
1668
|
+
clearTimeout(refundRetryTimer);
|
|
1669
|
+
this.refundRetryTimers.delete(swapId);
|
|
1670
|
+
}
|
|
1646
1671
|
this.notFoundCounts.delete(swapId);
|
|
1647
1672
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1648
1673
|
}
|
|
@@ -1914,15 +1939,57 @@ var SwapManager = class _SwapManager {
|
|
|
1914
1939
|
await this.executeAutonomousAction(swap);
|
|
1915
1940
|
}
|
|
1916
1941
|
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);
|
|
1942
|
+
if (this.refundRetryTimers.has(swap.id)) {
|
|
1943
|
+
return;
|
|
1923
1944
|
}
|
|
1924
|
-
this.
|
|
1945
|
+
this.finalizeMonitoredSwap(swap);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Drop a swap from monitoring and emit the terminal completion event.
|
|
1950
|
+
* Shared between the on-status-update finalization path and the
|
|
1951
|
+
* refund-retry finalization path (used when a previously-deferred
|
|
1952
|
+
* chain refund has finished its remaining work).
|
|
1953
|
+
*/
|
|
1954
|
+
finalizeMonitoredSwap(swap) {
|
|
1955
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1956
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1957
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1958
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1959
|
+
if (retryTimer) {
|
|
1960
|
+
clearTimeout(retryTimer);
|
|
1961
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1962
|
+
}
|
|
1963
|
+
const refundRetry = this.refundRetryTimers.get(swap.id);
|
|
1964
|
+
if (refundRetry) {
|
|
1965
|
+
clearTimeout(refundRetry);
|
|
1966
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1925
1967
|
}
|
|
1968
|
+
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Schedule another `executeAutonomousAction` run for a chain swap whose
|
|
1972
|
+
* refund left VTXOs deferred. After the retry completes, if no further
|
|
1973
|
+
* deferral was reported, finalize monitoring cleanup.
|
|
1974
|
+
*/
|
|
1975
|
+
scheduleRefundRetry(swap, delayMs) {
|
|
1976
|
+
const existing = this.refundRetryTimers.get(swap.id);
|
|
1977
|
+
if (existing) clearTimeout(existing);
|
|
1978
|
+
this.refundRetryTimers.set(
|
|
1979
|
+
swap.id,
|
|
1980
|
+
setTimeout(async () => {
|
|
1981
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1982
|
+
if (!this.isRunning) return;
|
|
1983
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1984
|
+
try {
|
|
1985
|
+
await this.executeAutonomousAction(swap);
|
|
1986
|
+
} finally {
|
|
1987
|
+
if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
|
|
1988
|
+
this.finalizeMonitoredSwap(swap);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}, delayMs)
|
|
1992
|
+
);
|
|
1926
1993
|
}
|
|
1927
1994
|
/**
|
|
1928
1995
|
* Execute autonomous action based on swap status
|
|
@@ -1977,10 +2044,27 @@ var SwapManager = class _SwapManager {
|
|
|
1977
2044
|
} else if (isChainRefundableStatus(swap.status)) {
|
|
1978
2045
|
if (swap.request.from === "ARK") {
|
|
1979
2046
|
logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
(
|
|
1983
|
-
|
|
2047
|
+
try {
|
|
2048
|
+
const outcome = await this.executeRefundArkAction(swap);
|
|
2049
|
+
if (outcome && outcome.skipped > 0) {
|
|
2050
|
+
logger.log(
|
|
2051
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
2052
|
+
);
|
|
2053
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2054
|
+
}
|
|
2055
|
+
this.actionExecutedListeners.forEach(
|
|
2056
|
+
(listener) => listener(swap, "refundArk")
|
|
2057
|
+
);
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
logger.error(
|
|
2060
|
+
`Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
|
|
2061
|
+
error
|
|
2062
|
+
);
|
|
2063
|
+
this.swapFailedListeners.forEach(
|
|
2064
|
+
(listener) => listener(swap, error)
|
|
2065
|
+
);
|
|
2066
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2067
|
+
}
|
|
1984
2068
|
}
|
|
1985
2069
|
if (swap.request.from === "BTC") {
|
|
1986
2070
|
logger.warn(
|
|
@@ -2061,7 +2145,7 @@ var SwapManager = class _SwapManager {
|
|
|
2061
2145
|
logger.error("refundArk callback not set");
|
|
2062
2146
|
return;
|
|
2063
2147
|
}
|
|
2064
|
-
|
|
2148
|
+
return this.refundArkCallback(swap);
|
|
2065
2149
|
}
|
|
2066
2150
|
/**
|
|
2067
2151
|
* Execute sign server claim action for chain swap.
|
|
@@ -2161,9 +2245,7 @@ var SwapManager = class _SwapManager {
|
|
|
2161
2245
|
*/
|
|
2162
2246
|
async pollAllSwaps() {
|
|
2163
2247
|
if (this.monitoredSwaps.size === 0) return;
|
|
2164
|
-
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2165
|
-
(swap) => this.pollSingleSwap(swap)
|
|
2166
|
-
);
|
|
2248
|
+
const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
|
|
2167
2249
|
await Promise.allSettled(pollPromises);
|
|
2168
2250
|
}
|
|
2169
2251
|
async pollSingleSwap(swap) {
|
|
@@ -2216,6 +2298,7 @@ var SwapManager = class _SwapManager {
|
|
|
2216
2298
|
* Boltz endpoint).
|
|
2217
2299
|
*/
|
|
2218
2300
|
async handleSwapNotFound(swap) {
|
|
2301
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2219
2302
|
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2220
2303
|
this.notFoundCounts.set(swap.id, count);
|
|
2221
2304
|
logger.warn(
|
|
@@ -2236,6 +2319,7 @@ var SwapManager = class _SwapManager {
|
|
|
2236
2319
|
* 404s without recovering anything.
|
|
2237
2320
|
*/
|
|
2238
2321
|
async markSwapAsUnknownToProvider(swap) {
|
|
2322
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2239
2323
|
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2240
2324
|
this.notFoundCounts.delete(swap.id);
|
|
2241
2325
|
return;
|
|
@@ -2248,6 +2332,11 @@ var SwapManager = class _SwapManager {
|
|
|
2248
2332
|
clearTimeout(retryTimer);
|
|
2249
2333
|
this.pollRetryTimers.delete(swap.id);
|
|
2250
2334
|
}
|
|
2335
|
+
const refundRetryTimer = this.refundRetryTimers.get(swap.id);
|
|
2336
|
+
if (refundRetryTimer) {
|
|
2337
|
+
clearTimeout(refundRetryTimer);
|
|
2338
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2339
|
+
}
|
|
2251
2340
|
this.notFoundCounts.delete(swap.id);
|
|
2252
2341
|
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
2253
2342
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
@@ -2998,7 +3087,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
2998
3087
|
await this.claimBtc(swap);
|
|
2999
3088
|
},
|
|
3000
3089
|
refundArk: async (swap) => {
|
|
3001
|
-
|
|
3090
|
+
return this.refundArk(swap);
|
|
3002
3091
|
},
|
|
3003
3092
|
signServerClaim: async (swap) => {
|
|
3004
3093
|
await this.signCooperativeClaimForServer(swap);
|
|
@@ -3211,51 +3300,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3211
3300
|
throw new Error(
|
|
3212
3301
|
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3213
3302
|
);
|
|
3214
|
-
let
|
|
3303
|
+
let unspentVtxos = [];
|
|
3304
|
+
let rawVtxos = [];
|
|
3215
3305
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
3216
|
-
const
|
|
3306
|
+
const result = await this.indexerProvider.getVtxos({
|
|
3217
3307
|
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
3218
3308
|
});
|
|
3219
|
-
|
|
3220
|
-
|
|
3309
|
+
rawVtxos = result.vtxos;
|
|
3310
|
+
unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
3311
|
+
if (unspentVtxos.length > 0) {
|
|
3221
3312
|
break;
|
|
3222
3313
|
}
|
|
3223
3314
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
3224
3315
|
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
3225
3316
|
}
|
|
3226
3317
|
}
|
|
3227
|
-
if (
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3318
|
+
if (unspentVtxos.length === 0) {
|
|
3319
|
+
if (rawVtxos.length === 0) {
|
|
3320
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
3321
|
+
}
|
|
3231
3322
|
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3232
3323
|
}
|
|
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
3324
|
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
vhtlcScript
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3325
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3326
|
+
const claimErrors = [];
|
|
3327
|
+
let usedOffchainClaim = false;
|
|
3328
|
+
for (const vtxo of unspentVtxos) {
|
|
3329
|
+
const input = {
|
|
3330
|
+
...vtxo,
|
|
3331
|
+
tapLeafScript: vhtlcScript.claim(),
|
|
3332
|
+
tapTree: vhtlcScript.encode()
|
|
3333
|
+
};
|
|
3334
|
+
const output = {
|
|
3335
|
+
amount: BigInt(vtxo.value),
|
|
3336
|
+
script: outputScript
|
|
3337
|
+
};
|
|
3338
|
+
try {
|
|
3339
|
+
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
3340
|
+
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
3341
|
+
} else {
|
|
3342
|
+
await claimVHTLCwithOffchainTx(
|
|
3343
|
+
vhtlcIdentity,
|
|
3344
|
+
vhtlcScript,
|
|
3345
|
+
serverXOnly,
|
|
3346
|
+
input,
|
|
3347
|
+
output,
|
|
3348
|
+
arkInfo,
|
|
3349
|
+
this.arkProvider
|
|
3350
|
+
);
|
|
3351
|
+
usedOffchainClaim = true;
|
|
3352
|
+
}
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
claimErrors.push({ vtxo, error });
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
if (claimErrors.length > 0) {
|
|
3358
|
+
const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
|
|
3359
|
+
throw new Error(
|
|
3360
|
+
`Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
|
|
3256
3361
|
);
|
|
3257
|
-
finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
|
|
3258
3362
|
}
|
|
3363
|
+
const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
|
|
3259
3364
|
await updateReverseSwapStatus(
|
|
3260
3365
|
pendingSwap,
|
|
3261
3366
|
finalStatus,
|
|
@@ -3630,6 +3735,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3630
3735
|
if (boltzCallCount > 0) {
|
|
3631
3736
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3632
3737
|
}
|
|
3738
|
+
boltzCallCount++;
|
|
3633
3739
|
await refundVHTLCwithOffchainTx(
|
|
3634
3740
|
pendingSwap.id,
|
|
3635
3741
|
this.wallet.identity,
|
|
@@ -3642,7 +3748,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3642
3748
|
arkInfo,
|
|
3643
3749
|
this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3644
3750
|
);
|
|
3645
|
-
boltzCallCount++;
|
|
3646
3751
|
sweptCount++;
|
|
3647
3752
|
} catch (error) {
|
|
3648
3753
|
if (!(error instanceof BoltzRefundError)) {
|
|
@@ -4140,8 +4245,17 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4140
4245
|
await this.swapProvider.postBtcTransaction(claimTx.hex);
|
|
4141
4246
|
}
|
|
4142
4247
|
/**
|
|
4143
|
-
* When an ARK to BTC swap fails, refund
|
|
4248
|
+
* When an ARK to BTC swap fails, refund every unspent VTXO at the chain
|
|
4249
|
+
* swap's ARK lockup address.
|
|
4250
|
+
*
|
|
4251
|
+
* Path selection per VTXO:
|
|
4252
|
+
* - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
|
|
4253
|
+
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
|
|
4254
|
+
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
|
|
4255
|
+
*
|
|
4144
4256
|
* @param pendingSwap - The pending chain swap to refund.
|
|
4257
|
+
* @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
|
|
4258
|
+
* the call was a no-op — callers should retry after CLTV.
|
|
4145
4259
|
*/
|
|
4146
4260
|
async refundArk(pendingSwap) {
|
|
4147
4261
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
@@ -4165,21 +4279,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4165
4279
|
"boltz",
|
|
4166
4280
|
pendingSwap.id
|
|
4167
4281
|
);
|
|
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
4282
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4184
4283
|
network: arkInfo.network,
|
|
4185
4284
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4195,37 +4294,105 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4195
4294
|
message: "Unable to claim: invalid VHTLC address"
|
|
4196
4295
|
});
|
|
4197
4296
|
}
|
|
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)
|
|
4297
|
+
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4298
|
+
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
4299
|
+
});
|
|
4300
|
+
if (vtxos.length === 0) {
|
|
4301
|
+
throw new Error(
|
|
4302
|
+
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4222
4303
|
);
|
|
4223
4304
|
}
|
|
4305
|
+
const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
4306
|
+
if (unspentVtxos.length === 0) {
|
|
4307
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4308
|
+
}
|
|
4309
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
4310
|
+
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
4311
|
+
const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
|
|
4312
|
+
let boltzCallCount = 0;
|
|
4313
|
+
let sweptCount = 0;
|
|
4314
|
+
let skippedCount = 0;
|
|
4315
|
+
for (const vtxo of unspentVtxos) {
|
|
4316
|
+
const isRecoverableVtxo = (0, import_sdk8.isRecoverable)(vtxo);
|
|
4317
|
+
const output = {
|
|
4318
|
+
amount: BigInt(vtxo.value),
|
|
4319
|
+
script: outputScript
|
|
4320
|
+
};
|
|
4321
|
+
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4322
|
+
const input2 = {
|
|
4323
|
+
...vtxo,
|
|
4324
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4325
|
+
tapTree: vhtlcScript.encode()
|
|
4326
|
+
};
|
|
4327
|
+
await this.joinBatch(
|
|
4328
|
+
this.wallet.identity,
|
|
4329
|
+
input2,
|
|
4330
|
+
output,
|
|
4331
|
+
arkInfo,
|
|
4332
|
+
isRecoverableVtxo
|
|
4333
|
+
);
|
|
4334
|
+
sweptCount++;
|
|
4335
|
+
continue;
|
|
4336
|
+
}
|
|
4337
|
+
if (isRecoverableVtxo) {
|
|
4338
|
+
logger.error(
|
|
4339
|
+
`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.`
|
|
4340
|
+
);
|
|
4341
|
+
skippedCount++;
|
|
4342
|
+
continue;
|
|
4343
|
+
}
|
|
4344
|
+
const input = {
|
|
4345
|
+
...vtxo,
|
|
4346
|
+
tapLeafScript: vhtlcScript.refund(),
|
|
4347
|
+
tapTree: vhtlcScript.encode()
|
|
4348
|
+
};
|
|
4349
|
+
try {
|
|
4350
|
+
if (boltzCallCount > 0) {
|
|
4351
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4352
|
+
}
|
|
4353
|
+
boltzCallCount++;
|
|
4354
|
+
await refundVHTLCwithOffchainTx(
|
|
4355
|
+
pendingSwap.id,
|
|
4356
|
+
this.wallet.identity,
|
|
4357
|
+
this.arkProvider,
|
|
4358
|
+
boltzXOnlyPublicKey,
|
|
4359
|
+
ourXOnlyPublicKey,
|
|
4360
|
+
serverXOnlyPublicKey,
|
|
4361
|
+
input,
|
|
4362
|
+
output,
|
|
4363
|
+
arkInfo,
|
|
4364
|
+
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4365
|
+
);
|
|
4366
|
+
sweptCount++;
|
|
4367
|
+
} catch (error) {
|
|
4368
|
+
if (!(error instanceof BoltzRefundError)) {
|
|
4369
|
+
throw error;
|
|
4370
|
+
}
|
|
4371
|
+
if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4372
|
+
logger.error(
|
|
4373
|
+
`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.`
|
|
4374
|
+
);
|
|
4375
|
+
skippedCount++;
|
|
4376
|
+
continue;
|
|
4377
|
+
}
|
|
4378
|
+
logger.warn(
|
|
4379
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
|
|
4380
|
+
);
|
|
4381
|
+
const fallbackInput = {
|
|
4382
|
+
...vtxo,
|
|
4383
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4384
|
+
tapTree: vhtlcScript.encode()
|
|
4385
|
+
};
|
|
4386
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
4387
|
+
sweptCount++;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4224
4390
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4225
4391
|
await this.savePendingChainSwap({
|
|
4226
4392
|
...pendingSwap,
|
|
4227
4393
|
status: finalStatus.status
|
|
4228
4394
|
});
|
|
4395
|
+
return { swept: sweptCount, skipped: skippedCount };
|
|
4229
4396
|
}
|
|
4230
4397
|
// =========================================================================
|
|
4231
4398
|
// Chain swaps: BTC -> ARK
|
|
@@ -4646,7 +4813,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4646
4813
|
this.validateQuoteOptions(options);
|
|
4647
4814
|
const floor = await this.resolveQuoteFloor(swapId, options);
|
|
4648
4815
|
const slippageBps = options?.maxSlippageBps ?? 0;
|
|
4649
|
-
|
|
4816
|
+
const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
|
|
4817
|
+
if (effectiveFloor < 1) {
|
|
4818
|
+
throw new TypeError(
|
|
4819
|
+
`Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
return effectiveFloor;
|
|
4650
4823
|
}
|
|
4651
4824
|
async resolveQuoteFloor(swapId, options) {
|
|
4652
4825
|
if (options?.minAcceptableAmount !== void 0) {
|
|
@@ -4682,7 +4855,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4682
4855
|
}
|
|
4683
4856
|
}
|
|
4684
4857
|
validateQuote(amount, effectiveFloor) {
|
|
4685
|
-
if (!(amount
|
|
4858
|
+
if (!Number.isSafeInteger(amount)) {
|
|
4859
|
+
throw new QuoteRejectedError({
|
|
4860
|
+
reason: "non_safe_integer",
|
|
4861
|
+
quotedAmount: amount
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
if (amount <= 0) {
|
|
4686
4865
|
throw new QuoteRejectedError({
|
|
4687
4866
|
reason: "non_positive",
|
|
4688
4867
|
quotedAmount: amount
|
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