@arkade-os/boltz-swap 0.3.32 → 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/README.md +22 -26
- package/dist/{arkade-swaps-CS8FZSVL.d.cts → arkade-swaps-BXAD1s8j.d.ts} +69 -8
- package/dist/{arkade-swaps-WiKCanCL.d.ts → arkade-swaps-CfMets16.d.cts} +69 -8
- package/dist/{chunk-NHBWNN6H.js → chunk-5K2FS2FE.js} +8 -27
- package/dist/chunk-SJQJQO7P.js +25 -0
- package/dist/{chunk-B3Q4TFWT.js → chunk-TDBUZE4N.js} +817 -866
- package/dist/expo/background.cjs +840 -892
- package/dist/expo/background.d.cts +3 -3
- package/dist/expo/background.d.ts +3 -3
- package/dist/expo/background.js +9 -20
- package/dist/expo/index.cjs +840 -870
- package/dist/expo/index.d.cts +8 -6
- package/dist/expo/index.d.ts +8 -6
- package/dist/expo/index.js +17 -20
- package/dist/index.cjs +1034 -1022
- package/dist/index.d.cts +95 -11
- package/dist/index.d.ts +95 -11
- package/dist/index.js +164 -119
- package/dist/repositories/realm/index.cjs +10 -22
- package/dist/repositories/realm/index.d.cts +7 -5
- package/dist/repositories/realm/index.d.ts +7 -5
- package/dist/repositories/realm/index.js +8 -22
- package/dist/repositories/sqlite/index.cjs +12 -23
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.js +10 -23
- package/dist/{swapsPollProcessor-BF3uTFae.d.cts → swapsPollProcessor-BpAqG0V6.d.cts} +3 -3
- package/dist/{swapsPollProcessor-wYOMzldd.d.ts → swapsPollProcessor-DFVOAy_-.d.ts} +3 -3
- package/dist/{types-BBI7-KJ0.d.cts → types--axEWA8c.d.cts} +42 -4
- package/dist/{types-BBI7-KJ0.d.ts → types--axEWA8c.d.ts} +42 -4
- package/package.json +10 -25
package/dist/expo/background.cjs
CHANGED
|
@@ -41,6 +41,7 @@ var TaskManager = __toESM(require("expo-task-manager"), 1);
|
|
|
41
41
|
var BackgroundTask = __toESM(require("expo-background-task"), 1);
|
|
42
42
|
var import_expo = require("@arkade-os/sdk/worker/expo");
|
|
43
43
|
var import_expo2 = require("@arkade-os/sdk/adapters/expo");
|
|
44
|
+
var import_sdk9 = require("@arkade-os/sdk");
|
|
44
45
|
|
|
45
46
|
// src/boltz-swap-provider.ts
|
|
46
47
|
var import_sdk = require("@arkade-os/sdk");
|
|
@@ -54,7 +55,10 @@ var SwapError = class extends Error {
|
|
|
54
55
|
/** The pending swap associated with this error, if available. */
|
|
55
56
|
pendingSwap;
|
|
56
57
|
constructor(options = {}) {
|
|
57
|
-
super(
|
|
58
|
+
super(
|
|
59
|
+
options.message ?? "Error during swap.",
|
|
60
|
+
options.cause !== void 0 ? { cause: options.cause } : void 0
|
|
61
|
+
);
|
|
58
62
|
this.name = "SwapError";
|
|
59
63
|
this.isClaimable = options.isClaimable ?? false;
|
|
60
64
|
this.isRefundable = options.isRefundable ?? false;
|
|
@@ -131,6 +135,100 @@ var TransactionRefundedError = class extends SwapError {
|
|
|
131
135
|
this.name = "TransactionRefundedError";
|
|
132
136
|
}
|
|
133
137
|
};
|
|
138
|
+
var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
139
|
+
reason;
|
|
140
|
+
quotedAmount;
|
|
141
|
+
floor;
|
|
142
|
+
constructor(options) {
|
|
143
|
+
super({
|
|
144
|
+
message: options.message ?? _QuoteRejectedError.defaultMessage(options),
|
|
145
|
+
...options
|
|
146
|
+
});
|
|
147
|
+
this.name = "QuoteRejectedError";
|
|
148
|
+
this.reason = options.reason;
|
|
149
|
+
this.quotedAmount = "quotedAmount" in options ? options.quotedAmount : void 0;
|
|
150
|
+
this.floor = "floor" in options ? options.floor : void 0;
|
|
151
|
+
}
|
|
152
|
+
static defaultMessage(options) {
|
|
153
|
+
switch (options.reason) {
|
|
154
|
+
case "below_floor":
|
|
155
|
+
return `Boltz quote ${options.quotedAmount} is below acceptable floor ${options.floor}`;
|
|
156
|
+
case "non_positive":
|
|
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`;
|
|
160
|
+
case "no_baseline":
|
|
161
|
+
return "Cannot accept quote: no minAcceptableAmount and no stored pending swap";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Serialize into a plain `Error` whose `.message` carries the full
|
|
166
|
+
* rejection payload as JSON behind a marker prefix. Structured clone
|
|
167
|
+
* (used by `postMessage` between page and service worker) preserves
|
|
168
|
+
* `Error.message` reliably but strips custom `.name` and own properties,
|
|
169
|
+
* so we move the typed data into the message field for transport.
|
|
170
|
+
*/
|
|
171
|
+
toTransportError() {
|
|
172
|
+
return new Error(
|
|
173
|
+
QUOTE_REJECTION_TRANSPORT_PREFIX + JSON.stringify({
|
|
174
|
+
reason: this.reason,
|
|
175
|
+
message: this.message,
|
|
176
|
+
quotedAmount: this.quotedAmount,
|
|
177
|
+
floor: this.floor
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Inverse of `toTransportError`. Returns a real `QuoteRejectedError` if
|
|
183
|
+
* `error` carries the transport prefix, else `null`.
|
|
184
|
+
*/
|
|
185
|
+
static fromTransportError(error) {
|
|
186
|
+
if (!(error instanceof Error) || !error.message.startsWith(QUOTE_REJECTION_TRANSPORT_PREFIX)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const payload = error.message.slice(QUOTE_REJECTION_TRANSPORT_PREFIX.length);
|
|
190
|
+
let data;
|
|
191
|
+
try {
|
|
192
|
+
data = JSON.parse(payload);
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
if (typeof data.reason !== "string" || !QUOTE_REJECTION_REASONS.has(data.reason)) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const message = typeof data.message === "string" ? data.message : void 0;
|
|
200
|
+
const reason = data.reason;
|
|
201
|
+
const quotedAmount = typeof data.quotedAmount === "number" ? data.quotedAmount : null;
|
|
202
|
+
const floor = typeof data.floor === "number" ? data.floor : null;
|
|
203
|
+
switch (reason) {
|
|
204
|
+
case "below_floor":
|
|
205
|
+
if (quotedAmount === null || floor === null) return null;
|
|
206
|
+
return new _QuoteRejectedError({
|
|
207
|
+
reason,
|
|
208
|
+
quotedAmount,
|
|
209
|
+
floor,
|
|
210
|
+
message
|
|
211
|
+
});
|
|
212
|
+
case "non_positive":
|
|
213
|
+
case "non_safe_integer":
|
|
214
|
+
if (quotedAmount === null) return null;
|
|
215
|
+
return new _QuoteRejectedError({
|
|
216
|
+
reason,
|
|
217
|
+
quotedAmount,
|
|
218
|
+
message
|
|
219
|
+
});
|
|
220
|
+
case "no_baseline":
|
|
221
|
+
return new _QuoteRejectedError({ reason, message });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var QUOTE_REJECTION_TRANSPORT_PREFIX = "QUOTE_REJECTED::";
|
|
226
|
+
var QUOTE_REJECTION_REASONS = /* @__PURE__ */ new Set([
|
|
227
|
+
"below_floor",
|
|
228
|
+
"non_positive",
|
|
229
|
+
"non_safe_integer",
|
|
230
|
+
"no_baseline"
|
|
231
|
+
]);
|
|
134
232
|
var BoltzRefundError = class extends Error {
|
|
135
233
|
constructor(message, cause) {
|
|
136
234
|
super(message);
|
|
@@ -142,18 +240,10 @@ var BoltzRefundError = class extends Error {
|
|
|
142
240
|
// src/boltz-swap-provider.ts
|
|
143
241
|
var import_base = require("@scure/base");
|
|
144
242
|
var isSubmarineFinalStatus = (status) => {
|
|
145
|
-
return [
|
|
146
|
-
"invoice.failedToPay",
|
|
147
|
-
"transaction.claimed",
|
|
148
|
-
"swap.expired"
|
|
149
|
-
].includes(status);
|
|
243
|
+
return ["invoice.failedToPay", "transaction.claimed", "swap.expired"].includes(status);
|
|
150
244
|
};
|
|
151
245
|
var isSubmarineRefundableStatus = (status) => {
|
|
152
|
-
return [
|
|
153
|
-
"invoice.failedToPay",
|
|
154
|
-
"transaction.lockupFailed",
|
|
155
|
-
"swap.expired"
|
|
156
|
-
].includes(status);
|
|
246
|
+
return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
|
|
157
247
|
};
|
|
158
248
|
var isSubmarineSuccessStatus = (status) => {
|
|
159
249
|
return status === "transaction.claimed";
|
|
@@ -172,10 +262,7 @@ var isReverseClaimableStatus = (status) => {
|
|
|
172
262
|
return ["transaction.mempool", "transaction.confirmed"].includes(status);
|
|
173
263
|
};
|
|
174
264
|
var isChainClaimableStatus = (status) => {
|
|
175
|
-
return [
|
|
176
|
-
"transaction.server.mempool",
|
|
177
|
-
"transaction.server.confirmed"
|
|
178
|
-
].includes(status);
|
|
265
|
+
return ["transaction.server.mempool", "transaction.server.confirmed"].includes(status);
|
|
179
266
|
};
|
|
180
267
|
var isChainFinalStatus = (status) => {
|
|
181
268
|
return [
|
|
@@ -292,8 +379,7 @@ var BASE_URLS = {
|
|
|
292
379
|
var isSwapNotFoundBody = (error) => {
|
|
293
380
|
const needle = "could not find swap";
|
|
294
381
|
const fromJson = error.errorData?.error;
|
|
295
|
-
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle))
|
|
296
|
-
return true;
|
|
382
|
+
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle)) return true;
|
|
297
383
|
return error.message.toLowerCase().includes(needle);
|
|
298
384
|
};
|
|
299
385
|
var BoltzSwapProvider = class {
|
|
@@ -307,10 +393,7 @@ var BoltzSwapProvider = class {
|
|
|
307
393
|
this.network = config.network;
|
|
308
394
|
this.referralId = config.referralId ?? "arkade-ts-sdk";
|
|
309
395
|
const apiUrl = config.apiUrl || BASE_URLS[config.network];
|
|
310
|
-
if (!apiUrl)
|
|
311
|
-
throw new Error(
|
|
312
|
-
`API URL is required for network: ${config.network}`
|
|
313
|
-
);
|
|
396
|
+
if (!apiUrl) throw new Error(`API URL is required for network: ${config.network}`);
|
|
314
397
|
this.apiUrl = apiUrl;
|
|
315
398
|
this.wsUrl = this.apiUrl.replace(/^http(s)?:\/\//, "ws$1://").replace("9069", "9004") + "/v2/ws";
|
|
316
399
|
}
|
|
@@ -329,10 +412,7 @@ var BoltzSwapProvider = class {
|
|
|
329
412
|
/** Returns current Lightning swap fees (submarine + reverse) from Boltz. */
|
|
330
413
|
async getFees() {
|
|
331
414
|
const [submarine, reverse] = await Promise.all([
|
|
332
|
-
this.request(
|
|
333
|
-
"/v2/swap/submarine",
|
|
334
|
-
"GET"
|
|
335
|
-
),
|
|
415
|
+
this.request("/v2/swap/submarine", "GET"),
|
|
336
416
|
this.request("/v2/swap/reverse", "GET")
|
|
337
417
|
]);
|
|
338
418
|
if (!isGetSubmarinePairsResponse(submarine))
|
|
@@ -352,10 +432,7 @@ var BoltzSwapProvider = class {
|
|
|
352
432
|
}
|
|
353
433
|
/** Returns current Lightning swap min/max limits from Boltz. */
|
|
354
434
|
async getLimits() {
|
|
355
|
-
const response = await this.request(
|
|
356
|
-
"/v2/swap/submarine",
|
|
357
|
-
"GET"
|
|
358
|
-
);
|
|
435
|
+
const response = await this.request("/v2/swap/submarine", "GET");
|
|
359
436
|
if (!isGetSubmarinePairsResponse(response))
|
|
360
437
|
throw new SchemaError({ message: "error fetching limits" });
|
|
361
438
|
return {
|
|
@@ -365,10 +442,7 @@ var BoltzSwapProvider = class {
|
|
|
365
442
|
}
|
|
366
443
|
/** Returns the current BTC chain tip height from Boltz. */
|
|
367
444
|
async getChainHeight() {
|
|
368
|
-
const response = await this.request(
|
|
369
|
-
"/v2/chain/heights",
|
|
370
|
-
"GET"
|
|
371
|
-
);
|
|
445
|
+
const response = await this.request("/v2/chain/heights", "GET");
|
|
372
446
|
if (typeof response?.BTC !== "number")
|
|
373
447
|
throw new SchemaError({
|
|
374
448
|
message: "error fetching chain heights"
|
|
@@ -398,10 +472,7 @@ var BoltzSwapProvider = class {
|
|
|
398
472
|
async getSwapStatus(id) {
|
|
399
473
|
let response;
|
|
400
474
|
try {
|
|
401
|
-
response = await this.request(
|
|
402
|
-
`/v2/swap/${id}`,
|
|
403
|
-
"GET"
|
|
404
|
-
);
|
|
475
|
+
response = await this.request(`/v2/swap/${id}`, "GET");
|
|
405
476
|
} catch (error) {
|
|
406
477
|
if (error instanceof NetworkError && error.statusCode === 404 && isSwapNotFoundBody(error)) {
|
|
407
478
|
throw new SwapNotFoundError(id, error.errorData);
|
|
@@ -497,12 +568,10 @@ var BoltzSwapProvider = class {
|
|
|
497
568
|
throw new SwapError({ message: "Invalid 'to' chain" });
|
|
498
569
|
if (["BTC", "ARK"].indexOf(from) === -1)
|
|
499
570
|
throw new SwapError({ message: "Invalid 'from' chain" });
|
|
500
|
-
if (to === from)
|
|
501
|
-
throw new SwapError({ message: "Invalid swap direction" });
|
|
571
|
+
if (to === from) throw new SwapError({ message: "Invalid swap direction" });
|
|
502
572
|
if (!preimageHash || preimageHash.length != 64)
|
|
503
573
|
throw new SwapError({ message: "Invalid preimageHash" });
|
|
504
|
-
if (feeSatsPerByte <= 0)
|
|
505
|
-
throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
574
|
+
if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
506
575
|
if (serverLockAmount !== void 0 && userLockAmount !== void 0 || serverLockAmount === void 0 && userLockAmount === void 0)
|
|
507
576
|
throw new SwapError({
|
|
508
577
|
message: "Either serverLockAmount or userLockAmount must be provided"
|
|
@@ -557,12 +626,8 @@ var BoltzSwapProvider = class {
|
|
|
557
626
|
message: "Error refunding submarine swap"
|
|
558
627
|
});
|
|
559
628
|
return {
|
|
560
|
-
transaction: import_sdk.Transaction.fromPSBT(
|
|
561
|
-
|
|
562
|
-
),
|
|
563
|
-
checkpoint: import_sdk.Transaction.fromPSBT(
|
|
564
|
-
import_base.base64.decode(response.checkpoint)
|
|
565
|
-
)
|
|
629
|
+
transaction: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.transaction)),
|
|
630
|
+
checkpoint: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.checkpoint))
|
|
566
631
|
};
|
|
567
632
|
}
|
|
568
633
|
/** Requests Boltz co-signature for a chain swap refund. Returns signed transaction + checkpoint. */
|
|
@@ -581,53 +646,53 @@ var BoltzSwapProvider = class {
|
|
|
581
646
|
message: "Error refunding chain swap"
|
|
582
647
|
});
|
|
583
648
|
return {
|
|
584
|
-
transaction: import_sdk.Transaction.fromPSBT(
|
|
585
|
-
|
|
586
|
-
),
|
|
587
|
-
checkpoint: import_sdk.Transaction.fromPSBT(
|
|
588
|
-
import_base.base64.decode(response.checkpoint)
|
|
589
|
-
)
|
|
649
|
+
transaction: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.transaction)),
|
|
650
|
+
checkpoint: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.checkpoint))
|
|
590
651
|
};
|
|
591
652
|
}
|
|
592
|
-
/**
|
|
653
|
+
/**
|
|
654
|
+
* Monitors swap status updates and forwards them to the update callback.
|
|
655
|
+
* Prefers a WebSocket subscription; on connection error or premature close
|
|
656
|
+
* it falls back to REST polling so callers don't fail when the WS endpoint
|
|
657
|
+
* is flaky (observed in CI). Resolves when the swap reaches a terminal
|
|
658
|
+
* status.
|
|
659
|
+
*/
|
|
593
660
|
async monitorSwap(swapId, update) {
|
|
594
661
|
return new Promise((resolve, reject) => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
);
|
|
662
|
+
let settled = false;
|
|
663
|
+
let lastStatus = null;
|
|
664
|
+
let pollTimer = null;
|
|
665
|
+
let webSocket = null;
|
|
666
|
+
let connectionTimeout = null;
|
|
667
|
+
const cleanup = () => {
|
|
668
|
+
if (connectionTimeout) {
|
|
669
|
+
clearTimeout(connectionTimeout);
|
|
670
|
+
connectionTimeout = null;
|
|
671
|
+
}
|
|
672
|
+
if (pollTimer) {
|
|
673
|
+
clearInterval(pollTimer);
|
|
674
|
+
pollTimer = null;
|
|
675
|
+
}
|
|
676
|
+
if (webSocket) {
|
|
677
|
+
try {
|
|
678
|
+
webSocket.close();
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
webSocket = null;
|
|
682
|
+
}
|
|
617
683
|
};
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
684
|
+
const finish = (err) => {
|
|
685
|
+
if (settled) return;
|
|
686
|
+
settled = true;
|
|
687
|
+
cleanup();
|
|
688
|
+
if (err) reject(err);
|
|
689
|
+
else resolve();
|
|
621
690
|
};
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
reject(new SwapError({ message: msg.args[0].error }));
|
|
628
|
-
}
|
|
629
|
-
const status = msg.args[0].status;
|
|
630
|
-
const negotiable = status === "transaction.lockupFailed" && msg.args[0].failureDetails?.actual !== void 0 && msg.args[0].failureDetails?.expected !== void 0;
|
|
691
|
+
const handleStatus = (status, data) => {
|
|
692
|
+
if (settled) return;
|
|
693
|
+
if (status === lastStatus) return;
|
|
694
|
+
lastStatus = status;
|
|
695
|
+
const negotiable = status === "transaction.lockupFailed" && data?.failureDetails?.actual !== void 0 && data?.failureDetails?.expected !== void 0;
|
|
631
696
|
switch (status) {
|
|
632
697
|
case "invoice.settled":
|
|
633
698
|
case "transaction.claimed":
|
|
@@ -636,13 +701,13 @@ var BoltzSwapProvider = class {
|
|
|
636
701
|
case "invoice.failedToPay":
|
|
637
702
|
case "transaction.failed":
|
|
638
703
|
case "swap.expired":
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
704
|
+
update(status, data);
|
|
705
|
+
finish();
|
|
706
|
+
return;
|
|
642
707
|
case "transaction.lockupFailed":
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
708
|
+
update(status, data);
|
|
709
|
+
if (!negotiable) finish();
|
|
710
|
+
return;
|
|
646
711
|
case "invoice.paid":
|
|
647
712
|
case "invoice.pending":
|
|
648
713
|
case "invoice.set":
|
|
@@ -652,9 +717,77 @@ var BoltzSwapProvider = class {
|
|
|
652
717
|
case "transaction.claim.pending":
|
|
653
718
|
case "transaction.server.mempool":
|
|
654
719
|
case "transaction.server.confirmed":
|
|
655
|
-
update(status,
|
|
720
|
+
update(status, data);
|
|
721
|
+
return;
|
|
656
722
|
}
|
|
657
723
|
};
|
|
724
|
+
const startPolling = () => {
|
|
725
|
+
if (settled || pollTimer) return;
|
|
726
|
+
const poll = async () => {
|
|
727
|
+
if (settled) return;
|
|
728
|
+
try {
|
|
729
|
+
const result = await this.getSwapStatus(swapId);
|
|
730
|
+
handleStatus(result.status, { ...result, id: swapId });
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
pollTimer = setInterval(poll, 1e3);
|
|
735
|
+
poll();
|
|
736
|
+
};
|
|
737
|
+
try {
|
|
738
|
+
webSocket = new globalThis.WebSocket(this.wsUrl);
|
|
739
|
+
connectionTimeout = setTimeout(() => {
|
|
740
|
+
try {
|
|
741
|
+
webSocket?.close();
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
webSocket = null;
|
|
745
|
+
startPolling();
|
|
746
|
+
}, 5e3);
|
|
747
|
+
webSocket.onerror = () => {
|
|
748
|
+
if (connectionTimeout) {
|
|
749
|
+
clearTimeout(connectionTimeout);
|
|
750
|
+
connectionTimeout = null;
|
|
751
|
+
}
|
|
752
|
+
webSocket = null;
|
|
753
|
+
startPolling();
|
|
754
|
+
};
|
|
755
|
+
webSocket.onopen = () => {
|
|
756
|
+
if (connectionTimeout) {
|
|
757
|
+
clearTimeout(connectionTimeout);
|
|
758
|
+
connectionTimeout = null;
|
|
759
|
+
}
|
|
760
|
+
webSocket?.send(
|
|
761
|
+
JSON.stringify({
|
|
762
|
+
op: "subscribe",
|
|
763
|
+
channel: "swap.update",
|
|
764
|
+
args: [swapId]
|
|
765
|
+
})
|
|
766
|
+
);
|
|
767
|
+
};
|
|
768
|
+
webSocket.onclose = () => {
|
|
769
|
+
if (connectionTimeout) {
|
|
770
|
+
clearTimeout(connectionTimeout);
|
|
771
|
+
connectionTimeout = null;
|
|
772
|
+
}
|
|
773
|
+
if (!settled && !pollTimer) startPolling();
|
|
774
|
+
};
|
|
775
|
+
webSocket.onmessage = (rawMsg) => {
|
|
776
|
+
try {
|
|
777
|
+
const msg = JSON.parse(rawMsg.data);
|
|
778
|
+
if (msg.event !== "update" || msg.args?.[0]?.id !== swapId) return;
|
|
779
|
+
if (msg.args[0].error) {
|
|
780
|
+
finish(new SwapError({ message: msg.args[0].error }));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
handleStatus(msg.args[0].status, msg.args[0]);
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
} catch {
|
|
788
|
+
webSocket = null;
|
|
789
|
+
startPolling();
|
|
790
|
+
}
|
|
658
791
|
});
|
|
659
792
|
}
|
|
660
793
|
/** Returns current chain swap fees for a given pair (e.g. ARK→BTC). */
|
|
@@ -662,10 +795,7 @@ var BoltzSwapProvider = class {
|
|
|
662
795
|
if (from === to) {
|
|
663
796
|
throw new SwapError({ message: "Invalid chain pair" });
|
|
664
797
|
}
|
|
665
|
-
const response = await this.request(
|
|
666
|
-
"/v2/swap/chain",
|
|
667
|
-
"GET"
|
|
668
|
-
);
|
|
798
|
+
const response = await this.request("/v2/swap/chain", "GET");
|
|
669
799
|
if (!isGetChainPairsResponse(response))
|
|
670
800
|
throw new SchemaError({ message: "error fetching fees" });
|
|
671
801
|
if (!response[from]?.[to]) {
|
|
@@ -680,10 +810,7 @@ var BoltzSwapProvider = class {
|
|
|
680
810
|
if (from === to) {
|
|
681
811
|
throw new SwapError({ message: "Invalid chain pair" });
|
|
682
812
|
}
|
|
683
|
-
const response = await this.request(
|
|
684
|
-
"/v2/swap/chain",
|
|
685
|
-
"GET"
|
|
686
|
-
);
|
|
813
|
+
const response = await this.request("/v2/swap/chain", "GET");
|
|
687
814
|
if (!isGetChainPairsResponse(response))
|
|
688
815
|
throw new SchemaError({ message: "error fetching limits" });
|
|
689
816
|
if (!response[from]?.[to]) {
|
|
@@ -812,9 +939,7 @@ var BoltzSwapProvider = class {
|
|
|
812
939
|
return await response.json();
|
|
813
940
|
} catch (error) {
|
|
814
941
|
if (error instanceof NetworkError) throw error;
|
|
815
|
-
throw new NetworkError(
|
|
816
|
-
`Request to ${url} failed: ${error.message}`
|
|
817
|
-
);
|
|
942
|
+
throw new NetworkError(`Request to ${url} failed: ${error.message}`);
|
|
818
943
|
}
|
|
819
944
|
}
|
|
820
945
|
};
|
|
@@ -837,8 +962,7 @@ var findKeyIndex = (keys, target) => keys.findIndex((k) => (0, import_utils.equa
|
|
|
837
962
|
var assertPublicKeys = (keys) => {
|
|
838
963
|
const seen = /* @__PURE__ */ new Set();
|
|
839
964
|
for (const key of keys) {
|
|
840
|
-
if (key.length !== 33)
|
|
841
|
-
throw new Error(`public key must be 33 bytes, got ${key.length}`);
|
|
965
|
+
if (key.length !== 33) throw new Error(`public key must be 33 bytes, got ${key.length}`);
|
|
842
966
|
const enc = import_base2.hex.encode(key);
|
|
843
967
|
if (seen.has(enc)) throw new Error(`duplicate public key ${enc}`);
|
|
844
968
|
seen.add(enc);
|
|
@@ -846,9 +970,7 @@ var assertPublicKeys = (keys) => {
|
|
|
846
970
|
};
|
|
847
971
|
var aggregateKeys = (publicKeys, tweak) => {
|
|
848
972
|
assertPublicKeys([...publicKeys]);
|
|
849
|
-
return (0, import_musig2.keyAggExport)(
|
|
850
|
-
(0, import_musig2.keyAggregate)([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : [])
|
|
851
|
-
);
|
|
973
|
+
return (0, import_musig2.keyAggExport)((0, import_musig2.keyAggregate)([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : []));
|
|
852
974
|
};
|
|
853
975
|
var MusigKeyAgg = class _MusigKeyAgg {
|
|
854
976
|
constructor(privateKey, myPublicKey, publicKeys, myIndex, aggPubkey, internalKey, _tweak) {
|
|
@@ -895,18 +1017,12 @@ var MusigWithMessage = class {
|
|
|
895
1017
|
this.msg = msg;
|
|
896
1018
|
}
|
|
897
1019
|
generateNonce() {
|
|
898
|
-
const nonce = (0, import_musig2.nonceGen)(
|
|
899
|
-
this.myPublicKey,
|
|
900
|
-
this.privateKey,
|
|
901
|
-
this.aggPubkey,
|
|
902
|
-
this.msg
|
|
903
|
-
);
|
|
1020
|
+
const nonce = (0, import_musig2.nonceGen)(this.myPublicKey, this.privateKey, this.aggPubkey, this.msg);
|
|
904
1021
|
return new MusigWithNonce(
|
|
905
1022
|
this.privateKey,
|
|
906
1023
|
this.myPublicKey,
|
|
907
1024
|
this.publicKeys,
|
|
908
1025
|
this.myIndex,
|
|
909
|
-
this.aggPubkey,
|
|
910
1026
|
this.tweak,
|
|
911
1027
|
this.msg,
|
|
912
1028
|
nonce
|
|
@@ -914,12 +1030,11 @@ var MusigWithMessage = class {
|
|
|
914
1030
|
}
|
|
915
1031
|
};
|
|
916
1032
|
var MusigWithNonce = class {
|
|
917
|
-
constructor(privateKey, myPublicKey, publicKeys, myIndex,
|
|
1033
|
+
constructor(privateKey, myPublicKey, publicKeys, myIndex, tweak, msg, nonce) {
|
|
918
1034
|
this.privateKey = privateKey;
|
|
919
1035
|
this.myPublicKey = myPublicKey;
|
|
920
1036
|
this.publicKeys = publicKeys;
|
|
921
1037
|
this.myIndex = myIndex;
|
|
922
|
-
this.aggPubkey = aggPubkey;
|
|
923
1038
|
this.tweak = tweak;
|
|
924
1039
|
this.msg = msg;
|
|
925
1040
|
this.nonce = nonce;
|
|
@@ -951,10 +1066,8 @@ var MusigWithNonce = class {
|
|
|
951
1066
|
const aggregatedNonce = (0, import_musig2.nonceAggregate)([...ordered]);
|
|
952
1067
|
return new MusigNoncesAggregated(
|
|
953
1068
|
this.privateKey,
|
|
954
|
-
this.myPublicKey,
|
|
955
1069
|
this.publicKeys,
|
|
956
1070
|
this.myIndex,
|
|
957
|
-
this.aggPubkey,
|
|
958
1071
|
this.tweak,
|
|
959
1072
|
this.msg,
|
|
960
1073
|
this.nonce,
|
|
@@ -964,12 +1077,10 @@ var MusigWithNonce = class {
|
|
|
964
1077
|
}
|
|
965
1078
|
};
|
|
966
1079
|
var MusigNoncesAggregated = class {
|
|
967
|
-
constructor(privateKey,
|
|
1080
|
+
constructor(privateKey, publicKeys, myIndex, tweak, msg, nonce, pubNonces, aggregatedNonce) {
|
|
968
1081
|
this.privateKey = privateKey;
|
|
969
|
-
this.myPublicKey = myPublicKey;
|
|
970
1082
|
this.publicKeys = publicKeys;
|
|
971
1083
|
this.myIndex = myIndex;
|
|
972
|
-
this.aggPubkey = aggPubkey;
|
|
973
1084
|
this.tweak = tweak;
|
|
974
1085
|
this.msg = msg;
|
|
975
1086
|
this.nonce = nonce;
|
|
@@ -1015,11 +1126,7 @@ var MusigSession = class {
|
|
|
1015
1126
|
const index = typeof publicKeyOrIndex === "number" ? publicKeyOrIndex : findKeyIndex(this.publicKeys, publicKeyOrIndex);
|
|
1016
1127
|
if (index < 0 || index >= this.publicKeys.length)
|
|
1017
1128
|
throw new Error("public key not found or index out of range");
|
|
1018
|
-
if (!this.session.partialSigVerify(
|
|
1019
|
-
signature,
|
|
1020
|
-
[...this.pubNonces],
|
|
1021
|
-
index
|
|
1022
|
-
)) {
|
|
1129
|
+
if (!this.session.partialSigVerify(signature, [...this.pubNonces], index)) {
|
|
1023
1130
|
throw new Error("invalid partial signature");
|
|
1024
1131
|
}
|
|
1025
1132
|
this.partialSignatures[index] = signature;
|
|
@@ -1028,12 +1135,7 @@ var MusigSession = class {
|
|
|
1028
1135
|
signPartial() {
|
|
1029
1136
|
const sig = this.session.sign(this.nonce.secret, this.privateKey, true);
|
|
1030
1137
|
this.partialSignatures[this.myIndex] = sig;
|
|
1031
|
-
return new MusigSigned(
|
|
1032
|
-
this.session,
|
|
1033
|
-
[...this.partialSignatures],
|
|
1034
|
-
sig,
|
|
1035
|
-
this.nonce.public
|
|
1036
|
-
);
|
|
1138
|
+
return new MusigSigned(this.session, [...this.partialSignatures], sig, this.nonce.public);
|
|
1037
1139
|
}
|
|
1038
1140
|
};
|
|
1039
1141
|
var MusigSigned = class {
|
|
@@ -1047,14 +1149,11 @@ var MusigSigned = class {
|
|
|
1047
1149
|
if (this.partialSignatures.some((s) => s === null)) {
|
|
1048
1150
|
throw new Error("not all partial signatures are set");
|
|
1049
1151
|
}
|
|
1050
|
-
return this.session.partialSigAgg(
|
|
1051
|
-
this.partialSignatures
|
|
1052
|
-
);
|
|
1152
|
+
return this.session.partialSigAgg(this.partialSignatures);
|
|
1053
1153
|
}
|
|
1054
1154
|
};
|
|
1055
1155
|
var create = (privateKey, publicKeys) => {
|
|
1056
|
-
if (publicKeys.length < 2)
|
|
1057
|
-
throw new Error("need at least 2 keys to aggregate");
|
|
1156
|
+
if (publicKeys.length < 2) throw new Error("need at least 2 keys to aggregate");
|
|
1058
1157
|
const keys = [...publicKeys];
|
|
1059
1158
|
assertPublicKeys(keys);
|
|
1060
1159
|
Object.freeze(keys);
|
|
@@ -1062,14 +1161,7 @@ var create = (privateKey, publicKeys) => {
|
|
|
1062
1161
|
const myIndex = findKeyIndex(keys, myPublicKey);
|
|
1063
1162
|
if (myIndex === -1) throw new Error("our key is not in publicKeys");
|
|
1064
1163
|
const aggPubkey = aggregateKeys(keys);
|
|
1065
|
-
return new MusigKeyAgg(
|
|
1066
|
-
privateKey,
|
|
1067
|
-
myPublicKey,
|
|
1068
|
-
keys,
|
|
1069
|
-
myIndex,
|
|
1070
|
-
aggPubkey,
|
|
1071
|
-
aggPubkey
|
|
1072
|
-
);
|
|
1164
|
+
return new MusigKeyAgg(privateKey, myPublicKey, keys, myIndex, aggPubkey, aggPubkey);
|
|
1073
1165
|
};
|
|
1074
1166
|
|
|
1075
1167
|
// src/utils/boltz-swap-tx.ts
|
|
@@ -1145,9 +1237,7 @@ var taprootHashTree = (tree) => {
|
|
|
1145
1237
|
};
|
|
1146
1238
|
var tweakMusig = (musig, tree) => {
|
|
1147
1239
|
const tweak = taprootHashTree(tree).hash;
|
|
1148
|
-
return musig.xonlyTweakAdd(
|
|
1149
|
-
import_secp256k12.schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak)
|
|
1150
|
-
);
|
|
1240
|
+
return musig.xonlyTweakAdd(import_secp256k12.schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak));
|
|
1151
1241
|
};
|
|
1152
1242
|
var toXOnly = (pubKey) => {
|
|
1153
1243
|
if (pubKey.length === 32) return pubKey;
|
|
@@ -1159,9 +1249,7 @@ var toXOnly = (pubKey) => {
|
|
|
1159
1249
|
}
|
|
1160
1250
|
return pubKey.subarray(1, 33);
|
|
1161
1251
|
}
|
|
1162
|
-
throw new Error(
|
|
1163
|
-
`Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`
|
|
1164
|
-
);
|
|
1252
|
+
throw new Error(`Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`);
|
|
1165
1253
|
};
|
|
1166
1254
|
var p2trScript = (publicKey) => import_btc_signer.Script.encode(["OP_1", toXOnly(publicKey)]);
|
|
1167
1255
|
var detectSwapOutput = (tweakedKey, transaction) => {
|
|
@@ -1176,8 +1264,7 @@ var detectSwapOutput = (tweakedKey, transaction) => {
|
|
|
1176
1264
|
};
|
|
1177
1265
|
var DUMMY_TAPROOT_SIGNATURE = new Uint8Array(64);
|
|
1178
1266
|
var constructClaimTransaction = (utxo, destinationScript, fee) => {
|
|
1179
|
-
if (fee < BigInt(0) || fee >= utxo.amount)
|
|
1180
|
-
throw new Error("fee exceeds utxo amount");
|
|
1267
|
+
if (fee < BigInt(0) || fee >= utxo.amount) throw new Error("fee exceeds utxo amount");
|
|
1181
1268
|
const tx = new import_btc_signer.Transaction({ version: 2 });
|
|
1182
1269
|
tx.addOutput({
|
|
1183
1270
|
amount: utxo.amount - fee,
|
|
@@ -1196,9 +1283,7 @@ var constructClaimTransaction = (utxo, destinationScript, fee) => {
|
|
|
1196
1283
|
};
|
|
1197
1284
|
var targetFee = (satPerVbyte, constructTx) => {
|
|
1198
1285
|
const tx = constructTx(BigInt(1));
|
|
1199
|
-
return constructTx(
|
|
1200
|
-
BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte))
|
|
1201
|
-
);
|
|
1286
|
+
return constructTx(BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte)));
|
|
1202
1287
|
};
|
|
1203
1288
|
|
|
1204
1289
|
// src/utils/decoding.ts
|
|
@@ -1206,9 +1291,7 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
|
|
|
1206
1291
|
var import_sdk2 = require("@arkade-os/sdk");
|
|
1207
1292
|
var decodeInvoice = (invoice) => {
|
|
1208
1293
|
const decoded = import_light_bolt11_decoder.default.decode(invoice);
|
|
1209
|
-
const millisats = Number(
|
|
1210
|
-
decoded.sections.find((s) => s.name === "amount")?.value ?? "0"
|
|
1211
|
-
);
|
|
1294
|
+
const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
|
|
1212
1295
|
return {
|
|
1213
1296
|
expiry: decoded.expiry ?? 3600,
|
|
1214
1297
|
amountSats: Math.floor(millisats / 1e3),
|
|
@@ -1271,10 +1354,7 @@ function extractTimeLockFromLeafOutput(scriptHex) {
|
|
|
1271
1354
|
const data = opcodes[hasCSV - 1];
|
|
1272
1355
|
if (data instanceof Uint8Array) {
|
|
1273
1356
|
const dataBytes = new Uint8Array(data).reverse();
|
|
1274
|
-
const {
|
|
1275
|
-
blocks,
|
|
1276
|
-
seconds
|
|
1277
|
-
} = import_bip68.default.decode(
|
|
1357
|
+
const { blocks, seconds } = import_bip68.default.decode(
|
|
1278
1358
|
parseInt(import_base5.hex.encode(dataBytes), 16)
|
|
1279
1359
|
);
|
|
1280
1360
|
return blocks ?? seconds ?? 0;
|
|
@@ -1307,6 +1387,13 @@ var SwapManager = class _SwapManager {
|
|
|
1307
1387
|
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1308
1388
|
*/
|
|
1309
1389
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1390
|
+
/**
|
|
1391
|
+
* Delay between re-attempts of a chain refund that left VTXOs deferred
|
|
1392
|
+
* (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
|
|
1393
|
+
* has elapsed). Boltz won't send another status update once the swap
|
|
1394
|
+
* is `swap.expired`, so the manager owns the local retry cadence.
|
|
1395
|
+
*/
|
|
1396
|
+
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1310
1397
|
swapProvider;
|
|
1311
1398
|
config;
|
|
1312
1399
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1325,6 +1412,11 @@ var SwapManager = class _SwapManager {
|
|
|
1325
1412
|
reconnectTimer = null;
|
|
1326
1413
|
initialPollTimer = null;
|
|
1327
1414
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1415
|
+
// Per-swap retry timers for chain refunds that left work undone
|
|
1416
|
+
// (refundArk returned `skipped > 0`). The swap is held in
|
|
1417
|
+
// `monitoredSwaps` past its terminal Boltz status until the local
|
|
1418
|
+
// refund completes or the manager stops.
|
|
1419
|
+
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1328
1420
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1329
1421
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1330
1422
|
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
@@ -1378,9 +1470,7 @@ var SwapManager = class _SwapManager {
|
|
|
1378
1470
|
this.wsConnectedListeners.add(config.events.onWebSocketConnected);
|
|
1379
1471
|
}
|
|
1380
1472
|
if (config.events?.onWebSocketDisconnected) {
|
|
1381
|
-
this.wsDisconnectedListeners.add(
|
|
1382
|
-
config.events.onWebSocketDisconnected
|
|
1383
|
-
);
|
|
1473
|
+
this.wsDisconnectedListeners.add(config.events.onWebSocketDisconnected);
|
|
1384
1474
|
}
|
|
1385
1475
|
this.currentReconnectDelay = this.config.reconnectDelayMs;
|
|
1386
1476
|
this.currentPollRetryDelay = this.config.pollRetryDelayMs;
|
|
@@ -1523,6 +1613,10 @@ var SwapManager = class _SwapManager {
|
|
|
1523
1613
|
clearTimeout(timer);
|
|
1524
1614
|
}
|
|
1525
1615
|
this.pollRetryTimers.clear();
|
|
1616
|
+
for (const timer of this.refundRetryTimers.values()) {
|
|
1617
|
+
clearTimeout(timer);
|
|
1618
|
+
}
|
|
1619
|
+
this.refundRetryTimers.clear();
|
|
1526
1620
|
this.notFoundCounts.clear();
|
|
1527
1621
|
}
|
|
1528
1622
|
/**
|
|
@@ -1531,9 +1625,7 @@ var SwapManager = class _SwapManager {
|
|
|
1531
1625
|
*/
|
|
1532
1626
|
setPollInterval(ms) {
|
|
1533
1627
|
if (ms <= 0) {
|
|
1534
|
-
throw new RangeError(
|
|
1535
|
-
`setPollInterval: ms must be a positive number, got ${ms}`
|
|
1536
|
-
);
|
|
1628
|
+
throw new RangeError(`setPollInterval: ms must be a positive number, got ${ms}`);
|
|
1537
1629
|
}
|
|
1538
1630
|
const cappedInterval = Math.min(ms, this.config.maxPollIntervalMs);
|
|
1539
1631
|
if (cappedInterval !== ms) {
|
|
@@ -1542,10 +1634,7 @@ var SwapManager = class _SwapManager {
|
|
|
1542
1634
|
);
|
|
1543
1635
|
}
|
|
1544
1636
|
this.config.pollInterval = cappedInterval;
|
|
1545
|
-
this.currentPollRetryDelay = Math.min(
|
|
1546
|
-
cappedInterval,
|
|
1547
|
-
this.config.pollRetryDelayMs
|
|
1548
|
-
);
|
|
1637
|
+
this.currentPollRetryDelay = Math.min(cappedInterval, this.config.pollRetryDelayMs);
|
|
1549
1638
|
if (this.isRunning) {
|
|
1550
1639
|
if (this.usePollingFallback) {
|
|
1551
1640
|
this.startPollingFallback();
|
|
@@ -1584,6 +1673,11 @@ var SwapManager = class _SwapManager {
|
|
|
1584
1673
|
clearTimeout(retryTimer);
|
|
1585
1674
|
this.pollRetryTimers.delete(swapId);
|
|
1586
1675
|
}
|
|
1676
|
+
const refundRetryTimer = this.refundRetryTimers.get(swapId);
|
|
1677
|
+
if (refundRetryTimer) {
|
|
1678
|
+
clearTimeout(refundRetryTimer);
|
|
1679
|
+
this.refundRetryTimers.delete(swapId);
|
|
1680
|
+
}
|
|
1587
1681
|
this.notFoundCounts.delete(swapId);
|
|
1588
1682
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1589
1683
|
}
|
|
@@ -1630,9 +1724,7 @@ var SwapManager = class _SwapManager {
|
|
|
1630
1724
|
}
|
|
1631
1725
|
if (this.isFinalStatus(swap)) {
|
|
1632
1726
|
if (isPendingReverseSwap(swap)) {
|
|
1633
|
-
const response = await this.swapProvider.getReverseSwapTxId(
|
|
1634
|
-
swap.id
|
|
1635
|
-
);
|
|
1727
|
+
const response = await this.swapProvider.getReverseSwapTxId(swap.id);
|
|
1636
1728
|
return { txid: response.id };
|
|
1637
1729
|
}
|
|
1638
1730
|
if (isPendingSubmarineSwap(swap)) {
|
|
@@ -1649,31 +1741,19 @@ var SwapManager = class _SwapManager {
|
|
|
1649
1741
|
if (updatedSwap.status === "invoice.settled") {
|
|
1650
1742
|
this.swapProvider.getReverseSwapTxId(updatedSwap.id).then((response) => resolve({ txid: response.id })).catch((error) => reject(error));
|
|
1651
1743
|
} else {
|
|
1652
|
-
reject(
|
|
1653
|
-
new Error(
|
|
1654
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1655
|
-
)
|
|
1656
|
-
);
|
|
1744
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1657
1745
|
}
|
|
1658
1746
|
} else if (isPendingSubmarineSwap(updatedSwap)) {
|
|
1659
1747
|
if (updatedSwap.status === "transaction.claimed") {
|
|
1660
1748
|
resolve({ txid: updatedSwap.id });
|
|
1661
1749
|
} else {
|
|
1662
|
-
reject(
|
|
1663
|
-
new Error(
|
|
1664
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1665
|
-
)
|
|
1666
|
-
);
|
|
1750
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1667
1751
|
}
|
|
1668
1752
|
} else if (isPendingChainSwap(updatedSwap)) {
|
|
1669
1753
|
if (updatedSwap.status === "transaction.claimed") {
|
|
1670
1754
|
resolve({ txid: updatedSwap.id });
|
|
1671
1755
|
} else {
|
|
1672
|
-
reject(
|
|
1673
|
-
new Error(
|
|
1674
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1675
|
-
)
|
|
1676
|
-
);
|
|
1756
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1677
1757
|
}
|
|
1678
1758
|
}
|
|
1679
1759
|
};
|
|
@@ -1716,16 +1796,12 @@ var SwapManager = class _SwapManager {
|
|
|
1716
1796
|
const connectionTimeout = setTimeout(() => {
|
|
1717
1797
|
logger.error("WebSocket connection timeout");
|
|
1718
1798
|
this.websocket?.close();
|
|
1719
|
-
this.enterPollingFallback(
|
|
1720
|
-
new NetworkError("WebSocket connection failed")
|
|
1721
|
-
);
|
|
1799
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1722
1800
|
}, 1e4);
|
|
1723
1801
|
this.websocket.onerror = (error) => {
|
|
1724
1802
|
clearTimeout(connectionTimeout);
|
|
1725
1803
|
logger.error("WebSocket error:", error);
|
|
1726
|
-
this.enterPollingFallback(
|
|
1727
|
-
new NetworkError("WebSocket connection failed")
|
|
1728
|
-
);
|
|
1804
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1729
1805
|
};
|
|
1730
1806
|
this.websocket.onopen = () => {
|
|
1731
1807
|
clearTimeout(connectionTimeout);
|
|
@@ -1765,9 +1841,7 @@ var SwapManager = class _SwapManager {
|
|
|
1765
1841
|
};
|
|
1766
1842
|
} catch (error) {
|
|
1767
1843
|
logger.error("Failed to create WebSocket:", error);
|
|
1768
|
-
this.enterPollingFallback(
|
|
1769
|
-
new NetworkError("WebSocket connection failed")
|
|
1770
|
-
);
|
|
1844
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1771
1845
|
}
|
|
1772
1846
|
}
|
|
1773
1847
|
/**
|
|
@@ -1802,11 +1876,8 @@ var SwapManager = class _SwapManager {
|
|
|
1802
1876
|
* Schedule WebSocket reconnection with exponential backoff
|
|
1803
1877
|
*/
|
|
1804
1878
|
scheduleReconnect() {
|
|
1805
|
-
if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport())
|
|
1806
|
-
|
|
1807
|
-
logger.log(
|
|
1808
|
-
`Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`
|
|
1809
|
-
);
|
|
1879
|
+
if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport()) return;
|
|
1880
|
+
logger.log(`Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`);
|
|
1810
1881
|
this.reconnectTimer = setTimeout(() => {
|
|
1811
1882
|
this.reconnectTimer = null;
|
|
1812
1883
|
this.isReconnecting = false;
|
|
@@ -1821,8 +1892,7 @@ var SwapManager = class _SwapManager {
|
|
|
1821
1892
|
* Subscribe to a specific swap ID on the WebSocket
|
|
1822
1893
|
*/
|
|
1823
1894
|
subscribeToSwap(swapId) {
|
|
1824
|
-
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN)
|
|
1825
|
-
return;
|
|
1895
|
+
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) return;
|
|
1826
1896
|
this.websocket.send(
|
|
1827
1897
|
JSON.stringify({
|
|
1828
1898
|
op: "subscribe",
|
|
@@ -1845,9 +1915,7 @@ var SwapManager = class _SwapManager {
|
|
|
1845
1915
|
if (msg.args[0].error) {
|
|
1846
1916
|
logger.error(`Swap ${swapId} error:`, msg.args[0].error);
|
|
1847
1917
|
const error = new Error(msg.args[0].error);
|
|
1848
|
-
this.swapFailedListeners.forEach(
|
|
1849
|
-
(listener) => listener(swap, error)
|
|
1850
|
-
);
|
|
1918
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
1851
1919
|
return;
|
|
1852
1920
|
}
|
|
1853
1921
|
const newStatus = msg.args[0].status;
|
|
@@ -1865,19 +1933,14 @@ var SwapManager = class _SwapManager {
|
|
|
1865
1933
|
const oldStatus = swap.status;
|
|
1866
1934
|
if (oldStatus === newStatus) return;
|
|
1867
1935
|
swap.status = newStatus;
|
|
1868
|
-
this.swapUpdateListeners.forEach(
|
|
1869
|
-
(listener) => listener(swap, oldStatus)
|
|
1870
|
-
);
|
|
1936
|
+
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
1871
1937
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
1872
1938
|
if (subscribers) {
|
|
1873
1939
|
subscribers.forEach((callback) => {
|
|
1874
1940
|
try {
|
|
1875
1941
|
callback(swap, oldStatus);
|
|
1876
1942
|
} catch (error) {
|
|
1877
|
-
logger.error(
|
|
1878
|
-
`Error in swap subscription callback for ${swap.id}:`,
|
|
1879
|
-
error
|
|
1880
|
-
);
|
|
1943
|
+
logger.error(`Error in swap subscription callback for ${swap.id}:`, error);
|
|
1881
1944
|
}
|
|
1882
1945
|
});
|
|
1883
1946
|
}
|
|
@@ -1886,15 +1949,57 @@ var SwapManager = class _SwapManager {
|
|
|
1886
1949
|
await this.executeAutonomousAction(swap);
|
|
1887
1950
|
}
|
|
1888
1951
|
if (this.isFinalStatus(swap)) {
|
|
1889
|
-
this.
|
|
1890
|
-
|
|
1891
|
-
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1892
|
-
if (retryTimer) {
|
|
1893
|
-
clearTimeout(retryTimer);
|
|
1894
|
-
this.pollRetryTimers.delete(swap.id);
|
|
1952
|
+
if (this.refundRetryTimers.has(swap.id)) {
|
|
1953
|
+
return;
|
|
1895
1954
|
}
|
|
1896
|
-
this.
|
|
1955
|
+
this.finalizeMonitoredSwap(swap);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Drop a swap from monitoring and emit the terminal completion event.
|
|
1960
|
+
* Shared between the on-status-update finalization path and the
|
|
1961
|
+
* refund-retry finalization path (used when a previously-deferred
|
|
1962
|
+
* chain refund has finished its remaining work).
|
|
1963
|
+
*/
|
|
1964
|
+
finalizeMonitoredSwap(swap) {
|
|
1965
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1966
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1967
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1968
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1969
|
+
if (retryTimer) {
|
|
1970
|
+
clearTimeout(retryTimer);
|
|
1971
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1972
|
+
}
|
|
1973
|
+
const refundRetry = this.refundRetryTimers.get(swap.id);
|
|
1974
|
+
if (refundRetry) {
|
|
1975
|
+
clearTimeout(refundRetry);
|
|
1976
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1897
1977
|
}
|
|
1978
|
+
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Schedule another `executeAutonomousAction` run for a chain swap whose
|
|
1982
|
+
* refund left VTXOs deferred. After the retry completes, if no further
|
|
1983
|
+
* deferral was reported, finalize monitoring cleanup.
|
|
1984
|
+
*/
|
|
1985
|
+
scheduleRefundRetry(swap, delayMs) {
|
|
1986
|
+
const existing = this.refundRetryTimers.get(swap.id);
|
|
1987
|
+
if (existing) clearTimeout(existing);
|
|
1988
|
+
this.refundRetryTimers.set(
|
|
1989
|
+
swap.id,
|
|
1990
|
+
setTimeout(async () => {
|
|
1991
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1992
|
+
if (!this.isRunning) return;
|
|
1993
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1994
|
+
try {
|
|
1995
|
+
await this.executeAutonomousAction(swap);
|
|
1996
|
+
} finally {
|
|
1997
|
+
if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
|
|
1998
|
+
this.finalizeMonitoredSwap(swap);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}, delayMs)
|
|
2002
|
+
);
|
|
1898
2003
|
}
|
|
1899
2004
|
/**
|
|
1900
2005
|
* Execute autonomous action based on swap status
|
|
@@ -1902,9 +2007,7 @@ var SwapManager = class _SwapManager {
|
|
|
1902
2007
|
*/
|
|
1903
2008
|
async executeAutonomousAction(swap) {
|
|
1904
2009
|
if (this.swapsInProgress.has(swap.id)) {
|
|
1905
|
-
logger.log(
|
|
1906
|
-
`Swap ${swap.id} is already being processed, skipping autonomous action`
|
|
1907
|
-
);
|
|
2010
|
+
logger.log(`Swap ${swap.id} is already being processed, skipping autonomous action`);
|
|
1908
2011
|
return;
|
|
1909
2012
|
}
|
|
1910
2013
|
try {
|
|
@@ -1919,9 +2022,7 @@ var SwapManager = class _SwapManager {
|
|
|
1919
2022
|
if (isReverseClaimableStatus(swap.status)) {
|
|
1920
2023
|
logger.log(`Auto-claiming reverse swap ${swap.id}`);
|
|
1921
2024
|
await this.executeClaimAction(swap);
|
|
1922
|
-
this.actionExecutedListeners.forEach(
|
|
1923
|
-
(listener) => listener(swap, "claim")
|
|
1924
|
-
);
|
|
2025
|
+
this.actionExecutedListeners.forEach((listener) => listener(swap, "claim"));
|
|
1925
2026
|
}
|
|
1926
2027
|
} else if (isPendingSubmarineSwap(swap)) {
|
|
1927
2028
|
if (!swap.request?.invoice || swap.request.invoice.length === 0) {
|
|
@@ -1933,9 +2034,7 @@ var SwapManager = class _SwapManager {
|
|
|
1933
2034
|
if (isSubmarineRefundableStatus(swap.status)) {
|
|
1934
2035
|
logger.log(`Auto-refunding submarine swap ${swap.id}`);
|
|
1935
2036
|
await this.executeRefundAction(swap);
|
|
1936
|
-
this.actionExecutedListeners.forEach(
|
|
1937
|
-
(listener) => listener(swap, "refund")
|
|
1938
|
-
);
|
|
2037
|
+
this.actionExecutedListeners.forEach((listener) => listener(swap, "refund"));
|
|
1939
2038
|
}
|
|
1940
2039
|
} else if (isPendingChainSwap(swap)) {
|
|
1941
2040
|
if (isChainClaimableStatus(swap.status)) {
|
|
@@ -1955,10 +2054,27 @@ var SwapManager = class _SwapManager {
|
|
|
1955
2054
|
} else if (isChainRefundableStatus(swap.status)) {
|
|
1956
2055
|
if (swap.request.from === "ARK") {
|
|
1957
2056
|
logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
(
|
|
1961
|
-
|
|
2057
|
+
try {
|
|
2058
|
+
const outcome = await this.executeRefundArkAction(swap);
|
|
2059
|
+
if (outcome && outcome.skipped > 0) {
|
|
2060
|
+
logger.log(
|
|
2061
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
2062
|
+
);
|
|
2063
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2064
|
+
}
|
|
2065
|
+
this.actionExecutedListeners.forEach(
|
|
2066
|
+
(listener) => listener(swap, "refundArk")
|
|
2067
|
+
);
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
logger.error(
|
|
2070
|
+
`Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
|
|
2071
|
+
error
|
|
2072
|
+
);
|
|
2073
|
+
this.swapFailedListeners.forEach(
|
|
2074
|
+
(listener) => listener(swap, error)
|
|
2075
|
+
);
|
|
2076
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2077
|
+
}
|
|
1962
2078
|
}
|
|
1963
2079
|
if (swap.request.from === "BTC") {
|
|
1964
2080
|
logger.warn(
|
|
@@ -1985,13 +2101,8 @@ var SwapManager = class _SwapManager {
|
|
|
1985
2101
|
}
|
|
1986
2102
|
}
|
|
1987
2103
|
} catch (error) {
|
|
1988
|
-
logger.error(
|
|
1989
|
-
|
|
1990
|
-
error
|
|
1991
|
-
);
|
|
1992
|
-
this.swapFailedListeners.forEach(
|
|
1993
|
-
(listener) => listener(swap, error)
|
|
1994
|
-
);
|
|
2104
|
+
logger.error(`Failed to execute autonomous action for swap ${swap.id}:`, error);
|
|
2105
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
1995
2106
|
} finally {
|
|
1996
2107
|
this.swapsInProgress.delete(swap.id);
|
|
1997
2108
|
}
|
|
@@ -2044,7 +2155,7 @@ var SwapManager = class _SwapManager {
|
|
|
2044
2155
|
logger.error("refundArk callback not set");
|
|
2045
2156
|
return;
|
|
2046
2157
|
}
|
|
2047
|
-
|
|
2158
|
+
return this.refundArkCallback(swap);
|
|
2048
2159
|
}
|
|
2049
2160
|
/**
|
|
2050
2161
|
* Execute sign server claim action for chain swap.
|
|
@@ -2092,9 +2203,7 @@ var SwapManager = class _SwapManager {
|
|
|
2092
2203
|
logger.log(`Resuming chain refund for swap ${swap.id}`);
|
|
2093
2204
|
await this.executeAutonomousAction(swap);
|
|
2094
2205
|
} else if (isPendingChainSwap(swap) && swap.request.to === "ARK" && isChainSignableStatus(swap.status)) {
|
|
2095
|
-
logger.log(
|
|
2096
|
-
`Resuming server claim signing for swap ${swap.id}`
|
|
2097
|
-
);
|
|
2206
|
+
logger.log(`Resuming server claim signing for swap ${swap.id}`);
|
|
2098
2207
|
await this.executeAutonomousAction(swap);
|
|
2099
2208
|
}
|
|
2100
2209
|
} catch (error) {
|
|
@@ -2146,16 +2255,12 @@ var SwapManager = class _SwapManager {
|
|
|
2146
2255
|
*/
|
|
2147
2256
|
async pollAllSwaps() {
|
|
2148
2257
|
if (this.monitoredSwaps.size === 0) return;
|
|
2149
|
-
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2150
|
-
(swap) => this.pollSingleSwap(swap)
|
|
2151
|
-
);
|
|
2258
|
+
const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
|
|
2152
2259
|
await Promise.allSettled(pollPromises);
|
|
2153
2260
|
}
|
|
2154
2261
|
async pollSingleSwap(swap) {
|
|
2155
2262
|
try {
|
|
2156
|
-
const statusResponse = await this.swapProvider.getSwapStatus(
|
|
2157
|
-
swap.id
|
|
2158
|
-
);
|
|
2263
|
+
const statusResponse = await this.swapProvider.getSwapStatus(swap.id);
|
|
2159
2264
|
this.notFoundCounts.delete(swap.id);
|
|
2160
2265
|
if (statusResponse.status !== swap.status) {
|
|
2161
2266
|
await this.handleSwapStatusUpdate(swap, statusResponse.status);
|
|
@@ -2166,9 +2271,7 @@ var SwapManager = class _SwapManager {
|
|
|
2166
2271
|
return;
|
|
2167
2272
|
}
|
|
2168
2273
|
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
2169
|
-
logger.warn(
|
|
2170
|
-
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
2171
|
-
);
|
|
2274
|
+
logger.warn(`Rate-limited polling swap ${swap.id}, retrying in 2s`);
|
|
2172
2275
|
const existing = this.pollRetryTimers.get(swap.id);
|
|
2173
2276
|
if (existing) clearTimeout(existing);
|
|
2174
2277
|
this.pollRetryTimers.set(
|
|
@@ -2176,25 +2279,17 @@ var SwapManager = class _SwapManager {
|
|
|
2176
2279
|
setTimeout(async () => {
|
|
2177
2280
|
this.pollRetryTimers.delete(swap.id);
|
|
2178
2281
|
try {
|
|
2179
|
-
const retry = await this.swapProvider.getSwapStatus(
|
|
2180
|
-
swap.id
|
|
2181
|
-
);
|
|
2282
|
+
const retry = await this.swapProvider.getSwapStatus(swap.id);
|
|
2182
2283
|
this.notFoundCounts.delete(swap.id);
|
|
2183
2284
|
if (retry.status !== swap.status) {
|
|
2184
|
-
await this.handleSwapStatusUpdate(
|
|
2185
|
-
swap,
|
|
2186
|
-
retry.status
|
|
2187
|
-
);
|
|
2285
|
+
await this.handleSwapStatusUpdate(swap, retry.status);
|
|
2188
2286
|
}
|
|
2189
2287
|
} catch (retryError) {
|
|
2190
2288
|
if (retryError instanceof SwapNotFoundError) {
|
|
2191
2289
|
await this.handleSwapNotFound(swap);
|
|
2192
2290
|
return;
|
|
2193
2291
|
}
|
|
2194
|
-
logger.error(
|
|
2195
|
-
`Retry poll for swap ${swap.id} also failed:`,
|
|
2196
|
-
retryError
|
|
2197
|
-
);
|
|
2292
|
+
logger.error(`Retry poll for swap ${swap.id} also failed:`, retryError);
|
|
2198
2293
|
}
|
|
2199
2294
|
}, 2e3)
|
|
2200
2295
|
);
|
|
@@ -2213,6 +2308,7 @@ var SwapManager = class _SwapManager {
|
|
|
2213
2308
|
* Boltz endpoint).
|
|
2214
2309
|
*/
|
|
2215
2310
|
async handleSwapNotFound(swap) {
|
|
2311
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2216
2312
|
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2217
2313
|
this.notFoundCounts.set(swap.id, count);
|
|
2218
2314
|
logger.warn(
|
|
@@ -2233,6 +2329,7 @@ var SwapManager = class _SwapManager {
|
|
|
2233
2329
|
* 404s without recovering anything.
|
|
2234
2330
|
*/
|
|
2235
2331
|
async markSwapAsUnknownToProvider(swap) {
|
|
2332
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2236
2333
|
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2237
2334
|
this.notFoundCounts.delete(swap.id);
|
|
2238
2335
|
return;
|
|
@@ -2245,10 +2342,13 @@ var SwapManager = class _SwapManager {
|
|
|
2245
2342
|
clearTimeout(retryTimer);
|
|
2246
2343
|
this.pollRetryTimers.delete(swap.id);
|
|
2247
2344
|
}
|
|
2345
|
+
const refundRetryTimer = this.refundRetryTimers.get(swap.id);
|
|
2346
|
+
if (refundRetryTimer) {
|
|
2347
|
+
clearTimeout(refundRetryTimer);
|
|
2348
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2349
|
+
}
|
|
2248
2350
|
this.notFoundCounts.delete(swap.id);
|
|
2249
|
-
this.swapUpdateListeners.forEach(
|
|
2250
|
-
(listener) => listener(swap, oldStatus)
|
|
2251
|
-
);
|
|
2351
|
+
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
2252
2352
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
2253
2353
|
if (subscribers) {
|
|
2254
2354
|
subscribers.forEach((callback) => {
|
|
@@ -2313,9 +2413,7 @@ async function saveSwap(swap, saver) {
|
|
|
2313
2413
|
if (saver.saveSubmarineSwap) {
|
|
2314
2414
|
await saver.saveSubmarineSwap(swap);
|
|
2315
2415
|
} else {
|
|
2316
|
-
console.warn(
|
|
2317
|
-
"No saveSubmarineSwap handler provided, swap not saved"
|
|
2318
|
-
);
|
|
2416
|
+
console.warn("No saveSubmarineSwap handler provided, swap not saved");
|
|
2319
2417
|
}
|
|
2320
2418
|
} else if (isPendingChainSwap(swap)) {
|
|
2321
2419
|
if (saver.saveChainSwap) {
|
|
@@ -2372,6 +2470,26 @@ function enrichSubmarineSwapInvoice(swap, invoice) {
|
|
|
2372
2470
|
return swap;
|
|
2373
2471
|
}
|
|
2374
2472
|
|
|
2473
|
+
// src/repositories/swap-repository.ts
|
|
2474
|
+
function hasImpossibleSwapsFilter(filter) {
|
|
2475
|
+
if (!filter) return false;
|
|
2476
|
+
return Array.isArray(filter.id) && filter.id.length === 0 || Array.isArray(filter.status) && filter.status.length === 0 || Array.isArray(filter.type) && filter.type.length === 0;
|
|
2477
|
+
}
|
|
2478
|
+
function matchesCriterion(value, criterion) {
|
|
2479
|
+
if (criterion === void 0) return true;
|
|
2480
|
+
return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
|
|
2481
|
+
}
|
|
2482
|
+
function applySwapsFilter(swaps, filter) {
|
|
2483
|
+
return swaps.filter(
|
|
2484
|
+
(swap) => !!swap && matchesCriterion(swap.id, filter.id) && matchesCriterion(swap.status, filter.status) && matchesCriterion(swap.type, filter.type)
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
function applyCreatedAtOrder(swaps, filter) {
|
|
2488
|
+
if (filter?.orderBy !== "createdAt") return swaps;
|
|
2489
|
+
const direction = filter.orderDirection === "asc" ? 1 : -1;
|
|
2490
|
+
return swaps.slice().sort((a, b) => (a.createdAt - b.createdAt) * direction);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2375
2493
|
// src/repositories/IndexedDb/swap-repository.ts
|
|
2376
2494
|
var import_sdk4 = require("@arkade-os/sdk");
|
|
2377
2495
|
var DEFAULT_DB_NAME = "arkade-boltz-swap";
|
|
@@ -2387,6 +2505,10 @@ function initDatabase(db) {
|
|
|
2387
2505
|
swapStore.createIndex("createdAt", "createdAt", { unique: false });
|
|
2388
2506
|
}
|
|
2389
2507
|
}
|
|
2508
|
+
function asArray(v) {
|
|
2509
|
+
if (v === void 0) return void 0;
|
|
2510
|
+
return Array.isArray(v) ? v : [v];
|
|
2511
|
+
}
|
|
2390
2512
|
var IndexedDbSwapRepository = class {
|
|
2391
2513
|
constructor(dbName = DEFAULT_DB_NAME) {
|
|
2392
2514
|
this.dbName = dbName;
|
|
@@ -2401,10 +2523,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2401
2523
|
async saveSwap(swap) {
|
|
2402
2524
|
const db = await this.getDB();
|
|
2403
2525
|
return new Promise((resolve, reject) => {
|
|
2404
|
-
const transaction = db.transaction(
|
|
2405
|
-
[STORE_SWAPS_STATE],
|
|
2406
|
-
"readwrite"
|
|
2407
|
-
);
|
|
2526
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2408
2527
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2409
2528
|
const request = store.put(swap);
|
|
2410
2529
|
request.onsuccess = () => resolve();
|
|
@@ -2414,10 +2533,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2414
2533
|
async deleteSwap(id) {
|
|
2415
2534
|
const db = await this.getDB();
|
|
2416
2535
|
return new Promise((resolve, reject) => {
|
|
2417
|
-
const transaction = db.transaction(
|
|
2418
|
-
[STORE_SWAPS_STATE],
|
|
2419
|
-
"readwrite"
|
|
2420
|
-
);
|
|
2536
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2421
2537
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2422
2538
|
const request = store.delete(id);
|
|
2423
2539
|
request.onsuccess = () => resolve();
|
|
@@ -2430,10 +2546,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2430
2546
|
async clear() {
|
|
2431
2547
|
const db = await this.getDB();
|
|
2432
2548
|
return new Promise((resolve, reject) => {
|
|
2433
|
-
const transaction = db.transaction(
|
|
2434
|
-
[STORE_SWAPS_STATE],
|
|
2435
|
-
"readwrite"
|
|
2436
|
-
);
|
|
2549
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2437
2550
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2438
2551
|
const request = store.clear();
|
|
2439
2552
|
request.onsuccess = () => resolve();
|
|
@@ -2450,11 +2563,10 @@ var IndexedDbSwapRepository = class {
|
|
|
2450
2563
|
request.onsuccess = () => resolve(request.result ?? []);
|
|
2451
2564
|
})
|
|
2452
2565
|
);
|
|
2453
|
-
return Promise.all(requests).then(
|
|
2454
|
-
(results) => results.flatMap((result) => result)
|
|
2455
|
-
);
|
|
2566
|
+
return Promise.all(requests).then((results) => results.flatMap((result) => result));
|
|
2456
2567
|
}
|
|
2457
2568
|
async getAllSwapsFromStore(filter) {
|
|
2569
|
+
if (hasImpossibleSwapsFilter(filter)) return [];
|
|
2458
2570
|
const db = await this.getDB();
|
|
2459
2571
|
const store = db.transaction([STORE_SWAPS_STATE], "readonly").objectStore(STORE_SWAPS_STATE);
|
|
2460
2572
|
if (!filter || Object.keys(filter).length === 0) {
|
|
@@ -2464,9 +2576,8 @@ var IndexedDbSwapRepository = class {
|
|
|
2464
2576
|
request.onerror = () => reject(request.error);
|
|
2465
2577
|
});
|
|
2466
2578
|
}
|
|
2467
|
-
const
|
|
2468
|
-
if (
|
|
2469
|
-
const ids = normalizedFilter.get("id");
|
|
2579
|
+
const ids = asArray(filter.id);
|
|
2580
|
+
if (ids) {
|
|
2470
2581
|
const swaps = await Promise.all(
|
|
2471
2582
|
ids.map(
|
|
2472
2583
|
(id) => new Promise((resolve, reject) => {
|
|
@@ -2476,34 +2587,17 @@ var IndexedDbSwapRepository = class {
|
|
|
2476
2587
|
})
|
|
2477
2588
|
)
|
|
2478
2589
|
);
|
|
2479
|
-
return
|
|
2480
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2481
|
-
filter
|
|
2482
|
-
);
|
|
2590
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2483
2591
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const swaps = await this.getSwapsByIndexValues(
|
|
2487
|
-
|
|
2488
|
-
"type",
|
|
2489
|
-
types
|
|
2490
|
-
);
|
|
2491
|
-
return this.sortIfNeeded(
|
|
2492
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2493
|
-
filter
|
|
2494
|
-
);
|
|
2592
|
+
const types = asArray(filter.type);
|
|
2593
|
+
if (types) {
|
|
2594
|
+
const swaps = await this.getSwapsByIndexValues(store, "type", types);
|
|
2595
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2495
2596
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
const swaps = await this.getSwapsByIndexValues(
|
|
2499
|
-
|
|
2500
|
-
"status",
|
|
2501
|
-
ids
|
|
2502
|
-
);
|
|
2503
|
-
return this.sortIfNeeded(
|
|
2504
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2505
|
-
filter
|
|
2506
|
-
);
|
|
2597
|
+
const statuses = asArray(filter.status);
|
|
2598
|
+
if (statuses) {
|
|
2599
|
+
const swaps = await this.getSwapsByIndexValues(store, "status", statuses);
|
|
2600
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2507
2601
|
}
|
|
2508
2602
|
if (filter.orderBy === "createdAt") {
|
|
2509
2603
|
return this.getAllSwapsByCreatedAt(store, filter.orderDirection);
|
|
@@ -2513,22 +2607,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2513
2607
|
request.onsuccess = () => resolve(request.result ?? []);
|
|
2514
2608
|
request.onerror = () => reject(request.error);
|
|
2515
2609
|
});
|
|
2516
|
-
return
|
|
2517
|
-
this.applySwapsFilter(allSwaps, normalizedFilter),
|
|
2518
|
-
filter
|
|
2519
|
-
);
|
|
2520
|
-
}
|
|
2521
|
-
applySwapsFilter(swaps, filter) {
|
|
2522
|
-
return swaps.filter((swap) => {
|
|
2523
|
-
if (swap === void 0) return false;
|
|
2524
|
-
if (filter.has("id") && !filter.get("id")?.includes(swap.id))
|
|
2525
|
-
return false;
|
|
2526
|
-
if (filter.has("status") && !filter.get("status")?.includes(swap.status))
|
|
2527
|
-
return false;
|
|
2528
|
-
if (filter.has("type") && !filter.get("type")?.includes(swap.type))
|
|
2529
|
-
return false;
|
|
2530
|
-
return true;
|
|
2531
|
-
});
|
|
2610
|
+
return applyCreatedAtOrder(applySwapsFilter(allSwaps, filter), filter);
|
|
2532
2611
|
}
|
|
2533
2612
|
async getAllSwapsByCreatedAt(store, orderDirection) {
|
|
2534
2613
|
const index = store.index("createdAt");
|
|
@@ -2548,30 +2627,12 @@ var IndexedDbSwapRepository = class {
|
|
|
2548
2627
|
};
|
|
2549
2628
|
});
|
|
2550
2629
|
}
|
|
2551
|
-
sortIfNeeded(swaps, filter) {
|
|
2552
|
-
if (filter?.orderBy !== "createdAt") return swaps;
|
|
2553
|
-
const direction = filter.orderDirection === "asc" ? 1 : -1;
|
|
2554
|
-
return swaps.slice().sort((a, b) => (a.createdAt - b.createdAt) * direction);
|
|
2555
|
-
}
|
|
2556
2630
|
async [Symbol.asyncDispose]() {
|
|
2557
2631
|
if (!this.db) return;
|
|
2558
2632
|
await (0, import_sdk4.closeDatabase)(this.dbName);
|
|
2559
2633
|
this.db = null;
|
|
2560
2634
|
}
|
|
2561
2635
|
};
|
|
2562
|
-
var FILTER_FIELDS = ["id", "status", "type"];
|
|
2563
|
-
function normalizeFilter(filter) {
|
|
2564
|
-
const res = /* @__PURE__ */ new Map();
|
|
2565
|
-
FILTER_FIELDS.forEach((current) => {
|
|
2566
|
-
if (!filter?.[current]) return;
|
|
2567
|
-
if (Array.isArray(filter[current])) {
|
|
2568
|
-
res.set(current, filter[current]);
|
|
2569
|
-
} else {
|
|
2570
|
-
res.set(current, [filter[current]]);
|
|
2571
|
-
}
|
|
2572
|
-
});
|
|
2573
|
-
return res;
|
|
2574
|
-
}
|
|
2575
2636
|
|
|
2576
2637
|
// src/utils/identity.ts
|
|
2577
2638
|
var import_sdk5 = require("@arkade-os/sdk");
|
|
@@ -2583,13 +2644,8 @@ function claimVHTLCIdentity(identity, preimage) {
|
|
|
2583
2644
|
let signedTx = await identity.sign(cpy, inputIndexes);
|
|
2584
2645
|
signedTx = import_sdk5.Transaction.fromPSBT(signedTx.toPSBT());
|
|
2585
2646
|
if (preimage) {
|
|
2586
|
-
for (const inputIndex of inputIndexes || Array.from(
|
|
2587
|
-
|
|
2588
|
-
(_, i) => i
|
|
2589
|
-
)) {
|
|
2590
|
-
(0, import_sdk5.setArkPsbtField)(signedTx, inputIndex, import_sdk5.ConditionWitness, [
|
|
2591
|
-
preimage
|
|
2592
|
-
]);
|
|
2647
|
+
for (const inputIndex of inputIndexes || Array.from({ length: signedTx.inputsLength }, (_, i) => i)) {
|
|
2648
|
+
(0, import_sdk5.setArkPsbtField)(signedTx, inputIndex, import_sdk5.ConditionWitness, [preimage]);
|
|
2593
2649
|
}
|
|
2594
2650
|
}
|
|
2595
2651
|
return signedTx;
|
|
@@ -2644,17 +2700,13 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2644
2700
|
if (!sweepTapTreeRoot) {
|
|
2645
2701
|
throw new Error("Sweep tap tree root not set");
|
|
2646
2702
|
}
|
|
2647
|
-
const xOnlyPublicKeys = event.cosignersPublicKeys.map(
|
|
2648
|
-
(k) => k.slice(2)
|
|
2649
|
-
);
|
|
2703
|
+
const xOnlyPublicKeys = event.cosignersPublicKeys.map((k) => k.slice(2));
|
|
2650
2704
|
const signerPublicKey = await session.getPublicKey();
|
|
2651
2705
|
const xonlySignerPublicKey = signerPublicKey.subarray(1);
|
|
2652
2706
|
if (!xOnlyPublicKeys.includes(import_base7.hex.encode(xonlySignerPublicKey))) {
|
|
2653
2707
|
return { skip: true };
|
|
2654
2708
|
}
|
|
2655
|
-
const commitmentTx = import_sdk6.Transaction.fromPSBT(
|
|
2656
|
-
import_base7.base64.decode(event.unsignedCommitmentTx)
|
|
2657
|
-
);
|
|
2709
|
+
const commitmentTx = import_sdk6.Transaction.fromPSBT(import_base7.base64.decode(event.unsignedCommitmentTx));
|
|
2658
2710
|
(0, import_sdk6.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
2659
2711
|
const sharedOutput = commitmentTx.getOutput(0);
|
|
2660
2712
|
if (!sharedOutput?.amount) {
|
|
@@ -2670,18 +2722,11 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2670
2722
|
if (!session) {
|
|
2671
2723
|
return { fullySigned: true };
|
|
2672
2724
|
}
|
|
2673
|
-
const { hasAllNonces } = await session.aggregatedNonces(
|
|
2674
|
-
event.txid,
|
|
2675
|
-
event.nonces
|
|
2676
|
-
);
|
|
2725
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
2677
2726
|
if (!hasAllNonces) return { fullySigned: false };
|
|
2678
2727
|
const signatures = await session.sign();
|
|
2679
2728
|
const pubkey = import_base7.hex.encode(await session.getPublicKey());
|
|
2680
|
-
await arkProvider.submitTreeSignatures(
|
|
2681
|
-
event.id,
|
|
2682
|
-
pubkey,
|
|
2683
|
-
signatures
|
|
2684
|
-
);
|
|
2729
|
+
await arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
2685
2730
|
return { fullySigned: true };
|
|
2686
2731
|
},
|
|
2687
2732
|
onBatchFinalization: async (event, _, connectorTree) => {
|
|
@@ -2689,9 +2734,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2689
2734
|
return;
|
|
2690
2735
|
}
|
|
2691
2736
|
if (!connectorTree) {
|
|
2692
|
-
throw new Error(
|
|
2693
|
-
"BatchFinalizationEvent: expected connector tree to be defined"
|
|
2694
|
-
);
|
|
2737
|
+
throw new Error("BatchFinalizationEvent: expected connector tree to be defined");
|
|
2695
2738
|
}
|
|
2696
2739
|
(0, import_sdk6.validateConnectorsTxGraph)(event.commitmentTx, connectorTree);
|
|
2697
2740
|
const connectors = connectorTree.leaves();
|
|
@@ -2706,9 +2749,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2706
2749
|
connectors[connectorIndex]
|
|
2707
2750
|
);
|
|
2708
2751
|
const signedForfeitTx = await identity.sign(forfeitTx);
|
|
2709
|
-
await arkProvider.submitSignedForfeitTxs([
|
|
2710
|
-
import_base7.base64.encode(signedForfeitTx.toPSBT())
|
|
2711
|
-
]);
|
|
2752
|
+
await arkProvider.submitSignedForfeitTxs([import_base7.base64.encode(signedForfeitTx.toPSBT())]);
|
|
2712
2753
|
}
|
|
2713
2754
|
};
|
|
2714
2755
|
}
|
|
@@ -2768,36 +2809,22 @@ var createVHTLCScript = (args) => {
|
|
|
2768
2809
|
serverPubkey,
|
|
2769
2810
|
timeoutBlockHeights: vhtlcTimeouts
|
|
2770
2811
|
} = args;
|
|
2771
|
-
const receiverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
);
|
|
2775
|
-
const senderXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2776
|
-
import_base8.hex.decode(senderPubkey),
|
|
2777
|
-
"sender"
|
|
2778
|
-
);
|
|
2779
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2780
|
-
import_base8.hex.decode(serverPubkey),
|
|
2781
|
-
"server"
|
|
2782
|
-
);
|
|
2812
|
+
const receiverXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(receiverPubkey), "receiver");
|
|
2813
|
+
const senderXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(senderPubkey), "sender");
|
|
2814
|
+
const serverXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(serverPubkey), "server");
|
|
2783
2815
|
const vhtlcScript = new import_sdk7.VHTLC.Script({
|
|
2784
2816
|
preimageHash: (0, import_legacy.ripemd160)(preimageHash),
|
|
2785
2817
|
sender: senderXOnlyPublicKey,
|
|
2786
2818
|
receiver: receiverXOnlyPublicKey,
|
|
2787
2819
|
server: serverXOnlyPublicKey,
|
|
2788
2820
|
refundLocktime: BigInt(vhtlcTimeouts.refund),
|
|
2789
|
-
unilateralClaimDelay: toBip68RelativeTimelock(
|
|
2790
|
-
|
|
2791
|
-
),
|
|
2792
|
-
unilateralRefundDelay: toBip68RelativeTimelock(
|
|
2793
|
-
vhtlcTimeouts.unilateralRefund
|
|
2794
|
-
),
|
|
2821
|
+
unilateralClaimDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralClaim),
|
|
2822
|
+
unilateralRefundDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralRefund),
|
|
2795
2823
|
unilateralRefundWithoutReceiverDelay: toBip68RelativeTimelock(
|
|
2796
2824
|
vhtlcTimeouts.unilateralRefundWithoutReceiver
|
|
2797
2825
|
)
|
|
2798
2826
|
});
|
|
2799
|
-
if (!vhtlcScript.claimScript)
|
|
2800
|
-
throw new Error("Failed to create VHTLC script");
|
|
2827
|
+
if (!vhtlcScript.claimScript) throw new Error("Failed to create VHTLC script");
|
|
2801
2828
|
const hrp = network === "bitcoin" ? "ark" : "tark";
|
|
2802
2829
|
const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
|
|
2803
2830
|
return { vhtlcScript, vhtlcAddress };
|
|
@@ -2831,11 +2858,7 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2831
2858
|
unknown: [import_sdk7.VtxoTaprootTree.encode(input.tapTree)],
|
|
2832
2859
|
sequence: (0, import_sdk7.getSequence)(input.tapLeafScript)
|
|
2833
2860
|
};
|
|
2834
|
-
const registerIntent = import_sdk7.Intent.create(
|
|
2835
|
-
intentMessage,
|
|
2836
|
-
[intentInput],
|
|
2837
|
-
[output]
|
|
2838
|
-
);
|
|
2861
|
+
const registerIntent = import_sdk7.Intent.create(intentMessage, [intentInput], [output]);
|
|
2839
2862
|
const deleteIntent = import_sdk7.Intent.create(deleteMessage, [intentInput]);
|
|
2840
2863
|
const [signedRegisterIntent, signedDeleteIntent] = await Promise.all([
|
|
2841
2864
|
identity.sign(registerIntent),
|
|
@@ -2859,14 +2882,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2859
2882
|
normalizeToXOnlyKey(forfeitPubkey, "forfeit"),
|
|
2860
2883
|
isRecoverable2 ? void 0 : import_btc_signer4.OutScript.encode(decodedAddress)
|
|
2861
2884
|
);
|
|
2862
|
-
const topics = [
|
|
2863
|
-
|
|
2864
|
-
`${input.txid}:${input.vout}`
|
|
2865
|
-
];
|
|
2866
|
-
const eventStream = arkProvider.getEventStream(
|
|
2867
|
-
abortController.signal,
|
|
2868
|
-
topics
|
|
2869
|
-
);
|
|
2885
|
+
const topics = [import_base8.hex.encode(signerPublicKey), `${input.txid}:${input.vout}`];
|
|
2886
|
+
const eventStream = arkProvider.getEventStream(abortController.signal, topics);
|
|
2870
2887
|
const commitmentTxid = await import_sdk7.Batch.join(eventStream, handler, {
|
|
2871
2888
|
abortController
|
|
2872
2889
|
});
|
|
@@ -2887,14 +2904,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2887
2904
|
};
|
|
2888
2905
|
var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKey, input, output, arkInfo, arkProvider) => {
|
|
2889
2906
|
const rawCheckpointTapscript = import_base8.hex.decode(arkInfo.checkpointTapscript);
|
|
2890
|
-
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(
|
|
2891
|
-
|
|
2892
|
-
);
|
|
2893
|
-
const { arkTx, checkpoints } = (0, import_sdk7.buildOffchainTx)(
|
|
2894
|
-
[input],
|
|
2895
|
-
[output],
|
|
2896
|
-
serverUnrollScript
|
|
2897
|
-
);
|
|
2907
|
+
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(rawCheckpointTapscript);
|
|
2908
|
+
const { arkTx, checkpoints } = (0, import_sdk7.buildOffchainTx)([input], [output], serverUnrollScript);
|
|
2898
2909
|
const signedArkTx = await identity.sign(arkTx);
|
|
2899
2910
|
const { arkTxid, finalArkTx, signedCheckpointTxs } = await arkProvider.submitTx(
|
|
2900
2911
|
import_base8.base64.encode(signedArkTx.toPSBT()),
|
|
@@ -2902,9 +2913,7 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2902
2913
|
);
|
|
2903
2914
|
const finalTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
|
|
2904
2915
|
const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
|
|
2905
|
-
const claimLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2906
|
-
scriptFromTapLeafScript(vhtlcScript.claim())
|
|
2907
|
-
);
|
|
2916
|
+
const claimLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(vhtlcScript.claim()));
|
|
2908
2917
|
for (let i = 0; i < finalTx.inputsLength; i++) {
|
|
2909
2918
|
if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
|
|
2910
2919
|
throw new Error("Invalid final Ark transaction");
|
|
@@ -2914,13 +2923,9 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2914
2923
|
signedCheckpointTxs.map(async (c, idx) => {
|
|
2915
2924
|
const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(c));
|
|
2916
2925
|
const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
|
|
2917
|
-
const cpLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2918
|
-
scriptFromTapLeafScript(checkpointLeaf)
|
|
2919
|
-
);
|
|
2926
|
+
const cpLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(checkpointLeaf));
|
|
2920
2927
|
if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
|
|
2921
|
-
throw new Error(
|
|
2922
|
-
"Invalid server signature in checkpoint transaction"
|
|
2923
|
-
);
|
|
2928
|
+
throw new Error("Invalid server signature in checkpoint transaction");
|
|
2924
2929
|
}
|
|
2925
2930
|
const signedCheckpoint = await identity.sign(tx, [0]);
|
|
2926
2931
|
return import_base8.base64.encode(signedCheckpoint.toPSBT());
|
|
@@ -2930,61 +2935,37 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2930
2935
|
};
|
|
2931
2936
|
var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfo, refundFunc) => {
|
|
2932
2937
|
const rawCheckpointTapscript = import_base8.hex.decode(arkInfo.checkpointTapscript);
|
|
2933
|
-
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(
|
|
2934
|
-
|
|
2938
|
+
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(rawCheckpointTapscript);
|
|
2939
|
+
const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = (0, import_sdk7.buildOffchainTx)(
|
|
2940
|
+
[input],
|
|
2941
|
+
[output],
|
|
2942
|
+
serverUnrollScript
|
|
2935
2943
|
);
|
|
2936
|
-
const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = (0, import_sdk7.buildOffchainTx)([input], [output], serverUnrollScript);
|
|
2937
2944
|
if (checkpointPtxs.length !== 1)
|
|
2938
|
-
throw new Error(
|
|
2939
|
-
`Expected one checkpoint transaction, got ${checkpointPtxs.length}`
|
|
2940
|
-
);
|
|
2945
|
+
throw new Error(`Expected one checkpoint transaction, got ${checkpointPtxs.length}`);
|
|
2941
2946
|
const unsignedCheckpointTx = checkpointPtxs[0];
|
|
2942
2947
|
let boltzSignedRefundTx;
|
|
2943
2948
|
let boltzSignedCheckpointTx;
|
|
2944
2949
|
try {
|
|
2945
|
-
const result = await refundFunc(
|
|
2946
|
-
swapId,
|
|
2947
|
-
unsignedRefundTx,
|
|
2948
|
-
unsignedCheckpointTx
|
|
2949
|
-
);
|
|
2950
|
+
const result = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
|
|
2950
2951
|
boltzSignedRefundTx = result.transaction;
|
|
2951
2952
|
boltzSignedCheckpointTx = result.checkpoint;
|
|
2952
2953
|
} catch (error) {
|
|
2953
|
-
throw new BoltzRefundError(
|
|
2954
|
-
`Boltz rejected refund for swap ${swapId}`,
|
|
2955
|
-
error
|
|
2956
|
-
);
|
|
2954
|
+
throw new BoltzRefundError(`Boltz rejected refund for swap ${swapId}`, error);
|
|
2957
2955
|
}
|
|
2958
2956
|
const boltzXOnlyPublicKeyHex = import_base8.hex.encode(boltzXOnlyPublicKey);
|
|
2959
|
-
const refundLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2960
|
-
|
|
2961
|
-
);
|
|
2962
|
-
if (!verifySignatures(
|
|
2963
|
-
boltzSignedRefundTx,
|
|
2964
|
-
0,
|
|
2965
|
-
[boltzXOnlyPublicKeyHex],
|
|
2966
|
-
refundLeafHash
|
|
2967
|
-
)) {
|
|
2957
|
+
const refundLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(input.tapLeafScript));
|
|
2958
|
+
if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex], refundLeafHash)) {
|
|
2968
2959
|
throw new Error("Invalid Boltz signature in refund transaction");
|
|
2969
2960
|
}
|
|
2970
2961
|
const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
|
|
2971
|
-
const checkpointLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2972
|
-
|
|
2973
|
-
);
|
|
2974
|
-
if (!verifySignatures(
|
|
2975
|
-
boltzSignedCheckpointTx,
|
|
2976
|
-
0,
|
|
2977
|
-
[boltzXOnlyPublicKeyHex],
|
|
2978
|
-
checkpointLeafHash
|
|
2979
|
-
)) {
|
|
2962
|
+
const checkpointLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(checkpointLeaf));
|
|
2963
|
+
if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex], checkpointLeafHash)) {
|
|
2980
2964
|
throw new Error("Invalid Boltz signature in checkpoint transaction");
|
|
2981
2965
|
}
|
|
2982
2966
|
const signedRefundTx = await identity.sign(unsignedRefundTx);
|
|
2983
2967
|
const signedCheckpointTx = await identity.sign(unsignedCheckpointTx);
|
|
2984
|
-
const combinedSignedRefundTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
2985
|
-
boltzSignedRefundTx,
|
|
2986
|
-
signedRefundTx
|
|
2987
|
-
);
|
|
2968
|
+
const combinedSignedRefundTx = (0, import_sdk7.combineTapscriptSigs)(boltzSignedRefundTx, signedRefundTx);
|
|
2988
2969
|
const combinedSignedCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
2989
2970
|
boltzSignedCheckpointTx,
|
|
2990
2971
|
signedCheckpointTx
|
|
@@ -3008,25 +2989,16 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
|
|
|
3008
2989
|
`Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
|
|
3009
2990
|
);
|
|
3010
2991
|
}
|
|
3011
|
-
const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(
|
|
3012
|
-
import_base8.base64.decode(signedCheckpointTxs[0])
|
|
3013
|
-
);
|
|
2992
|
+
const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(signedCheckpointTxs[0]));
|
|
3014
2993
|
const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
|
|
3015
|
-
if (!verifySignatures(
|
|
3016
|
-
serverSignedCheckpointTx,
|
|
3017
|
-
0,
|
|
3018
|
-
[serverPubkeyHex],
|
|
3019
|
-
checkpointLeafHash
|
|
3020
|
-
)) {
|
|
2994
|
+
if (!verifySignatures(serverSignedCheckpointTx, 0, [serverPubkeyHex], checkpointLeafHash)) {
|
|
3021
2995
|
throw new Error("Invalid server signature in checkpoint transaction");
|
|
3022
2996
|
}
|
|
3023
2997
|
const finalCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
3024
2998
|
combinedSignedCheckpointTx,
|
|
3025
2999
|
serverSignedCheckpointTx
|
|
3026
3000
|
);
|
|
3027
|
-
await arkProvider.finalizeTx(arkTxid, [
|
|
3028
|
-
import_base8.base64.encode(finalCheckpointTx.toPSBT())
|
|
3029
|
-
]);
|
|
3001
|
+
await arkProvider.finalizeTx(arkTxid, [import_base8.base64.encode(finalCheckpointTx.toPSBT())]);
|
|
3030
3002
|
};
|
|
3031
3003
|
function scriptFromTapLeafScript(leaf) {
|
|
3032
3004
|
return leaf[1].subarray(0, leaf[1].length - 1);
|
|
@@ -3034,21 +3006,21 @@ function scriptFromTapLeafScript(leaf) {
|
|
|
3034
3006
|
|
|
3035
3007
|
// src/arkade-swaps.ts
|
|
3036
3008
|
var dedupeVtxos = (vtxos) => [
|
|
3037
|
-
...new Map(
|
|
3038
|
-
vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])
|
|
3039
|
-
).values()
|
|
3009
|
+
...new Map(vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])).values()
|
|
3040
3010
|
];
|
|
3041
3011
|
var hasNonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
3042
3012
|
var canRecoverViaBoltz3of3 = (refundableVtxos, swap) => {
|
|
3043
3013
|
const hasRequiredSwapMetadata = hasNonEmptyString(swap.id) && hasNonEmptyString(swap.request.refundPublicKey) && hasNonEmptyString(swap.response.address) && hasNonEmptyString(swap.response.claimPublicKey) && !!swap.response.timeoutBlockHeights;
|
|
3044
3014
|
if (!hasRequiredSwapMetadata) return false;
|
|
3045
|
-
return refundableVtxos.some(
|
|
3046
|
-
(vtxo) => !vtxo.isSpent && !(0, import_sdk8.isRecoverable)(vtxo)
|
|
3047
|
-
);
|
|
3015
|
+
return refundableVtxos.some((vtxo) => !vtxo.isSpent && !(0, import_sdk8.isRecoverable)(vtxo));
|
|
3048
3016
|
};
|
|
3049
3017
|
var isSubmarineRefundLocktimeReached = (refundTimestamp) => Math.floor(Date.now() / 1e3) >= refundTimestamp;
|
|
3050
3018
|
var CLAIM_VTXO_RETRY_ATTEMPTS = 3;
|
|
3051
3019
|
var CLAIM_VTXO_RETRY_DELAY_MS = 500;
|
|
3020
|
+
var quoteOptionsForSwap = (swap) => {
|
|
3021
|
+
const amount = swap.response?.claimDetails?.amount;
|
|
3022
|
+
return typeof amount === "number" ? { minAcceptableAmount: amount } : void 0;
|
|
3023
|
+
};
|
|
3052
3024
|
var ArkadeSwaps = class _ArkadeSwaps {
|
|
3053
3025
|
/** The Arkade wallet instance used for signing and address generation. */
|
|
3054
3026
|
wallet;
|
|
@@ -3084,10 +3056,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3084
3056
|
return new _ArkadeSwaps(config);
|
|
3085
3057
|
}
|
|
3086
3058
|
const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
|
|
3087
|
-
if (!arkProvider)
|
|
3088
|
-
throw new Error(
|
|
3089
|
-
"Ark provider is required either in wallet or config."
|
|
3090
|
-
);
|
|
3059
|
+
if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
|
|
3091
3060
|
const arkInfo = await arkProvider.getInfo();
|
|
3092
3061
|
const network = arkInfo.network;
|
|
3093
3062
|
const swapProvider = new BoltzSwapProvider({ network });
|
|
@@ -3098,16 +3067,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3098
3067
|
if (!config.swapProvider) throw new Error("Swap provider is required.");
|
|
3099
3068
|
this.wallet = config.wallet;
|
|
3100
3069
|
const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
|
|
3101
|
-
if (!arkProvider)
|
|
3102
|
-
throw new Error(
|
|
3103
|
-
"Ark provider is required either in wallet or config."
|
|
3104
|
-
);
|
|
3070
|
+
if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
|
|
3105
3071
|
this.arkProvider = arkProvider;
|
|
3106
3072
|
const indexerProvider = config.indexerProvider ?? config.wallet.indexerProvider;
|
|
3107
3073
|
if (!indexerProvider)
|
|
3108
|
-
throw new Error(
|
|
3109
|
-
"Indexer provider is required either in wallet or config."
|
|
3110
|
-
);
|
|
3074
|
+
throw new Error("Indexer provider is required either in wallet or config.");
|
|
3111
3075
|
this.indexerProvider = indexerProvider;
|
|
3112
3076
|
this.swapProvider = config.swapProvider;
|
|
3113
3077
|
if (config.swapRepository) {
|
|
@@ -3118,10 +3082,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3118
3082
|
if (config.swapManager !== false) {
|
|
3119
3083
|
const swapManagerConfig = !config.swapManager || config.swapManager === true ? {} : config.swapManager;
|
|
3120
3084
|
const shouldAutostart = swapManagerConfig.autoStart ?? true;
|
|
3121
|
-
this.swapManager = new SwapManager(
|
|
3122
|
-
this.swapProvider,
|
|
3123
|
-
swapManagerConfig
|
|
3124
|
-
);
|
|
3085
|
+
this.swapManager = new SwapManager(this.swapProvider, swapManagerConfig);
|
|
3125
3086
|
this.swapManager.setCallbacks({
|
|
3126
3087
|
claim: async (swap) => {
|
|
3127
3088
|
await this.claimVHTLC(swap);
|
|
@@ -3136,7 +3097,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3136
3097
|
await this.claimBtc(swap);
|
|
3137
3098
|
},
|
|
3138
3099
|
refundArk: async (swap) => {
|
|
3139
|
-
|
|
3100
|
+
return this.refundArk(swap);
|
|
3140
3101
|
},
|
|
3141
3102
|
signServerClaim: async (swap) => {
|
|
3142
3103
|
await this.signCooperativeClaimForServer(swap);
|
|
@@ -3269,19 +3230,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3269
3230
|
* @throws {SwapError} If amount is <= 0 or key retrieval fails.
|
|
3270
3231
|
*/
|
|
3271
3232
|
async createReverseSwap(args) {
|
|
3272
|
-
if (args.amount <= 0)
|
|
3273
|
-
|
|
3274
|
-
const claimPublicKey = import_base9.hex.encode(
|
|
3275
|
-
await this.wallet.identity.compressedPublicKey()
|
|
3276
|
-
);
|
|
3233
|
+
if (args.amount <= 0) throw new SwapError({ message: "Amount must be greater than 0" });
|
|
3234
|
+
const claimPublicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
3277
3235
|
if (!claimPublicKey)
|
|
3278
3236
|
throw new SwapError({
|
|
3279
3237
|
message: "Failed to get claim public key from wallet"
|
|
3280
3238
|
});
|
|
3281
3239
|
const preimage = (0, import_utils3.randomBytes)(32);
|
|
3282
3240
|
const preimageHash = import_base9.hex.encode((0, import_sha23.sha256)(preimage));
|
|
3283
|
-
if (!preimageHash)
|
|
3284
|
-
throw new SwapError({ message: "Failed to get preimage hash" });
|
|
3241
|
+
if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
|
|
3285
3242
|
const swapRequest = {
|
|
3286
3243
|
invoiceAmount: args.amount,
|
|
3287
3244
|
claimPublicKey,
|
|
@@ -3311,18 +3268,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3311
3268
|
*/
|
|
3312
3269
|
async claimVHTLC(pendingSwap) {
|
|
3313
3270
|
if (!pendingSwap.preimage)
|
|
3314
|
-
throw new Error(
|
|
3315
|
-
`Swap ${pendingSwap.id}: preimage is required to claim VHTLC`
|
|
3316
|
-
);
|
|
3271
|
+
throw new Error(`Swap ${pendingSwap.id}: preimage is required to claim VHTLC`);
|
|
3317
3272
|
const {
|
|
3318
3273
|
refundPublicKey,
|
|
3319
3274
|
lockupAddress,
|
|
3320
3275
|
timeoutBlockHeights: vhtlcTimeouts
|
|
3321
3276
|
} = pendingSwap.response;
|
|
3322
3277
|
if (!refundPublicKey || !lockupAddress || !vhtlcTimeouts)
|
|
3323
|
-
throw new Error(
|
|
3324
|
-
`Swap ${pendingSwap.id}: incomplete reverse swap response`
|
|
3325
|
-
);
|
|
3278
|
+
throw new Error(`Swap ${pendingSwap.id}: incomplete reverse swap response`);
|
|
3326
3279
|
const preimage = import_base9.hex.decode(pendingSwap.preimage);
|
|
3327
3280
|
const arkInfo = await this.arkProvider.getInfo();
|
|
3328
3281
|
const address = await this.wallet.getAddress();
|
|
@@ -3357,58 +3310,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3357
3310
|
throw new Error(
|
|
3358
3311
|
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3359
3312
|
);
|
|
3360
|
-
let
|
|
3313
|
+
let unspentVtxos = [];
|
|
3314
|
+
let rawVtxos = [];
|
|
3361
3315
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
3362
|
-
const
|
|
3316
|
+
const result = await this.indexerProvider.getVtxos({
|
|
3363
3317
|
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
3364
3318
|
});
|
|
3365
|
-
|
|
3366
|
-
|
|
3319
|
+
rawVtxos = result.vtxos;
|
|
3320
|
+
unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
3321
|
+
if (unspentVtxos.length > 0) {
|
|
3367
3322
|
break;
|
|
3368
3323
|
}
|
|
3369
3324
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
3370
|
-
await new Promise(
|
|
3371
|
-
(resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
|
|
3372
|
-
);
|
|
3325
|
+
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
3373
3326
|
}
|
|
3374
3327
|
}
|
|
3375
|
-
if (
|
|
3376
|
-
|
|
3377
|
-
`Swap ${pendingSwap.id}: no spendable virtual coins found`
|
|
3378
|
-
|
|
3379
|
-
}
|
|
3380
|
-
if (vtxo.isSpent) {
|
|
3328
|
+
if (unspentVtxos.length === 0) {
|
|
3329
|
+
if (rawVtxos.length === 0) {
|
|
3330
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
3331
|
+
}
|
|
3381
3332
|
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3382
3333
|
}
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3334
|
+
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
3335
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3336
|
+
const claimErrors = [];
|
|
3337
|
+
let usedOffchainClaim = false;
|
|
3338
|
+
for (const vtxo of unspentVtxos) {
|
|
3339
|
+
const input = {
|
|
3340
|
+
...vtxo,
|
|
3341
|
+
tapLeafScript: vhtlcScript.claim(),
|
|
3342
|
+
tapTree: vhtlcScript.encode()
|
|
3343
|
+
};
|
|
3344
|
+
const output = {
|
|
3345
|
+
amount: BigInt(vtxo.value),
|
|
3346
|
+
script: outputScript
|
|
3347
|
+
};
|
|
3348
|
+
try {
|
|
3349
|
+
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
3350
|
+
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
3351
|
+
} else {
|
|
3352
|
+
await claimVHTLCwithOffchainTx(
|
|
3353
|
+
vhtlcIdentity,
|
|
3354
|
+
vhtlcScript,
|
|
3355
|
+
serverXOnly,
|
|
3356
|
+
input,
|
|
3357
|
+
output,
|
|
3358
|
+
arkInfo,
|
|
3359
|
+
this.arkProvider
|
|
3360
|
+
);
|
|
3361
|
+
usedOffchainClaim = true;
|
|
3362
|
+
}
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
claimErrors.push({ vtxo, error });
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
if (claimErrors.length > 0) {
|
|
3368
|
+
const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
|
|
3369
|
+
throw new Error(
|
|
3370
|
+
`Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
|
|
3409
3371
|
);
|
|
3410
|
-
finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
|
|
3411
3372
|
}
|
|
3373
|
+
const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
|
|
3412
3374
|
await updateReverseSwapStatus(
|
|
3413
3375
|
pendingSwap,
|
|
3414
3376
|
finalStatus,
|
|
@@ -3511,9 +3473,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3511
3473
|
async sendLightningPayment(args) {
|
|
3512
3474
|
const pendingSwap = await this.createSubmarineSwap(args);
|
|
3513
3475
|
if (!pendingSwap.response.address)
|
|
3514
|
-
throw new Error(
|
|
3515
|
-
`Swap ${pendingSwap.id}: missing address in submarine swap response`
|
|
3516
|
-
);
|
|
3476
|
+
throw new Error(`Swap ${pendingSwap.id}: missing address in submarine swap response`);
|
|
3517
3477
|
await this.savePendingSubmarineSwap(pendingSwap);
|
|
3518
3478
|
const txid = await this.wallet.send({
|
|
3519
3479
|
address: pendingSwap.response.address,
|
|
@@ -3546,9 +3506,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3546
3506
|
* @throws {SwapError} If invoice is missing or key retrieval fails.
|
|
3547
3507
|
*/
|
|
3548
3508
|
async createSubmarineSwap(args) {
|
|
3549
|
-
const refundPublicKey = import_base9.hex.encode(
|
|
3550
|
-
await this.wallet.identity.compressedPublicKey()
|
|
3551
|
-
);
|
|
3509
|
+
const refundPublicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
3552
3510
|
if (!refundPublicKey)
|
|
3553
3511
|
throw new SwapError({
|
|
3554
3512
|
message: "Failed to get refund public key from wallet"
|
|
@@ -3586,9 +3544,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3586
3544
|
async buildSubmarineVHTLCContext(swap, arkInfo) {
|
|
3587
3545
|
const preimageHash = swap.request.invoice ? getInvoicePaymentHash(swap.request.invoice) : swap.preimageHash;
|
|
3588
3546
|
if (!preimageHash)
|
|
3589
|
-
throw new Error(
|
|
3590
|
-
`Swap ${swap.id}: preimage hash is required to refund VHTLC`
|
|
3591
|
-
);
|
|
3547
|
+
throw new Error(`Swap ${swap.id}: preimage hash is required to refund VHTLC`);
|
|
3592
3548
|
const resolvedArkInfo = arkInfo ?? await this.arkProvider.getInfo();
|
|
3593
3549
|
const ourXOnlyPublicKey = normalizeToXOnlyKey(
|
|
3594
3550
|
await this.wallet.identity.xOnlyPublicKey(),
|
|
@@ -3602,9 +3558,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3602
3558
|
);
|
|
3603
3559
|
const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
|
|
3604
3560
|
if (!claimPublicKey || !vhtlcTimeouts)
|
|
3605
|
-
throw new Error(
|
|
3606
|
-
`Swap ${swap.id}: incomplete submarine swap response`
|
|
3607
|
-
);
|
|
3561
|
+
throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
|
|
3608
3562
|
const boltzXOnlyPublicKey = normalizeToXOnlyKey(
|
|
3609
3563
|
import_base9.hex.decode(claimPublicKey),
|
|
3610
3564
|
"boltz",
|
|
@@ -3619,9 +3573,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3619
3573
|
timeoutBlockHeights: vhtlcTimeouts
|
|
3620
3574
|
});
|
|
3621
3575
|
if (!vhtlcScript.claimScript)
|
|
3622
|
-
throw new Error(
|
|
3623
|
-
`Swap ${swap.id}: failed to create VHTLC script for submarine swap`
|
|
3624
|
-
);
|
|
3576
|
+
throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
|
|
3625
3577
|
if (vhtlcAddress !== swap.response.address)
|
|
3626
3578
|
throw new Error(
|
|
3627
3579
|
`VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
|
|
@@ -3660,10 +3612,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3660
3612
|
recoverableOnly: true
|
|
3661
3613
|
})
|
|
3662
3614
|
]);
|
|
3663
|
-
const refundableVtxos = dedupeVtxos([
|
|
3664
|
-
...spendableResult.vtxos,
|
|
3665
|
-
...recoverableResult.vtxos
|
|
3666
|
-
]);
|
|
3615
|
+
const refundableVtxos = dedupeVtxos([...spendableResult.vtxos, ...recoverableResult.vtxos]);
|
|
3667
3616
|
let diagnostic;
|
|
3668
3617
|
if (refundableVtxos.length === 0) {
|
|
3669
3618
|
const { vtxos: allVtxos } = await this.indexerProvider.getVtxos({
|
|
@@ -3683,13 +3632,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3683
3632
|
submarineRecoveryInfoFromLookup(swap, lookup) {
|
|
3684
3633
|
const { refundableVtxos, diagnostic, vhtlcTimeouts } = lookup;
|
|
3685
3634
|
if (refundableVtxos.length > 0) {
|
|
3686
|
-
const cltvSatisfied = isSubmarineRefundLocktimeReached(
|
|
3687
|
-
|
|
3688
|
-
);
|
|
3689
|
-
const amountSats = refundableVtxos.reduce(
|
|
3690
|
-
(sum, vtxo) => sum + Number(vtxo.value),
|
|
3691
|
-
0
|
|
3692
|
-
);
|
|
3635
|
+
const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
|
|
3636
|
+
const amountSats = refundableVtxos.reduce((sum, vtxo) => sum + Number(vtxo.value), 0);
|
|
3693
3637
|
const isRecoverable2 = cltvSatisfied || canRecoverViaBoltz3of3(refundableVtxos, swap);
|
|
3694
3638
|
return {
|
|
3695
3639
|
swap,
|
|
@@ -3753,19 +3697,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3753
3697
|
);
|
|
3754
3698
|
}
|
|
3755
3699
|
if (diagnostic.allSpent) {
|
|
3756
|
-
throw new Error(
|
|
3757
|
-
`Swap ${pendingSwap.id}: VHTLC is already spent`
|
|
3758
|
-
);
|
|
3700
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3759
3701
|
}
|
|
3760
|
-
throw new Error(
|
|
3761
|
-
`Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`
|
|
3762
|
-
);
|
|
3702
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`);
|
|
3763
3703
|
}
|
|
3764
3704
|
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3765
3705
|
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
3766
|
-
const cltvSatisfied = isSubmarineRefundLocktimeReached(
|
|
3767
|
-
vhtlcTimeouts.refund
|
|
3768
|
-
);
|
|
3706
|
+
const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
|
|
3769
3707
|
let boltzCallCount = 0;
|
|
3770
3708
|
let sweptCount = 0;
|
|
3771
3709
|
let skippedCount = 0;
|
|
@@ -3807,6 +3745,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3807
3745
|
if (boltzCallCount > 0) {
|
|
3808
3746
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3809
3747
|
}
|
|
3748
|
+
boltzCallCount++;
|
|
3810
3749
|
await refundVHTLCwithOffchainTx(
|
|
3811
3750
|
pendingSwap.id,
|
|
3812
3751
|
this.wallet.identity,
|
|
@@ -3817,11 +3756,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3817
3756
|
input,
|
|
3818
3757
|
output,
|
|
3819
3758
|
arkInfo,
|
|
3820
|
-
this.swapProvider.refundSubmarineSwap.bind(
|
|
3821
|
-
this.swapProvider
|
|
3822
|
-
)
|
|
3759
|
+
this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3823
3760
|
);
|
|
3824
|
-
boltzCallCount++;
|
|
3825
3761
|
sweptCount++;
|
|
3826
3762
|
} catch (error) {
|
|
3827
3763
|
if (!(error instanceof BoltzRefundError)) {
|
|
@@ -3842,13 +3778,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3842
3778
|
tapLeafScript: refundWithoutReceiverLeaf,
|
|
3843
3779
|
tapTree: vhtlcScript.encode()
|
|
3844
3780
|
};
|
|
3845
|
-
await this.joinBatch(
|
|
3846
|
-
this.wallet.identity,
|
|
3847
|
-
fallbackInput,
|
|
3848
|
-
output,
|
|
3849
|
-
arkInfo,
|
|
3850
|
-
false
|
|
3851
|
-
);
|
|
3781
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
3852
3782
|
sweptCount++;
|
|
3853
3783
|
}
|
|
3854
3784
|
}
|
|
@@ -3944,10 +3874,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3944
3874
|
try {
|
|
3945
3875
|
return {
|
|
3946
3876
|
swap,
|
|
3947
|
-
context: await this.buildSubmarineVHTLCContext(
|
|
3948
|
-
swap,
|
|
3949
|
-
arkInfo
|
|
3950
|
-
)
|
|
3877
|
+
context: await this.buildSubmarineVHTLCContext(swap, arkInfo)
|
|
3951
3878
|
};
|
|
3952
3879
|
} catch (err) {
|
|
3953
3880
|
return {
|
|
@@ -3960,9 +3887,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3960
3887
|
const valid = prepared.filter(
|
|
3961
3888
|
(item) => "context" in item
|
|
3962
3889
|
);
|
|
3963
|
-
const scripts = [
|
|
3964
|
-
...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))
|
|
3965
|
-
];
|
|
3890
|
+
const scripts = [...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))];
|
|
3966
3891
|
const refundableByScript = /* @__PURE__ */ new Map();
|
|
3967
3892
|
if (scripts.length > 0) {
|
|
3968
3893
|
const [spendableResult, recoverableResult] = await Promise.all([
|
|
@@ -3997,9 +3922,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3997
3922
|
error: item.error
|
|
3998
3923
|
};
|
|
3999
3924
|
}
|
|
4000
|
-
const refundableVtxos = refundableByScript.get(
|
|
4001
|
-
item.context.vhtlcPkScriptHex.toLowerCase()
|
|
4002
|
-
) ?? [];
|
|
3925
|
+
const refundableVtxos = refundableByScript.get(item.context.vhtlcPkScriptHex.toLowerCase()) ?? [];
|
|
4003
3926
|
return this.submarineRecoveryInfoFromLookup(item.swap, {
|
|
4004
3927
|
...item.context,
|
|
4005
3928
|
refundableVtxos
|
|
@@ -4178,9 +4101,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4178
4101
|
*/
|
|
4179
4102
|
async waitAndClaimBtc(pendingSwap) {
|
|
4180
4103
|
if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
|
|
4181
|
-
const { txid } = await this.swapManager.waitForSwapCompletion(
|
|
4182
|
-
pendingSwap.id
|
|
4183
|
-
);
|
|
4104
|
+
const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
|
|
4184
4105
|
return { txid };
|
|
4185
4106
|
}
|
|
4186
4107
|
return new Promise((resolve, reject) => {
|
|
@@ -4207,24 +4128,25 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4207
4128
|
}
|
|
4208
4129
|
case "transaction.claimed":
|
|
4209
4130
|
await updateSwapStatus();
|
|
4210
|
-
const claimedStatus = await this.getSwapStatus(
|
|
4211
|
-
pendingSwap.id
|
|
4212
|
-
);
|
|
4131
|
+
const claimedStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4213
4132
|
resolve({
|
|
4214
4133
|
txid: claimedStatus.transaction?.id ?? ""
|
|
4215
4134
|
});
|
|
4216
4135
|
break;
|
|
4217
4136
|
case "transaction.lockupFailed":
|
|
4218
4137
|
await updateSwapStatus();
|
|
4219
|
-
await this.quoteSwap(swap.response.id).catch(
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4138
|
+
await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
|
|
4139
|
+
(err) => {
|
|
4140
|
+
reject(
|
|
4141
|
+
new SwapError({
|
|
4142
|
+
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4143
|
+
isRefundable: true,
|
|
4144
|
+
pendingSwap: swap,
|
|
4145
|
+
cause: err
|
|
4146
|
+
})
|
|
4147
|
+
);
|
|
4148
|
+
}
|
|
4149
|
+
);
|
|
4228
4150
|
break;
|
|
4229
4151
|
case "swap.expired":
|
|
4230
4152
|
await updateSwapStatus();
|
|
@@ -4262,30 +4184,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4262
4184
|
*/
|
|
4263
4185
|
async claimBtc(pendingSwap) {
|
|
4264
4186
|
if (!pendingSwap.toAddress)
|
|
4265
|
-
throw new Error(
|
|
4266
|
-
`Swap ${pendingSwap.id}: destination address is required`
|
|
4267
|
-
);
|
|
4187
|
+
throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
|
|
4268
4188
|
if (!pendingSwap.response.claimDetails.swapTree)
|
|
4269
|
-
throw new Error(
|
|
4270
|
-
`Swap ${pendingSwap.id}: missing swap tree in claim details`
|
|
4271
|
-
);
|
|
4189
|
+
throw new Error(`Swap ${pendingSwap.id}: missing swap tree in claim details`);
|
|
4272
4190
|
if (!pendingSwap.response.claimDetails.serverPublicKey)
|
|
4273
|
-
throw new Error(
|
|
4274
|
-
`Swap ${pendingSwap.id}: missing server public key in claim details`
|
|
4275
|
-
);
|
|
4191
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
|
|
4276
4192
|
const swapStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4277
4193
|
if (!swapStatus.transaction?.hex)
|
|
4278
|
-
throw new Error(
|
|
4279
|
-
|
|
4280
|
-
);
|
|
4281
|
-
const lockupTx = import_btc_signer5.Transaction.fromRaw(
|
|
4282
|
-
import_base9.hex.decode(swapStatus.transaction.hex)
|
|
4283
|
-
);
|
|
4194
|
+
throw new Error(`Swap ${pendingSwap.id}: BTC transaction hex is required`);
|
|
4195
|
+
const lockupTx = import_btc_signer5.Transaction.fromRaw(import_base9.hex.decode(swapStatus.transaction.hex));
|
|
4284
4196
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4285
4197
|
const network = arkInfo.network === "bitcoin" ? import_utils4.NETWORK : arkInfo.network === "mutinynet" ? MUTINYNET_NETWORK : REGTEST_NETWORK;
|
|
4286
|
-
const swapTree = deserializeSwapTree(
|
|
4287
|
-
pendingSwap.response.claimDetails.swapTree
|
|
4288
|
-
);
|
|
4198
|
+
const swapTree = deserializeSwapTree(pendingSwap.response.claimDetails.swapTree);
|
|
4289
4199
|
const musig = tweakMusig(
|
|
4290
4200
|
create(import_base9.hex.decode(pendingSwap.ephemeralKey), [
|
|
4291
4201
|
import_base9.hex.decode(pendingSwap.response.claimDetails.serverPublicKey),
|
|
@@ -4306,19 +4216,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4306
4216
|
vout: swapOutput.vout,
|
|
4307
4217
|
transactionId: lockupTx.id
|
|
4308
4218
|
},
|
|
4309
|
-
import_btc_signer5.OutScript.encode(
|
|
4310
|
-
(0, import_btc_signer5.Address)(network).decode(pendingSwap.toAddress)
|
|
4311
|
-
),
|
|
4219
|
+
import_btc_signer5.OutScript.encode((0, import_btc_signer5.Address)(network).decode(pendingSwap.toAddress)),
|
|
4312
4220
|
feeToDeliverExactAmount > fee ? feeToDeliverExactAmount : fee
|
|
4313
4221
|
)
|
|
4314
4222
|
);
|
|
4315
4223
|
const musigMessage = musig.message(
|
|
4316
|
-
claimTx.preimageWitnessV1(
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
import_btc_signer5.SigHash.DEFAULT,
|
|
4320
|
-
[swapOutput.amount]
|
|
4321
|
-
)
|
|
4224
|
+
claimTx.preimageWitnessV1(0, [swapOutput.script], import_btc_signer5.SigHash.DEFAULT, [
|
|
4225
|
+
swapOutput.amount
|
|
4226
|
+
])
|
|
4322
4227
|
).generateNonce();
|
|
4323
4228
|
const signedTxData = await this.swapProvider.postChainClaimDetails(
|
|
4324
4229
|
pendingSwap.response.id,
|
|
@@ -4332,14 +4237,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4332
4237
|
}
|
|
4333
4238
|
);
|
|
4334
4239
|
if (!signedTxData.pubNonce || !signedTxData.partialSignature)
|
|
4335
|
-
throw new Error(
|
|
4336
|
-
`Swap ${pendingSwap.id}: invalid signature data from server`
|
|
4337
|
-
);
|
|
4240
|
+
throw new Error(`Swap ${pendingSwap.id}: invalid signature data from server`);
|
|
4338
4241
|
const musigSession = musigMessage.aggregateNonces([
|
|
4339
4242
|
[
|
|
4340
|
-
import_base9.hex.decode(
|
|
4341
|
-
pendingSwap.response.claimDetails.serverPublicKey
|
|
4342
|
-
),
|
|
4243
|
+
import_base9.hex.decode(pendingSwap.response.claimDetails.serverPublicKey),
|
|
4343
4244
|
import_base9.hex.decode(signedTxData.pubNonce)
|
|
4344
4245
|
]
|
|
4345
4246
|
]).initializeSession();
|
|
@@ -4354,18 +4255,23 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4354
4255
|
await this.swapProvider.postBtcTransaction(claimTx.hex);
|
|
4355
4256
|
}
|
|
4356
4257
|
/**
|
|
4357
|
-
* When an ARK to BTC swap fails, refund
|
|
4258
|
+
* When an ARK to BTC swap fails, refund every unspent VTXO at the chain
|
|
4259
|
+
* swap's ARK lockup address.
|
|
4260
|
+
*
|
|
4261
|
+
* Path selection per VTXO:
|
|
4262
|
+
* - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
|
|
4263
|
+
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
|
|
4264
|
+
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
|
|
4265
|
+
*
|
|
4358
4266
|
* @param pendingSwap - The pending chain swap to refund.
|
|
4267
|
+
* @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
|
|
4268
|
+
* the call was a no-op — callers should retry after CLTV.
|
|
4359
4269
|
*/
|
|
4360
4270
|
async refundArk(pendingSwap) {
|
|
4361
4271
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
4362
|
-
throw new Error(
|
|
4363
|
-
`Swap ${pendingSwap.id}: missing server public key in lockup details`
|
|
4364
|
-
);
|
|
4272
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
|
|
4365
4273
|
if (!pendingSwap.response.lockupDetails.timeouts)
|
|
4366
|
-
throw new Error(
|
|
4367
|
-
`Swap ${pendingSwap.id}: missing timeouts in lockup details`
|
|
4368
|
-
);
|
|
4274
|
+
throw new Error(`Swap ${pendingSwap.id}: missing timeouts in lockup details`);
|
|
4369
4275
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4370
4276
|
const address = await this.wallet.getAddress();
|
|
4371
4277
|
const ourXOnlyPublicKey = normalizeToXOnlyKey(
|
|
@@ -4383,21 +4289,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4383
4289
|
"boltz",
|
|
4384
4290
|
pendingSwap.id
|
|
4385
4291
|
);
|
|
4386
|
-
const vhtlcPkScript = import_sdk8.ArkAddress.decode(
|
|
4387
|
-
pendingSwap.response.lockupDetails.lockupAddress
|
|
4388
|
-
).pkScript;
|
|
4389
|
-
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4390
|
-
scripts: [import_base9.hex.encode(vhtlcPkScript)]
|
|
4391
|
-
});
|
|
4392
|
-
if (vtxos.length === 0) {
|
|
4393
|
-
throw new Error(
|
|
4394
|
-
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4395
|
-
);
|
|
4396
|
-
}
|
|
4397
|
-
const vtxo = vtxos[0];
|
|
4398
|
-
if (vtxo.isSpent) {
|
|
4399
|
-
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4400
|
-
}
|
|
4401
4292
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4402
4293
|
network: arkInfo.network,
|
|
4403
4294
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4407,45 +4298,111 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4407
4298
|
timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
|
|
4408
4299
|
});
|
|
4409
4300
|
if (!vhtlcScript.refundScript)
|
|
4410
|
-
throw new Error(
|
|
4411
|
-
`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
|
|
4412
|
-
);
|
|
4301
|
+
throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
|
|
4413
4302
|
if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
|
|
4414
4303
|
throw new SwapError({
|
|
4415
4304
|
message: "Unable to claim: invalid VHTLC address"
|
|
4416
4305
|
});
|
|
4417
4306
|
}
|
|
4418
|
-
const
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
const output = {
|
|
4425
|
-
amount: BigInt(vtxo.value),
|
|
4426
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4427
|
-
};
|
|
4428
|
-
if (isRecoverableVtxo) {
|
|
4429
|
-
await this.joinBatch(this.wallet.identity, input, output, arkInfo);
|
|
4430
|
-
} else {
|
|
4431
|
-
await refundVHTLCwithOffchainTx(
|
|
4432
|
-
pendingSwap.id,
|
|
4433
|
-
this.wallet.identity,
|
|
4434
|
-
this.arkProvider,
|
|
4435
|
-
boltzXOnlyPublicKey,
|
|
4436
|
-
ourXOnlyPublicKey,
|
|
4437
|
-
serverXOnlyPublicKey,
|
|
4438
|
-
input,
|
|
4439
|
-
output,
|
|
4440
|
-
arkInfo,
|
|
4441
|
-
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4307
|
+
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4308
|
+
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
4309
|
+
});
|
|
4310
|
+
if (vtxos.length === 0) {
|
|
4311
|
+
throw new Error(
|
|
4312
|
+
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4442
4313
|
);
|
|
4443
4314
|
}
|
|
4315
|
+
const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
4316
|
+
if (unspentVtxos.length === 0) {
|
|
4317
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4318
|
+
}
|
|
4319
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
4320
|
+
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
4321
|
+
const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
|
|
4322
|
+
let boltzCallCount = 0;
|
|
4323
|
+
let sweptCount = 0;
|
|
4324
|
+
let skippedCount = 0;
|
|
4325
|
+
for (const vtxo of unspentVtxos) {
|
|
4326
|
+
const isRecoverableVtxo = (0, import_sdk8.isRecoverable)(vtxo);
|
|
4327
|
+
const output = {
|
|
4328
|
+
amount: BigInt(vtxo.value),
|
|
4329
|
+
script: outputScript
|
|
4330
|
+
};
|
|
4331
|
+
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4332
|
+
const input2 = {
|
|
4333
|
+
...vtxo,
|
|
4334
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4335
|
+
tapTree: vhtlcScript.encode()
|
|
4336
|
+
};
|
|
4337
|
+
await this.joinBatch(
|
|
4338
|
+
this.wallet.identity,
|
|
4339
|
+
input2,
|
|
4340
|
+
output,
|
|
4341
|
+
arkInfo,
|
|
4342
|
+
isRecoverableVtxo
|
|
4343
|
+
);
|
|
4344
|
+
sweptCount++;
|
|
4345
|
+
continue;
|
|
4346
|
+
}
|
|
4347
|
+
if (isRecoverableVtxo) {
|
|
4348
|
+
logger.error(
|
|
4349
|
+
`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.`
|
|
4350
|
+
);
|
|
4351
|
+
skippedCount++;
|
|
4352
|
+
continue;
|
|
4353
|
+
}
|
|
4354
|
+
const input = {
|
|
4355
|
+
...vtxo,
|
|
4356
|
+
tapLeafScript: vhtlcScript.refund(),
|
|
4357
|
+
tapTree: vhtlcScript.encode()
|
|
4358
|
+
};
|
|
4359
|
+
try {
|
|
4360
|
+
if (boltzCallCount > 0) {
|
|
4361
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4362
|
+
}
|
|
4363
|
+
boltzCallCount++;
|
|
4364
|
+
await refundVHTLCwithOffchainTx(
|
|
4365
|
+
pendingSwap.id,
|
|
4366
|
+
this.wallet.identity,
|
|
4367
|
+
this.arkProvider,
|
|
4368
|
+
boltzXOnlyPublicKey,
|
|
4369
|
+
ourXOnlyPublicKey,
|
|
4370
|
+
serverXOnlyPublicKey,
|
|
4371
|
+
input,
|
|
4372
|
+
output,
|
|
4373
|
+
arkInfo,
|
|
4374
|
+
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4375
|
+
);
|
|
4376
|
+
sweptCount++;
|
|
4377
|
+
} catch (error) {
|
|
4378
|
+
if (!(error instanceof BoltzRefundError)) {
|
|
4379
|
+
throw error;
|
|
4380
|
+
}
|
|
4381
|
+
if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4382
|
+
logger.error(
|
|
4383
|
+
`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.`
|
|
4384
|
+
);
|
|
4385
|
+
skippedCount++;
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
logger.warn(
|
|
4389
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
|
|
4390
|
+
);
|
|
4391
|
+
const fallbackInput = {
|
|
4392
|
+
...vtxo,
|
|
4393
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4394
|
+
tapTree: vhtlcScript.encode()
|
|
4395
|
+
};
|
|
4396
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
4397
|
+
sweptCount++;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4444
4400
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4445
4401
|
await this.savePendingChainSwap({
|
|
4446
4402
|
...pendingSwap,
|
|
4447
4403
|
status: finalStatus.status
|
|
4448
4404
|
});
|
|
4405
|
+
return { swept: sweptCount, skipped: skippedCount };
|
|
4449
4406
|
}
|
|
4450
4407
|
// =========================================================================
|
|
4451
4408
|
// Chain swaps: BTC -> ARK
|
|
@@ -4490,9 +4447,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4490
4447
|
*/
|
|
4491
4448
|
async waitAndClaimArk(pendingSwap) {
|
|
4492
4449
|
if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
|
|
4493
|
-
const { txid } = await this.swapManager.waitForSwapCompletion(
|
|
4494
|
-
pendingSwap.id
|
|
4495
|
-
);
|
|
4450
|
+
const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
|
|
4496
4451
|
return { txid };
|
|
4497
4452
|
}
|
|
4498
4453
|
return new Promise((resolve, reject) => {
|
|
@@ -4513,37 +4468,33 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4513
4468
|
break;
|
|
4514
4469
|
case "transaction.claimed":
|
|
4515
4470
|
await updateSwapStatus();
|
|
4516
|
-
const claimedStatus = await this.getSwapStatus(
|
|
4517
|
-
pendingSwap.id
|
|
4518
|
-
);
|
|
4471
|
+
const claimedStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4519
4472
|
resolve({
|
|
4520
4473
|
txid: claimedStatus.transaction?.id ?? ""
|
|
4521
4474
|
});
|
|
4522
4475
|
break;
|
|
4523
4476
|
case "transaction.claim.pending":
|
|
4524
4477
|
await updateSwapStatus();
|
|
4525
|
-
await this.signCooperativeClaimForServer(swap).catch(
|
|
4478
|
+
await this.signCooperativeClaimForServer(swap).catch((err) => {
|
|
4479
|
+
logger.error(`Failed to sign cooperative claim for ${swap.id}:`, err);
|
|
4480
|
+
});
|
|
4481
|
+
break;
|
|
4482
|
+
case "transaction.lockupFailed":
|
|
4483
|
+
await updateSwapStatus();
|
|
4484
|
+
await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
|
|
4526
4485
|
(err) => {
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4486
|
+
reject(
|
|
4487
|
+
new SwapError({
|
|
4488
|
+
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4489
|
+
isRefundable: false,
|
|
4490
|
+
// TODO btc refund not implemented yet
|
|
4491
|
+
pendingSwap: swap,
|
|
4492
|
+
cause: err
|
|
4493
|
+
})
|
|
4530
4494
|
);
|
|
4531
4495
|
}
|
|
4532
4496
|
);
|
|
4533
4497
|
break;
|
|
4534
|
-
case "transaction.lockupFailed":
|
|
4535
|
-
await updateSwapStatus();
|
|
4536
|
-
await this.quoteSwap(swap.response.id).catch((err) => {
|
|
4537
|
-
reject(
|
|
4538
|
-
new SwapError({
|
|
4539
|
-
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4540
|
-
isRefundable: false,
|
|
4541
|
-
// TODO btc refund not implemented yet
|
|
4542
|
-
pendingSwap: swap
|
|
4543
|
-
})
|
|
4544
|
-
);
|
|
4545
|
-
});
|
|
4546
|
-
break;
|
|
4547
4498
|
case "swap.expired":
|
|
4548
4499
|
await updateSwapStatus();
|
|
4549
4500
|
reject(
|
|
@@ -4583,17 +4534,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4583
4534
|
*/
|
|
4584
4535
|
async claimArk(pendingSwap) {
|
|
4585
4536
|
if (!pendingSwap.toAddress)
|
|
4586
|
-
throw new Error(
|
|
4587
|
-
`Swap ${pendingSwap.id}: destination address is required`
|
|
4588
|
-
);
|
|
4537
|
+
throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
|
|
4589
4538
|
if (!pendingSwap.response.claimDetails.serverPublicKey)
|
|
4590
|
-
throw new Error(
|
|
4591
|
-
`Swap ${pendingSwap.id}: missing server public key in claim details`
|
|
4592
|
-
);
|
|
4539
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
|
|
4593
4540
|
if (!pendingSwap.response.claimDetails.timeouts)
|
|
4594
|
-
throw new Error(
|
|
4595
|
-
`Swap ${pendingSwap.id}: missing timeouts in claim details`
|
|
4596
|
-
);
|
|
4541
|
+
throw new Error(`Swap ${pendingSwap.id}: missing timeouts in claim details`);
|
|
4597
4542
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4598
4543
|
const preimage = import_base9.hex.decode(pendingSwap.preimage);
|
|
4599
4544
|
const address = await this.wallet.getAddress();
|
|
@@ -4605,10 +4550,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4605
4550
|
pendingSwap.response.claimDetails.serverPublicKey,
|
|
4606
4551
|
"sender"
|
|
4607
4552
|
);
|
|
4608
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
4609
|
-
arkInfo.signerPubkey,
|
|
4610
|
-
"server"
|
|
4611
|
-
);
|
|
4553
|
+
const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
|
|
4612
4554
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4613
4555
|
network: arkInfo.network,
|
|
4614
4556
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4618,9 +4560,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4618
4560
|
timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
|
|
4619
4561
|
});
|
|
4620
4562
|
if (!vhtlcScript.claimScript)
|
|
4621
|
-
throw new Error(
|
|
4622
|
-
`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
|
|
4623
|
-
);
|
|
4563
|
+
throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
|
|
4624
4564
|
if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
|
|
4625
4565
|
throw new SwapError({
|
|
4626
4566
|
message: "Unable to claim: invalid VHTLC address"
|
|
@@ -4637,15 +4577,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4637
4577
|
break;
|
|
4638
4578
|
}
|
|
4639
4579
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
4640
|
-
await new Promise(
|
|
4641
|
-
(resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
|
|
4642
|
-
);
|
|
4580
|
+
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
4643
4581
|
}
|
|
4644
4582
|
}
|
|
4645
4583
|
if (!vtxo) {
|
|
4646
|
-
throw new Error(
|
|
4647
|
-
`Swap ${pendingSwap.id}: no spendable virtual coins found`
|
|
4648
|
-
);
|
|
4584
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
4649
4585
|
}
|
|
4650
4586
|
const input = {
|
|
4651
4587
|
...vtxo,
|
|
@@ -4656,10 +4592,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4656
4592
|
amount: BigInt(vtxo.value),
|
|
4657
4593
|
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4658
4594
|
};
|
|
4659
|
-
const vhtlcIdentity = claimVHTLCIdentity(
|
|
4660
|
-
this.wallet.identity,
|
|
4661
|
-
preimage
|
|
4662
|
-
);
|
|
4595
|
+
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
4663
4596
|
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
4664
4597
|
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
4665
4598
|
} else {
|
|
@@ -4685,16 +4618,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4685
4618
|
*/
|
|
4686
4619
|
async signCooperativeClaimForServer(pendingSwap) {
|
|
4687
4620
|
if (!pendingSwap.response.lockupDetails.swapTree)
|
|
4688
|
-
throw new Error(
|
|
4689
|
-
`Swap ${pendingSwap.id}: missing swap tree in lockup details`
|
|
4690
|
-
);
|
|
4621
|
+
throw new Error(`Swap ${pendingSwap.id}: missing swap tree in lockup details`);
|
|
4691
4622
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
4692
|
-
throw new Error(
|
|
4693
|
-
|
|
4694
|
-
);
|
|
4695
|
-
const claimDetails = await this.swapProvider.getChainClaimDetails(
|
|
4696
|
-
pendingSwap.id
|
|
4697
|
-
);
|
|
4623
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
|
|
4624
|
+
const claimDetails = await this.swapProvider.getChainClaimDetails(pendingSwap.id);
|
|
4698
4625
|
const serverPubKey = pendingSwap.response.lockupDetails.serverPublicKey;
|
|
4699
4626
|
if (claimDetails.publicKey !== serverPubKey) {
|
|
4700
4627
|
throw new Error(
|
|
@@ -4710,9 +4637,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4710
4637
|
);
|
|
4711
4638
|
const musigNonces = musig.message(import_base9.hex.decode(claimDetails.transactionHash)).generateNonce().aggregateNonces([
|
|
4712
4639
|
[
|
|
4713
|
-
import_base9.hex.decode(
|
|
4714
|
-
pendingSwap.response.lockupDetails.serverPublicKey
|
|
4715
|
-
),
|
|
4640
|
+
import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
|
|
4716
4641
|
import_base9.hex.decode(claimDetails.pubNonce)
|
|
4717
4642
|
]
|
|
4718
4643
|
]).initializeSession();
|
|
@@ -4731,10 +4656,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4731
4656
|
* @returns The transaction ID of the claim.
|
|
4732
4657
|
*/
|
|
4733
4658
|
async waitAndClaimChain(pendingSwap) {
|
|
4734
|
-
if (pendingSwap.request.to === "ARK")
|
|
4735
|
-
|
|
4736
|
-
if (pendingSwap.request.to === "BTC")
|
|
4737
|
-
return this.waitAndClaimBtc(pendingSwap);
|
|
4659
|
+
if (pendingSwap.request.to === "ARK") return this.waitAndClaimArk(pendingSwap);
|
|
4660
|
+
if (pendingSwap.request.to === "BTC") return this.waitAndClaimBtc(pendingSwap);
|
|
4738
4661
|
throw new SwapError({
|
|
4739
4662
|
message: `Unsupported swap destination: ${pendingSwap.request.to}`
|
|
4740
4663
|
});
|
|
@@ -4749,11 +4672,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4749
4672
|
*/
|
|
4750
4673
|
async createChainSwap(args) {
|
|
4751
4674
|
const { to, from, receiverLockAmount, senderLockAmount, toAddress } = args;
|
|
4752
|
-
if (!toAddress)
|
|
4753
|
-
throw new SwapError({ message: "Destination address is required" });
|
|
4675
|
+
if (!toAddress) throw new SwapError({ message: "Destination address is required" });
|
|
4754
4676
|
const feeSatsPerByte = args.feeSatsPerByte ?? 1;
|
|
4755
|
-
if (feeSatsPerByte <= 0)
|
|
4756
|
-
throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
4677
|
+
if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
4757
4678
|
let amount, serverLockAmount, userLockAmount;
|
|
4758
4679
|
if (receiverLockAmount) {
|
|
4759
4680
|
amount = receiverLockAmount;
|
|
@@ -4768,8 +4689,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4768
4689
|
}
|
|
4769
4690
|
const preimage = (0, import_utils3.randomBytes)(32);
|
|
4770
4691
|
const preimageHash = import_base9.hex.encode((0, import_sha23.sha256)(preimage));
|
|
4771
|
-
if (!preimageHash)
|
|
4772
|
-
throw new SwapError({ message: "Failed to get preimage hash" });
|
|
4692
|
+
if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
|
|
4773
4693
|
const ephemeralKey = import_secp256k13.secp256k1.utils.randomSecretKey();
|
|
4774
4694
|
const refundPublicKey = to === "ARK" ? import_base9.hex.encode(import_secp256k13.secp256k1.getPublicKey(ephemeralKey)) : import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
4775
4695
|
if (!refundPublicKey)
|
|
@@ -4818,30 +4738,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4818
4738
|
const { to, from, swap, arkInfo } = args;
|
|
4819
4739
|
if (from === "ARK") {
|
|
4820
4740
|
if (!swap.response.lockupDetails.serverPublicKey)
|
|
4821
|
-
throw new Error(
|
|
4822
|
-
`Swap ${swap.id}: missing serverPublicKey in lockup details`
|
|
4823
|
-
);
|
|
4741
|
+
throw new Error(`Swap ${swap.id}: missing serverPublicKey in lockup details`);
|
|
4824
4742
|
if (!swap.response.lockupDetails.timeouts)
|
|
4825
|
-
throw new Error(
|
|
4826
|
-
`Swap ${swap.id}: missing timeouts in lockup details`
|
|
4827
|
-
);
|
|
4743
|
+
throw new Error(`Swap ${swap.id}: missing timeouts in lockup details`);
|
|
4828
4744
|
}
|
|
4829
4745
|
if (to === "ARK") {
|
|
4830
4746
|
if (!swap.response.claimDetails.serverPublicKey)
|
|
4831
|
-
throw new Error(
|
|
4832
|
-
`Swap ${swap.id}: missing serverPublicKey in claim details`
|
|
4833
|
-
);
|
|
4747
|
+
throw new Error(`Swap ${swap.id}: missing serverPublicKey in claim details`);
|
|
4834
4748
|
if (!swap.response.claimDetails.timeouts)
|
|
4835
|
-
throw new Error(
|
|
4836
|
-
`Swap ${swap.id}: missing timeouts in claim details`
|
|
4837
|
-
);
|
|
4749
|
+
throw new Error(`Swap ${swap.id}: missing timeouts in claim details`);
|
|
4838
4750
|
}
|
|
4839
4751
|
const lockupAddress = to === "ARK" ? swap.response.claimDetails.lockupAddress : swap.response.lockupDetails.lockupAddress;
|
|
4840
4752
|
const receiverPubkey = to === "ARK" ? swap.request.claimPublicKey : swap.response.lockupDetails.serverPublicKey;
|
|
4841
4753
|
const senderPubkey = to === "ARK" ? swap.response.claimDetails.serverPublicKey : swap.request.refundPublicKey;
|
|
4842
|
-
const serverPubkey = import_base9.hex.encode(
|
|
4843
|
-
normalizeToXOnlyKey(arkInfo.signerPubkey, "server")
|
|
4844
|
-
);
|
|
4754
|
+
const serverPubkey = import_base9.hex.encode(normalizeToXOnlyKey(arkInfo.signerPubkey, "server"));
|
|
4845
4755
|
const vhtlcTimeouts = to === "ARK" ? swap.response.claimDetails.timeouts : swap.response.lockupDetails.timeouts;
|
|
4846
4756
|
const { vhtlcAddress } = this.createVHTLCScript({
|
|
4847
4757
|
network: arkInfo.network,
|
|
@@ -4859,15 +4769,122 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4859
4769
|
return true;
|
|
4860
4770
|
}
|
|
4861
4771
|
/**
|
|
4862
|
-
* Renegotiates the quote for an existing swap.
|
|
4772
|
+
* Renegotiates the quote for an existing chain swap. Convenience wrapper
|
|
4773
|
+
* over `getSwapQuote` + `acceptSwapQuote` with a safety floor.
|
|
4774
|
+
*
|
|
4775
|
+
* The floor is resolved in order:
|
|
4776
|
+
* 1. `options.minAcceptableAmount` if provided.
|
|
4777
|
+
* 2. The original `response.claimDetails.amount` of the stored
|
|
4778
|
+
* pending swap (Boltz-confirmed server-lock amount at creation).
|
|
4779
|
+
* 3. Otherwise throws `QuoteRejectedError({ reason: "no_baseline" })`.
|
|
4780
|
+
*
|
|
4781
|
+
* `options.maxSlippageBps` (default 0) relaxes the floor by basis points.
|
|
4782
|
+
* Quotes ≤ 0 are always rejected. On rejection the acceptance is NOT
|
|
4783
|
+
* posted to Boltz.
|
|
4784
|
+
*
|
|
4785
|
+
* Prefer `getSwapQuote` / `acceptSwapQuote` for callers that want to
|
|
4786
|
+
* inspect the quote before committing.
|
|
4787
|
+
*
|
|
4863
4788
|
* @param swapId - The ID of the swap.
|
|
4789
|
+
* @param options - Optional floor and slippage configuration.
|
|
4864
4790
|
* @returns The accepted quote amount.
|
|
4791
|
+
* @throws QuoteRejectedError if the quote is non-positive, below the
|
|
4792
|
+
* effective floor, or no baseline is available.
|
|
4793
|
+
*/
|
|
4794
|
+
async quoteSwap(swapId, options) {
|
|
4795
|
+
const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
|
|
4796
|
+
const amount = await this.getSwapQuote(swapId);
|
|
4797
|
+
this.validateQuote(amount, effectiveFloor);
|
|
4798
|
+
await this.swapProvider.postChainQuote(swapId, { amount });
|
|
4799
|
+
return amount;
|
|
4800
|
+
}
|
|
4801
|
+
/**
|
|
4802
|
+
* Fetches a renegotiated quote from Boltz without accepting it.
|
|
4803
|
+
* Pair with `acceptSwapQuote` to commit a specific value.
|
|
4865
4804
|
*/
|
|
4866
|
-
async
|
|
4805
|
+
async getSwapQuote(swapId) {
|
|
4867
4806
|
const { amount } = await this.swapProvider.getChainQuote(swapId);
|
|
4807
|
+
return amount;
|
|
4808
|
+
}
|
|
4809
|
+
/**
|
|
4810
|
+
* Accepts a quote amount for an existing chain swap, after validating it
|
|
4811
|
+
* against the configured floor. See `quoteSwap` for floor-resolution rules.
|
|
4812
|
+
*
|
|
4813
|
+
* @throws QuoteRejectedError if `amount` ≤ 0, below the effective floor,
|
|
4814
|
+
* or no baseline is available.
|
|
4815
|
+
*/
|
|
4816
|
+
async acceptSwapQuote(swapId, amount, options) {
|
|
4817
|
+
const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
|
|
4818
|
+
this.validateQuote(amount, effectiveFloor);
|
|
4868
4819
|
await this.swapProvider.postChainQuote(swapId, { amount });
|
|
4869
4820
|
return amount;
|
|
4870
4821
|
}
|
|
4822
|
+
async resolveEffectiveFloor(swapId, options) {
|
|
4823
|
+
this.validateQuoteOptions(options);
|
|
4824
|
+
const floor = await this.resolveQuoteFloor(swapId, options);
|
|
4825
|
+
const slippageBps = options?.maxSlippageBps ?? 0;
|
|
4826
|
+
const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
|
|
4827
|
+
if (effectiveFloor < 1) {
|
|
4828
|
+
throw new TypeError(
|
|
4829
|
+
`Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4832
|
+
return effectiveFloor;
|
|
4833
|
+
}
|
|
4834
|
+
async resolveQuoteFloor(swapId, options) {
|
|
4835
|
+
if (options?.minAcceptableAmount !== void 0) {
|
|
4836
|
+
return options.minAcceptableAmount;
|
|
4837
|
+
}
|
|
4838
|
+
const swaps = await this.swapRepository.getAllSwaps({
|
|
4839
|
+
id: swapId,
|
|
4840
|
+
type: "chain"
|
|
4841
|
+
});
|
|
4842
|
+
const stored = swaps[0];
|
|
4843
|
+
const amount = stored?.response?.claimDetails?.amount;
|
|
4844
|
+
if (typeof amount !== "number") {
|
|
4845
|
+
throw new QuoteRejectedError({ reason: "no_baseline" });
|
|
4846
|
+
}
|
|
4847
|
+
return amount;
|
|
4848
|
+
}
|
|
4849
|
+
validateQuoteOptions(options) {
|
|
4850
|
+
if (options?.minAcceptableAmount !== void 0) {
|
|
4851
|
+
const v = options.minAcceptableAmount;
|
|
4852
|
+
if (!Number.isInteger(v) || v <= 0) {
|
|
4853
|
+
throw new TypeError(
|
|
4854
|
+
`Invalid minAcceptableAmount: ${v} \u2014 must be a positive integer`
|
|
4855
|
+
);
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
if (options?.maxSlippageBps !== void 0) {
|
|
4859
|
+
const v = options.maxSlippageBps;
|
|
4860
|
+
if (!Number.isInteger(v) || v < 0 || v > 1e4) {
|
|
4861
|
+
throw new TypeError(
|
|
4862
|
+
`Invalid maxSlippageBps: ${v} \u2014 must be an integer in [0, 10000]`
|
|
4863
|
+
);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
validateQuote(amount, effectiveFloor) {
|
|
4868
|
+
if (!Number.isSafeInteger(amount)) {
|
|
4869
|
+
throw new QuoteRejectedError({
|
|
4870
|
+
reason: "non_safe_integer",
|
|
4871
|
+
quotedAmount: amount
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4874
|
+
if (amount <= 0) {
|
|
4875
|
+
throw new QuoteRejectedError({
|
|
4876
|
+
reason: "non_positive",
|
|
4877
|
+
quotedAmount: amount
|
|
4878
|
+
});
|
|
4879
|
+
}
|
|
4880
|
+
if (amount < effectiveFloor) {
|
|
4881
|
+
throw new QuoteRejectedError({
|
|
4882
|
+
reason: "below_floor",
|
|
4883
|
+
quotedAmount: amount,
|
|
4884
|
+
floor: effectiveFloor
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4871
4888
|
// =========================================================================
|
|
4872
4889
|
// Shared utilities
|
|
4873
4890
|
// =========================================================================
|
|
@@ -4881,14 +4898,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4881
4898
|
* @returns The commitment transaction ID.
|
|
4882
4899
|
*/
|
|
4883
4900
|
async joinBatch(identity, input, output, arkInfo, isRecoverable2 = true) {
|
|
4884
|
-
return joinBatch(
|
|
4885
|
-
this.arkProvider,
|
|
4886
|
-
identity,
|
|
4887
|
-
input,
|
|
4888
|
-
output,
|
|
4889
|
-
arkInfo,
|
|
4890
|
-
isRecoverable2
|
|
4891
|
-
);
|
|
4901
|
+
return joinBatch(this.arkProvider, identity, input, output, arkInfo, isRecoverable2);
|
|
4892
4902
|
}
|
|
4893
4903
|
/**
|
|
4894
4904
|
* Creates a VHTLC script for the swap.
|
|
@@ -4926,9 +4936,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4926
4936
|
async getPendingSubmarineSwaps() {
|
|
4927
4937
|
const swaps = await this.getPendingSubmarineSwapsFromStorage();
|
|
4928
4938
|
if (!swaps) return [];
|
|
4929
|
-
return swaps.filter(
|
|
4930
|
-
(swap) => swap.status === "invoice.set"
|
|
4931
|
-
);
|
|
4939
|
+
return swaps.filter((swap) => swap.status === "invoice.set");
|
|
4932
4940
|
}
|
|
4933
4941
|
/**
|
|
4934
4942
|
* Returns pending reverse swaps (those with status `swap.created`).
|
|
@@ -4936,9 +4944,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4936
4944
|
async getPendingReverseSwaps() {
|
|
4937
4945
|
const swaps = await this.getPendingReverseSwapsFromStorage();
|
|
4938
4946
|
if (!swaps) return [];
|
|
4939
|
-
return swaps.filter(
|
|
4940
|
-
(swap) => swap.status === "swap.created"
|
|
4941
|
-
);
|
|
4947
|
+
return swaps.filter((swap) => swap.status === "swap.created");
|
|
4942
4948
|
}
|
|
4943
4949
|
/**
|
|
4944
4950
|
* Returns pending chain swaps (those with status `swap.created`).
|
|
@@ -4977,10 +4983,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4977
4983
|
this.savePendingReverseSwap.bind(this)
|
|
4978
4984
|
)
|
|
4979
4985
|
).catch((error) => {
|
|
4980
|
-
logger.error(
|
|
4981
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
4982
|
-
error
|
|
4983
|
-
);
|
|
4986
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
4984
4987
|
})
|
|
4985
4988
|
);
|
|
4986
4989
|
}
|
|
@@ -4994,23 +4997,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4994
4997
|
this.savePendingSubmarineSwap.bind(this)
|
|
4995
4998
|
)
|
|
4996
4999
|
).catch((error) => {
|
|
4997
|
-
logger.error(
|
|
4998
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
4999
|
-
error
|
|
5000
|
-
);
|
|
5000
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
5001
5001
|
})
|
|
5002
5002
|
);
|
|
5003
5003
|
}
|
|
5004
5004
|
for (const swap of await this.getPendingChainSwapsFromStorage()) {
|
|
5005
5005
|
if (isChainFinalStatus(swap.status)) continue;
|
|
5006
5006
|
promises.push(
|
|
5007
|
-
this.getSwapStatus(swap.id).then(
|
|
5008
|
-
(
|
|
5009
|
-
).catch((error) => {
|
|
5010
|
-
logger.error(
|
|
5011
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
5012
|
-
error
|
|
5013
|
-
);
|
|
5007
|
+
this.getSwapStatus(swap.id).then(({ status }) => this.savePendingChainSwap({ ...swap, status })).catch((error) => {
|
|
5008
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
5014
5009
|
})
|
|
5015
5010
|
);
|
|
5016
5011
|
}
|
|
@@ -5027,9 +5022,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5027
5022
|
* display/monitoring and are not automatically wired into the SwapManager.
|
|
5028
5023
|
*/
|
|
5029
5024
|
async restoreSwaps(boltzFees) {
|
|
5030
|
-
const publicKey = import_base9.hex.encode(
|
|
5031
|
-
await this.wallet.identity.compressedPublicKey()
|
|
5032
|
-
);
|
|
5025
|
+
const publicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
5033
5026
|
if (!publicKey) throw new Error("Failed to get public key from wallet");
|
|
5034
5027
|
const fees = boltzFees ?? await this.swapProvider.getFees();
|
|
5035
5028
|
const chainSwaps = [];
|
|
@@ -5081,25 +5074,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5081
5074
|
preimage: ""
|
|
5082
5075
|
});
|
|
5083
5076
|
} else if (isRestoredSubmarineSwap(swap)) {
|
|
5084
|
-
const {
|
|
5085
|
-
amount,
|
|
5086
|
-
lockupAddress,
|
|
5087
|
-
serverPublicKey,
|
|
5088
|
-
tree,
|
|
5089
|
-
timeoutBlockHeights
|
|
5090
|
-
} = swap.refundDetails;
|
|
5077
|
+
const { amount, lockupAddress, serverPublicKey, tree, timeoutBlockHeights } = swap.refundDetails;
|
|
5091
5078
|
let preimage = "";
|
|
5092
5079
|
if (!isSubmarineFinalStatus(status)) {
|
|
5093
5080
|
try {
|
|
5094
|
-
const data = await this.swapProvider.getSwapPreimage(
|
|
5095
|
-
swap.id
|
|
5096
|
-
);
|
|
5081
|
+
const data = await this.swapProvider.getSwapPreimage(swap.id);
|
|
5097
5082
|
preimage = data.preimage;
|
|
5098
5083
|
} catch (error) {
|
|
5099
|
-
logger.warn(
|
|
5100
|
-
`Failed to restore preimage for submarine swap ${id}`,
|
|
5101
|
-
error
|
|
5102
|
-
);
|
|
5084
|
+
logger.warn(`Failed to restore preimage for submarine swap ${id}`, error);
|
|
5103
5085
|
}
|
|
5104
5086
|
}
|
|
5105
5087
|
submarineSwaps.push({
|
|
@@ -5137,12 +5119,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5137
5119
|
} else if (isRestoredChainSwap(swap)) {
|
|
5138
5120
|
const refundDetails = swap.refundDetails;
|
|
5139
5121
|
if (!refundDetails) continue;
|
|
5140
|
-
const {
|
|
5141
|
-
amount,
|
|
5142
|
-
lockupAddress,
|
|
5143
|
-
serverPublicKey,
|
|
5144
|
-
timeoutBlockHeight
|
|
5145
|
-
} = refundDetails;
|
|
5122
|
+
const { amount, lockupAddress, serverPublicKey, timeoutBlockHeight } = refundDetails;
|
|
5146
5123
|
chainSwaps.push({
|
|
5147
5124
|
id,
|
|
5148
5125
|
type: "chain",
|
|
@@ -5195,19 +5172,11 @@ var SWAP_POLL_TASK_TYPE = "swap-poll";
|
|
|
5195
5172
|
var swapsPollProcessor = {
|
|
5196
5173
|
taskType: SWAP_POLL_TASK_TYPE,
|
|
5197
5174
|
async execute(item, deps) {
|
|
5198
|
-
const {
|
|
5199
|
-
swapRepository,
|
|
5200
|
-
swapProvider,
|
|
5201
|
-
wallet,
|
|
5202
|
-
arkProvider,
|
|
5203
|
-
indexerProvider
|
|
5204
|
-
} = deps;
|
|
5175
|
+
const { swapRepository, swapProvider, wallet, arkProvider, indexerProvider } = deps;
|
|
5205
5176
|
const allSwaps = await swapRepository.getAllSwaps();
|
|
5206
5177
|
const pendingSwaps = allSwaps.filter((swap) => {
|
|
5207
|
-
if (isPendingReverseSwap(swap))
|
|
5208
|
-
|
|
5209
|
-
if (isPendingSubmarineSwap(swap))
|
|
5210
|
-
return !isSubmarineFinalStatus(swap.status);
|
|
5178
|
+
if (isPendingReverseSwap(swap)) return !isReverseFinalStatus(swap.status);
|
|
5179
|
+
if (isPendingSubmarineSwap(swap)) return !isSubmarineFinalStatus(swap.status);
|
|
5211
5180
|
return false;
|
|
5212
5181
|
});
|
|
5213
5182
|
let polled = 0;
|
|
@@ -5237,19 +5206,14 @@ var swapsPollProcessor = {
|
|
|
5237
5206
|
}
|
|
5238
5207
|
if (isPendingReverseSwap(swap) && isReverseClaimableStatus(currentStatus)) {
|
|
5239
5208
|
if (!swap.preimage) {
|
|
5240
|
-
logger.warn(
|
|
5241
|
-
`[swap-poll] Skipping claim for ${swap.id}: no preimage`
|
|
5242
|
-
);
|
|
5209
|
+
logger.warn(`[swap-poll] Skipping claim for ${swap.id}: no preimage`);
|
|
5243
5210
|
continue;
|
|
5244
5211
|
}
|
|
5245
5212
|
try {
|
|
5246
5213
|
await tempSwaps.claimVHTLC(swap);
|
|
5247
5214
|
claimed++;
|
|
5248
5215
|
} catch (claimError) {
|
|
5249
|
-
logger.error(
|
|
5250
|
-
`[swap-poll] Claim failed for ${swap.id}:`,
|
|
5251
|
-
claimError
|
|
5252
|
-
);
|
|
5216
|
+
logger.error(`[swap-poll] Claim failed for ${swap.id}:`, claimError);
|
|
5253
5217
|
errors++;
|
|
5254
5218
|
}
|
|
5255
5219
|
}
|
|
@@ -5265,18 +5229,12 @@ var swapsPollProcessor = {
|
|
|
5265
5229
|
await tempSwaps.refundVHTLC(swapWithStatus);
|
|
5266
5230
|
refunded++;
|
|
5267
5231
|
} catch (refundError) {
|
|
5268
|
-
logger.error(
|
|
5269
|
-
`[swap-poll] Refund failed for ${swap.id}:`,
|
|
5270
|
-
refundError
|
|
5271
|
-
);
|
|
5232
|
+
logger.error(`[swap-poll] Refund failed for ${swap.id}:`, refundError);
|
|
5272
5233
|
errors++;
|
|
5273
5234
|
}
|
|
5274
5235
|
}
|
|
5275
5236
|
} catch (swapError) {
|
|
5276
|
-
logger.error(
|
|
5277
|
-
`[swap-poll] Error processing swap ${swap.id}:`,
|
|
5278
|
-
swapError
|
|
5279
|
-
);
|
|
5237
|
+
logger.error(`[swap-poll] Error processing swap ${swap.id}:`, swapError);
|
|
5280
5238
|
errors++;
|
|
5281
5239
|
}
|
|
5282
5240
|
}
|
|
@@ -5293,9 +5251,6 @@ var swapsPollProcessor = {
|
|
|
5293
5251
|
};
|
|
5294
5252
|
|
|
5295
5253
|
// src/expo/background.ts
|
|
5296
|
-
function getRandomId() {
|
|
5297
|
-
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
5298
|
-
}
|
|
5299
5254
|
function createBackgroundWalletShim(args) {
|
|
5300
5255
|
const notImplemented = (method) => {
|
|
5301
5256
|
throw new Error(
|
|
@@ -5311,6 +5266,7 @@ function createBackgroundWalletShim(args) {
|
|
|
5311
5266
|
getBoardingUtxos: async () => notImplemented("getBoardingUtxos"),
|
|
5312
5267
|
getTransactionHistory: async () => notImplemented("getTransactionHistory"),
|
|
5313
5268
|
getContractManager: async () => notImplemented("getContractManager"),
|
|
5269
|
+
getDelegateManager: async () => notImplemented("getDelegateManager"),
|
|
5314
5270
|
getDelegatorManager: async () => notImplemented("getDelegatorManager"),
|
|
5315
5271
|
sendBitcoin: async () => notImplemented("sendBitcoin"),
|
|
5316
5272
|
send: async () => notImplemented("send"),
|
|
@@ -5330,9 +5286,7 @@ function defineExpoSwapBackgroundTask(taskName, options) {
|
|
|
5330
5286
|
}
|
|
5331
5287
|
const identity = await identityFactory();
|
|
5332
5288
|
const arkProvider = new import_expo2.ExpoArkProvider(config.arkServerUrl);
|
|
5333
|
-
const indexerProvider = new import_expo2.ExpoIndexerProvider(
|
|
5334
|
-
config.arkServerUrl
|
|
5335
|
-
);
|
|
5289
|
+
const indexerProvider = new import_expo2.ExpoIndexerProvider(config.arkServerUrl);
|
|
5336
5290
|
const swapProvider = new BoltzSwapProvider({
|
|
5337
5291
|
network: config.network,
|
|
5338
5292
|
apiUrl: config.boltzApiUrl
|
|
@@ -5347,11 +5301,7 @@ function defineExpoSwapBackgroundTask(taskName, options) {
|
|
|
5347
5301
|
const serverPubKey = hex9.decode(info.signerPubkey);
|
|
5348
5302
|
const xOnlyServerPubKey = serverPubKey.length === 33 ? serverPubKey.slice(1) : serverPubKey;
|
|
5349
5303
|
const hrp = info.network === "bitcoin" ? "ark" : "tark";
|
|
5350
|
-
return new ArkAddress3(
|
|
5351
|
-
xOnlyServerPubKey,
|
|
5352
|
-
pubkey,
|
|
5353
|
-
hrp
|
|
5354
|
-
).encode();
|
|
5304
|
+
return new ArkAddress3(xOnlyServerPubKey, pubkey, hrp).encode();
|
|
5355
5305
|
}
|
|
5356
5306
|
});
|
|
5357
5307
|
const deps = {
|
|
@@ -5365,14 +5315,12 @@ function defineExpoSwapBackgroundTask(taskName, options) {
|
|
|
5365
5315
|
await (0, import_expo.runTasks)(taskQueue, [swapsPollProcessor], deps);
|
|
5366
5316
|
const results = await taskQueue.getResults();
|
|
5367
5317
|
if (results.length > 0) {
|
|
5368
|
-
await taskQueue.acknowledgeResults(
|
|
5369
|
-
results.map((r) => r.id)
|
|
5370
|
-
);
|
|
5318
|
+
await taskQueue.acknowledgeResults(results.map((r) => r.id));
|
|
5371
5319
|
}
|
|
5372
5320
|
const existing = await taskQueue.getTasks(SWAP_POLL_TASK_TYPE);
|
|
5373
5321
|
if (existing.length === 0) {
|
|
5374
5322
|
const task = {
|
|
5375
|
-
id: getRandomId(),
|
|
5323
|
+
id: (0, import_sdk9.getRandomId)(),
|
|
5376
5324
|
type: SWAP_POLL_TASK_TYPE,
|
|
5377
5325
|
data: {},
|
|
5378
5326
|
createdAt: Date.now()
|