@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/index.cjs
CHANGED
|
@@ -45,7 +45,10 @@ var SwapError = class extends Error {
|
|
|
45
45
|
/** The pending swap associated with this error, if available. */
|
|
46
46
|
pendingSwap;
|
|
47
47
|
constructor(options = {}) {
|
|
48
|
-
super(
|
|
48
|
+
super(
|
|
49
|
+
options.message ?? "Error during swap.",
|
|
50
|
+
options.cause !== void 0 ? { cause: options.cause } : void 0
|
|
51
|
+
);
|
|
49
52
|
this.name = "SwapError";
|
|
50
53
|
this.isClaimable = options.isClaimable ?? false;
|
|
51
54
|
this.isRefundable = options.isRefundable ?? false;
|
|
@@ -122,6 +125,100 @@ var TransactionRefundedError = class extends SwapError {
|
|
|
122
125
|
this.name = "TransactionRefundedError";
|
|
123
126
|
}
|
|
124
127
|
};
|
|
128
|
+
var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
|
|
129
|
+
reason;
|
|
130
|
+
quotedAmount;
|
|
131
|
+
floor;
|
|
132
|
+
constructor(options) {
|
|
133
|
+
super({
|
|
134
|
+
message: options.message ?? _QuoteRejectedError.defaultMessage(options),
|
|
135
|
+
...options
|
|
136
|
+
});
|
|
137
|
+
this.name = "QuoteRejectedError";
|
|
138
|
+
this.reason = options.reason;
|
|
139
|
+
this.quotedAmount = "quotedAmount" in options ? options.quotedAmount : void 0;
|
|
140
|
+
this.floor = "floor" in options ? options.floor : void 0;
|
|
141
|
+
}
|
|
142
|
+
static defaultMessage(options) {
|
|
143
|
+
switch (options.reason) {
|
|
144
|
+
case "below_floor":
|
|
145
|
+
return `Boltz quote ${options.quotedAmount} is below acceptable floor ${options.floor}`;
|
|
146
|
+
case "non_positive":
|
|
147
|
+
return `Boltz quote ${options.quotedAmount} is not positive`;
|
|
148
|
+
case "non_safe_integer":
|
|
149
|
+
return `Boltz quote ${options.quotedAmount} is not a safe positive satoshi integer`;
|
|
150
|
+
case "no_baseline":
|
|
151
|
+
return "Cannot accept quote: no minAcceptableAmount and no stored pending swap";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Serialize into a plain `Error` whose `.message` carries the full
|
|
156
|
+
* rejection payload as JSON behind a marker prefix. Structured clone
|
|
157
|
+
* (used by `postMessage` between page and service worker) preserves
|
|
158
|
+
* `Error.message` reliably but strips custom `.name` and own properties,
|
|
159
|
+
* so we move the typed data into the message field for transport.
|
|
160
|
+
*/
|
|
161
|
+
toTransportError() {
|
|
162
|
+
return new Error(
|
|
163
|
+
QUOTE_REJECTION_TRANSPORT_PREFIX + JSON.stringify({
|
|
164
|
+
reason: this.reason,
|
|
165
|
+
message: this.message,
|
|
166
|
+
quotedAmount: this.quotedAmount,
|
|
167
|
+
floor: this.floor
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Inverse of `toTransportError`. Returns a real `QuoteRejectedError` if
|
|
173
|
+
* `error` carries the transport prefix, else `null`.
|
|
174
|
+
*/
|
|
175
|
+
static fromTransportError(error) {
|
|
176
|
+
if (!(error instanceof Error) || !error.message.startsWith(QUOTE_REJECTION_TRANSPORT_PREFIX)) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const payload = error.message.slice(QUOTE_REJECTION_TRANSPORT_PREFIX.length);
|
|
180
|
+
let data;
|
|
181
|
+
try {
|
|
182
|
+
data = JSON.parse(payload);
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (typeof data.reason !== "string" || !QUOTE_REJECTION_REASONS.has(data.reason)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const message = typeof data.message === "string" ? data.message : void 0;
|
|
190
|
+
const reason = data.reason;
|
|
191
|
+
const quotedAmount = typeof data.quotedAmount === "number" ? data.quotedAmount : null;
|
|
192
|
+
const floor = typeof data.floor === "number" ? data.floor : null;
|
|
193
|
+
switch (reason) {
|
|
194
|
+
case "below_floor":
|
|
195
|
+
if (quotedAmount === null || floor === null) return null;
|
|
196
|
+
return new _QuoteRejectedError({
|
|
197
|
+
reason,
|
|
198
|
+
quotedAmount,
|
|
199
|
+
floor,
|
|
200
|
+
message
|
|
201
|
+
});
|
|
202
|
+
case "non_positive":
|
|
203
|
+
case "non_safe_integer":
|
|
204
|
+
if (quotedAmount === null) return null;
|
|
205
|
+
return new _QuoteRejectedError({
|
|
206
|
+
reason,
|
|
207
|
+
quotedAmount,
|
|
208
|
+
message
|
|
209
|
+
});
|
|
210
|
+
case "no_baseline":
|
|
211
|
+
return new _QuoteRejectedError({ reason, message });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var QUOTE_REJECTION_TRANSPORT_PREFIX = "QUOTE_REJECTED::";
|
|
216
|
+
var QUOTE_REJECTION_REASONS = /* @__PURE__ */ new Set([
|
|
217
|
+
"below_floor",
|
|
218
|
+
"non_positive",
|
|
219
|
+
"non_safe_integer",
|
|
220
|
+
"no_baseline"
|
|
221
|
+
]);
|
|
125
222
|
var BoltzRefundError = class extends Error {
|
|
126
223
|
constructor(message, cause) {
|
|
127
224
|
super(message);
|
|
@@ -137,18 +234,10 @@ var import_sdk8 = require("@arkade-os/sdk");
|
|
|
137
234
|
var import_sdk = require("@arkade-os/sdk");
|
|
138
235
|
var import_base = require("@scure/base");
|
|
139
236
|
var isSubmarineFinalStatus = (status) => {
|
|
140
|
-
return [
|
|
141
|
-
"invoice.failedToPay",
|
|
142
|
-
"transaction.claimed",
|
|
143
|
-
"swap.expired"
|
|
144
|
-
].includes(status);
|
|
237
|
+
return ["invoice.failedToPay", "transaction.claimed", "swap.expired"].includes(status);
|
|
145
238
|
};
|
|
146
239
|
var isSubmarineRefundableStatus = (status) => {
|
|
147
|
-
return [
|
|
148
|
-
"invoice.failedToPay",
|
|
149
|
-
"transaction.lockupFailed",
|
|
150
|
-
"swap.expired"
|
|
151
|
-
].includes(status);
|
|
240
|
+
return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
|
|
152
241
|
};
|
|
153
242
|
var isSubmarineSuccessStatus = (status) => {
|
|
154
243
|
return status === "transaction.claimed";
|
|
@@ -167,10 +256,7 @@ var isReverseClaimableStatus = (status) => {
|
|
|
167
256
|
return ["transaction.mempool", "transaction.confirmed"].includes(status);
|
|
168
257
|
};
|
|
169
258
|
var isChainClaimableStatus = (status) => {
|
|
170
|
-
return [
|
|
171
|
-
"transaction.server.mempool",
|
|
172
|
-
"transaction.server.confirmed"
|
|
173
|
-
].includes(status);
|
|
259
|
+
return ["transaction.server.mempool", "transaction.server.confirmed"].includes(status);
|
|
174
260
|
};
|
|
175
261
|
var isChainFinalStatus = (status) => {
|
|
176
262
|
return [
|
|
@@ -284,8 +370,7 @@ var BASE_URLS = {
|
|
|
284
370
|
var isSwapNotFoundBody = (error) => {
|
|
285
371
|
const needle = "could not find swap";
|
|
286
372
|
const fromJson = error.errorData?.error;
|
|
287
|
-
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle))
|
|
288
|
-
return true;
|
|
373
|
+
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle)) return true;
|
|
289
374
|
return error.message.toLowerCase().includes(needle);
|
|
290
375
|
};
|
|
291
376
|
var BoltzSwapProvider = class {
|
|
@@ -299,10 +384,7 @@ var BoltzSwapProvider = class {
|
|
|
299
384
|
this.network = config.network;
|
|
300
385
|
this.referralId = config.referralId ?? "arkade-ts-sdk";
|
|
301
386
|
const apiUrl = config.apiUrl || BASE_URLS[config.network];
|
|
302
|
-
if (!apiUrl)
|
|
303
|
-
throw new Error(
|
|
304
|
-
`API URL is required for network: ${config.network}`
|
|
305
|
-
);
|
|
387
|
+
if (!apiUrl) throw new Error(`API URL is required for network: ${config.network}`);
|
|
306
388
|
this.apiUrl = apiUrl;
|
|
307
389
|
this.wsUrl = this.apiUrl.replace(/^http(s)?:\/\//, "ws$1://").replace("9069", "9004") + "/v2/ws";
|
|
308
390
|
}
|
|
@@ -321,10 +403,7 @@ var BoltzSwapProvider = class {
|
|
|
321
403
|
/** Returns current Lightning swap fees (submarine + reverse) from Boltz. */
|
|
322
404
|
async getFees() {
|
|
323
405
|
const [submarine, reverse] = await Promise.all([
|
|
324
|
-
this.request(
|
|
325
|
-
"/v2/swap/submarine",
|
|
326
|
-
"GET"
|
|
327
|
-
),
|
|
406
|
+
this.request("/v2/swap/submarine", "GET"),
|
|
328
407
|
this.request("/v2/swap/reverse", "GET")
|
|
329
408
|
]);
|
|
330
409
|
if (!isGetSubmarinePairsResponse(submarine))
|
|
@@ -344,10 +423,7 @@ var BoltzSwapProvider = class {
|
|
|
344
423
|
}
|
|
345
424
|
/** Returns current Lightning swap min/max limits from Boltz. */
|
|
346
425
|
async getLimits() {
|
|
347
|
-
const response = await this.request(
|
|
348
|
-
"/v2/swap/submarine",
|
|
349
|
-
"GET"
|
|
350
|
-
);
|
|
426
|
+
const response = await this.request("/v2/swap/submarine", "GET");
|
|
351
427
|
if (!isGetSubmarinePairsResponse(response))
|
|
352
428
|
throw new SchemaError({ message: "error fetching limits" });
|
|
353
429
|
return {
|
|
@@ -357,10 +433,7 @@ var BoltzSwapProvider = class {
|
|
|
357
433
|
}
|
|
358
434
|
/** Returns the current BTC chain tip height from Boltz. */
|
|
359
435
|
async getChainHeight() {
|
|
360
|
-
const response = await this.request(
|
|
361
|
-
"/v2/chain/heights",
|
|
362
|
-
"GET"
|
|
363
|
-
);
|
|
436
|
+
const response = await this.request("/v2/chain/heights", "GET");
|
|
364
437
|
if (typeof response?.BTC !== "number")
|
|
365
438
|
throw new SchemaError({
|
|
366
439
|
message: "error fetching chain heights"
|
|
@@ -390,10 +463,7 @@ var BoltzSwapProvider = class {
|
|
|
390
463
|
async getSwapStatus(id) {
|
|
391
464
|
let response;
|
|
392
465
|
try {
|
|
393
|
-
response = await this.request(
|
|
394
|
-
`/v2/swap/${id}`,
|
|
395
|
-
"GET"
|
|
396
|
-
);
|
|
466
|
+
response = await this.request(`/v2/swap/${id}`, "GET");
|
|
397
467
|
} catch (error) {
|
|
398
468
|
if (error instanceof NetworkError && error.statusCode === 404 && isSwapNotFoundBody(error)) {
|
|
399
469
|
throw new SwapNotFoundError(id, error.errorData);
|
|
@@ -489,12 +559,10 @@ var BoltzSwapProvider = class {
|
|
|
489
559
|
throw new SwapError({ message: "Invalid 'to' chain" });
|
|
490
560
|
if (["BTC", "ARK"].indexOf(from) === -1)
|
|
491
561
|
throw new SwapError({ message: "Invalid 'from' chain" });
|
|
492
|
-
if (to === from)
|
|
493
|
-
throw new SwapError({ message: "Invalid swap direction" });
|
|
562
|
+
if (to === from) throw new SwapError({ message: "Invalid swap direction" });
|
|
494
563
|
if (!preimageHash || preimageHash.length != 64)
|
|
495
564
|
throw new SwapError({ message: "Invalid preimageHash" });
|
|
496
|
-
if (feeSatsPerByte <= 0)
|
|
497
|
-
throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
565
|
+
if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
498
566
|
if (serverLockAmount !== void 0 && userLockAmount !== void 0 || serverLockAmount === void 0 && userLockAmount === void 0)
|
|
499
567
|
throw new SwapError({
|
|
500
568
|
message: "Either serverLockAmount or userLockAmount must be provided"
|
|
@@ -549,12 +617,8 @@ var BoltzSwapProvider = class {
|
|
|
549
617
|
message: "Error refunding submarine swap"
|
|
550
618
|
});
|
|
551
619
|
return {
|
|
552
|
-
transaction: import_sdk.Transaction.fromPSBT(
|
|
553
|
-
|
|
554
|
-
),
|
|
555
|
-
checkpoint: import_sdk.Transaction.fromPSBT(
|
|
556
|
-
import_base.base64.decode(response.checkpoint)
|
|
557
|
-
)
|
|
620
|
+
transaction: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.transaction)),
|
|
621
|
+
checkpoint: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.checkpoint))
|
|
558
622
|
};
|
|
559
623
|
}
|
|
560
624
|
/** Requests Boltz co-signature for a chain swap refund. Returns signed transaction + checkpoint. */
|
|
@@ -573,53 +637,53 @@ var BoltzSwapProvider = class {
|
|
|
573
637
|
message: "Error refunding chain swap"
|
|
574
638
|
});
|
|
575
639
|
return {
|
|
576
|
-
transaction: import_sdk.Transaction.fromPSBT(
|
|
577
|
-
|
|
578
|
-
),
|
|
579
|
-
checkpoint: import_sdk.Transaction.fromPSBT(
|
|
580
|
-
import_base.base64.decode(response.checkpoint)
|
|
581
|
-
)
|
|
640
|
+
transaction: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.transaction)),
|
|
641
|
+
checkpoint: import_sdk.Transaction.fromPSBT(import_base.base64.decode(response.checkpoint))
|
|
582
642
|
};
|
|
583
643
|
}
|
|
584
|
-
/**
|
|
644
|
+
/**
|
|
645
|
+
* Monitors swap status updates and forwards them to the update callback.
|
|
646
|
+
* Prefers a WebSocket subscription; on connection error or premature close
|
|
647
|
+
* it falls back to REST polling so callers don't fail when the WS endpoint
|
|
648
|
+
* is flaky (observed in CI). Resolves when the swap reaches a terminal
|
|
649
|
+
* status.
|
|
650
|
+
*/
|
|
585
651
|
async monitorSwap(swapId, update) {
|
|
586
652
|
return new Promise((resolve, reject) => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
);
|
|
653
|
+
let settled = false;
|
|
654
|
+
let lastStatus = null;
|
|
655
|
+
let pollTimer = null;
|
|
656
|
+
let webSocket = null;
|
|
657
|
+
let connectionTimeout = null;
|
|
658
|
+
const cleanup = () => {
|
|
659
|
+
if (connectionTimeout) {
|
|
660
|
+
clearTimeout(connectionTimeout);
|
|
661
|
+
connectionTimeout = null;
|
|
662
|
+
}
|
|
663
|
+
if (pollTimer) {
|
|
664
|
+
clearInterval(pollTimer);
|
|
665
|
+
pollTimer = null;
|
|
666
|
+
}
|
|
667
|
+
if (webSocket) {
|
|
668
|
+
try {
|
|
669
|
+
webSocket.close();
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
webSocket = null;
|
|
673
|
+
}
|
|
609
674
|
};
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
675
|
+
const finish = (err) => {
|
|
676
|
+
if (settled) return;
|
|
677
|
+
settled = true;
|
|
678
|
+
cleanup();
|
|
679
|
+
if (err) reject(err);
|
|
680
|
+
else resolve();
|
|
613
681
|
};
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
reject(new SwapError({ message: msg.args[0].error }));
|
|
620
|
-
}
|
|
621
|
-
const status = msg.args[0].status;
|
|
622
|
-
const negotiable = status === "transaction.lockupFailed" && msg.args[0].failureDetails?.actual !== void 0 && msg.args[0].failureDetails?.expected !== void 0;
|
|
682
|
+
const handleStatus = (status, data) => {
|
|
683
|
+
if (settled) return;
|
|
684
|
+
if (status === lastStatus) return;
|
|
685
|
+
lastStatus = status;
|
|
686
|
+
const negotiable = status === "transaction.lockupFailed" && data?.failureDetails?.actual !== void 0 && data?.failureDetails?.expected !== void 0;
|
|
623
687
|
switch (status) {
|
|
624
688
|
case "invoice.settled":
|
|
625
689
|
case "transaction.claimed":
|
|
@@ -628,13 +692,13 @@ var BoltzSwapProvider = class {
|
|
|
628
692
|
case "invoice.failedToPay":
|
|
629
693
|
case "transaction.failed":
|
|
630
694
|
case "swap.expired":
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
695
|
+
update(status, data);
|
|
696
|
+
finish();
|
|
697
|
+
return;
|
|
634
698
|
case "transaction.lockupFailed":
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
699
|
+
update(status, data);
|
|
700
|
+
if (!negotiable) finish();
|
|
701
|
+
return;
|
|
638
702
|
case "invoice.paid":
|
|
639
703
|
case "invoice.pending":
|
|
640
704
|
case "invoice.set":
|
|
@@ -644,9 +708,77 @@ var BoltzSwapProvider = class {
|
|
|
644
708
|
case "transaction.claim.pending":
|
|
645
709
|
case "transaction.server.mempool":
|
|
646
710
|
case "transaction.server.confirmed":
|
|
647
|
-
update(status,
|
|
711
|
+
update(status, data);
|
|
712
|
+
return;
|
|
648
713
|
}
|
|
649
714
|
};
|
|
715
|
+
const startPolling = () => {
|
|
716
|
+
if (settled || pollTimer) return;
|
|
717
|
+
const poll = async () => {
|
|
718
|
+
if (settled) return;
|
|
719
|
+
try {
|
|
720
|
+
const result = await this.getSwapStatus(swapId);
|
|
721
|
+
handleStatus(result.status, { ...result, id: swapId });
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
pollTimer = setInterval(poll, 1e3);
|
|
726
|
+
poll();
|
|
727
|
+
};
|
|
728
|
+
try {
|
|
729
|
+
webSocket = new globalThis.WebSocket(this.wsUrl);
|
|
730
|
+
connectionTimeout = setTimeout(() => {
|
|
731
|
+
try {
|
|
732
|
+
webSocket?.close();
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
webSocket = null;
|
|
736
|
+
startPolling();
|
|
737
|
+
}, 5e3);
|
|
738
|
+
webSocket.onerror = () => {
|
|
739
|
+
if (connectionTimeout) {
|
|
740
|
+
clearTimeout(connectionTimeout);
|
|
741
|
+
connectionTimeout = null;
|
|
742
|
+
}
|
|
743
|
+
webSocket = null;
|
|
744
|
+
startPolling();
|
|
745
|
+
};
|
|
746
|
+
webSocket.onopen = () => {
|
|
747
|
+
if (connectionTimeout) {
|
|
748
|
+
clearTimeout(connectionTimeout);
|
|
749
|
+
connectionTimeout = null;
|
|
750
|
+
}
|
|
751
|
+
webSocket?.send(
|
|
752
|
+
JSON.stringify({
|
|
753
|
+
op: "subscribe",
|
|
754
|
+
channel: "swap.update",
|
|
755
|
+
args: [swapId]
|
|
756
|
+
})
|
|
757
|
+
);
|
|
758
|
+
};
|
|
759
|
+
webSocket.onclose = () => {
|
|
760
|
+
if (connectionTimeout) {
|
|
761
|
+
clearTimeout(connectionTimeout);
|
|
762
|
+
connectionTimeout = null;
|
|
763
|
+
}
|
|
764
|
+
if (!settled && !pollTimer) startPolling();
|
|
765
|
+
};
|
|
766
|
+
webSocket.onmessage = (rawMsg) => {
|
|
767
|
+
try {
|
|
768
|
+
const msg = JSON.parse(rawMsg.data);
|
|
769
|
+
if (msg.event !== "update" || msg.args?.[0]?.id !== swapId) return;
|
|
770
|
+
if (msg.args[0].error) {
|
|
771
|
+
finish(new SwapError({ message: msg.args[0].error }));
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
handleStatus(msg.args[0].status, msg.args[0]);
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
} catch {
|
|
779
|
+
webSocket = null;
|
|
780
|
+
startPolling();
|
|
781
|
+
}
|
|
650
782
|
});
|
|
651
783
|
}
|
|
652
784
|
/** Returns current chain swap fees for a given pair (e.g. ARK→BTC). */
|
|
@@ -654,10 +786,7 @@ var BoltzSwapProvider = class {
|
|
|
654
786
|
if (from === to) {
|
|
655
787
|
throw new SwapError({ message: "Invalid chain pair" });
|
|
656
788
|
}
|
|
657
|
-
const response = await this.request(
|
|
658
|
-
"/v2/swap/chain",
|
|
659
|
-
"GET"
|
|
660
|
-
);
|
|
789
|
+
const response = await this.request("/v2/swap/chain", "GET");
|
|
661
790
|
if (!isGetChainPairsResponse(response))
|
|
662
791
|
throw new SchemaError({ message: "error fetching fees" });
|
|
663
792
|
if (!response[from]?.[to]) {
|
|
@@ -672,10 +801,7 @@ var BoltzSwapProvider = class {
|
|
|
672
801
|
if (from === to) {
|
|
673
802
|
throw new SwapError({ message: "Invalid chain pair" });
|
|
674
803
|
}
|
|
675
|
-
const response = await this.request(
|
|
676
|
-
"/v2/swap/chain",
|
|
677
|
-
"GET"
|
|
678
|
-
);
|
|
804
|
+
const response = await this.request("/v2/swap/chain", "GET");
|
|
679
805
|
if (!isGetChainPairsResponse(response))
|
|
680
806
|
throw new SchemaError({ message: "error fetching limits" });
|
|
681
807
|
if (!response[from]?.[to]) {
|
|
@@ -804,9 +930,7 @@ var BoltzSwapProvider = class {
|
|
|
804
930
|
return await response.json();
|
|
805
931
|
} catch (error) {
|
|
806
932
|
if (error instanceof NetworkError) throw error;
|
|
807
|
-
throw new NetworkError(
|
|
808
|
-
`Request to ${url} failed: ${error.message}`
|
|
809
|
-
);
|
|
933
|
+
throw new NetworkError(`Request to ${url} failed: ${error.message}`);
|
|
810
934
|
}
|
|
811
935
|
}
|
|
812
936
|
};
|
|
@@ -828,8 +952,7 @@ var findKeyIndex = (keys, target) => keys.findIndex((k) => (0, import_utils.equa
|
|
|
828
952
|
var assertPublicKeys = (keys) => {
|
|
829
953
|
const seen = /* @__PURE__ */ new Set();
|
|
830
954
|
for (const key of keys) {
|
|
831
|
-
if (key.length !== 33)
|
|
832
|
-
throw new Error(`public key must be 33 bytes, got ${key.length}`);
|
|
955
|
+
if (key.length !== 33) throw new Error(`public key must be 33 bytes, got ${key.length}`);
|
|
833
956
|
const enc = import_base2.hex.encode(key);
|
|
834
957
|
if (seen.has(enc)) throw new Error(`duplicate public key ${enc}`);
|
|
835
958
|
seen.add(enc);
|
|
@@ -837,9 +960,7 @@ var assertPublicKeys = (keys) => {
|
|
|
837
960
|
};
|
|
838
961
|
var aggregateKeys = (publicKeys, tweak) => {
|
|
839
962
|
assertPublicKeys([...publicKeys]);
|
|
840
|
-
return (0, import_musig2.keyAggExport)(
|
|
841
|
-
(0, import_musig2.keyAggregate)([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : [])
|
|
842
|
-
);
|
|
963
|
+
return (0, import_musig2.keyAggExport)((0, import_musig2.keyAggregate)([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : []));
|
|
843
964
|
};
|
|
844
965
|
var MusigKeyAgg = class _MusigKeyAgg {
|
|
845
966
|
constructor(privateKey, myPublicKey, publicKeys, myIndex, aggPubkey, internalKey, _tweak) {
|
|
@@ -886,18 +1007,12 @@ var MusigWithMessage = class {
|
|
|
886
1007
|
this.msg = msg;
|
|
887
1008
|
}
|
|
888
1009
|
generateNonce() {
|
|
889
|
-
const nonce = (0, import_musig2.nonceGen)(
|
|
890
|
-
this.myPublicKey,
|
|
891
|
-
this.privateKey,
|
|
892
|
-
this.aggPubkey,
|
|
893
|
-
this.msg
|
|
894
|
-
);
|
|
1010
|
+
const nonce = (0, import_musig2.nonceGen)(this.myPublicKey, this.privateKey, this.aggPubkey, this.msg);
|
|
895
1011
|
return new MusigWithNonce(
|
|
896
1012
|
this.privateKey,
|
|
897
1013
|
this.myPublicKey,
|
|
898
1014
|
this.publicKeys,
|
|
899
1015
|
this.myIndex,
|
|
900
|
-
this.aggPubkey,
|
|
901
1016
|
this.tweak,
|
|
902
1017
|
this.msg,
|
|
903
1018
|
nonce
|
|
@@ -905,12 +1020,11 @@ var MusigWithMessage = class {
|
|
|
905
1020
|
}
|
|
906
1021
|
};
|
|
907
1022
|
var MusigWithNonce = class {
|
|
908
|
-
constructor(privateKey, myPublicKey, publicKeys, myIndex,
|
|
1023
|
+
constructor(privateKey, myPublicKey, publicKeys, myIndex, tweak, msg, nonce) {
|
|
909
1024
|
this.privateKey = privateKey;
|
|
910
1025
|
this.myPublicKey = myPublicKey;
|
|
911
1026
|
this.publicKeys = publicKeys;
|
|
912
1027
|
this.myIndex = myIndex;
|
|
913
|
-
this.aggPubkey = aggPubkey;
|
|
914
1028
|
this.tweak = tweak;
|
|
915
1029
|
this.msg = msg;
|
|
916
1030
|
this.nonce = nonce;
|
|
@@ -942,10 +1056,8 @@ var MusigWithNonce = class {
|
|
|
942
1056
|
const aggregatedNonce = (0, import_musig2.nonceAggregate)([...ordered]);
|
|
943
1057
|
return new MusigNoncesAggregated(
|
|
944
1058
|
this.privateKey,
|
|
945
|
-
this.myPublicKey,
|
|
946
1059
|
this.publicKeys,
|
|
947
1060
|
this.myIndex,
|
|
948
|
-
this.aggPubkey,
|
|
949
1061
|
this.tweak,
|
|
950
1062
|
this.msg,
|
|
951
1063
|
this.nonce,
|
|
@@ -955,12 +1067,10 @@ var MusigWithNonce = class {
|
|
|
955
1067
|
}
|
|
956
1068
|
};
|
|
957
1069
|
var MusigNoncesAggregated = class {
|
|
958
|
-
constructor(privateKey,
|
|
1070
|
+
constructor(privateKey, publicKeys, myIndex, tweak, msg, nonce, pubNonces, aggregatedNonce) {
|
|
959
1071
|
this.privateKey = privateKey;
|
|
960
|
-
this.myPublicKey = myPublicKey;
|
|
961
1072
|
this.publicKeys = publicKeys;
|
|
962
1073
|
this.myIndex = myIndex;
|
|
963
|
-
this.aggPubkey = aggPubkey;
|
|
964
1074
|
this.tweak = tweak;
|
|
965
1075
|
this.msg = msg;
|
|
966
1076
|
this.nonce = nonce;
|
|
@@ -1006,11 +1116,7 @@ var MusigSession = class {
|
|
|
1006
1116
|
const index = typeof publicKeyOrIndex === "number" ? publicKeyOrIndex : findKeyIndex(this.publicKeys, publicKeyOrIndex);
|
|
1007
1117
|
if (index < 0 || index >= this.publicKeys.length)
|
|
1008
1118
|
throw new Error("public key not found or index out of range");
|
|
1009
|
-
if (!this.session.partialSigVerify(
|
|
1010
|
-
signature,
|
|
1011
|
-
[...this.pubNonces],
|
|
1012
|
-
index
|
|
1013
|
-
)) {
|
|
1119
|
+
if (!this.session.partialSigVerify(signature, [...this.pubNonces], index)) {
|
|
1014
1120
|
throw new Error("invalid partial signature");
|
|
1015
1121
|
}
|
|
1016
1122
|
this.partialSignatures[index] = signature;
|
|
@@ -1019,12 +1125,7 @@ var MusigSession = class {
|
|
|
1019
1125
|
signPartial() {
|
|
1020
1126
|
const sig = this.session.sign(this.nonce.secret, this.privateKey, true);
|
|
1021
1127
|
this.partialSignatures[this.myIndex] = sig;
|
|
1022
|
-
return new MusigSigned(
|
|
1023
|
-
this.session,
|
|
1024
|
-
[...this.partialSignatures],
|
|
1025
|
-
sig,
|
|
1026
|
-
this.nonce.public
|
|
1027
|
-
);
|
|
1128
|
+
return new MusigSigned(this.session, [...this.partialSignatures], sig, this.nonce.public);
|
|
1028
1129
|
}
|
|
1029
1130
|
};
|
|
1030
1131
|
var MusigSigned = class {
|
|
@@ -1038,14 +1139,11 @@ var MusigSigned = class {
|
|
|
1038
1139
|
if (this.partialSignatures.some((s) => s === null)) {
|
|
1039
1140
|
throw new Error("not all partial signatures are set");
|
|
1040
1141
|
}
|
|
1041
|
-
return this.session.partialSigAgg(
|
|
1042
|
-
this.partialSignatures
|
|
1043
|
-
);
|
|
1142
|
+
return this.session.partialSigAgg(this.partialSignatures);
|
|
1044
1143
|
}
|
|
1045
1144
|
};
|
|
1046
1145
|
var create = (privateKey, publicKeys) => {
|
|
1047
|
-
if (publicKeys.length < 2)
|
|
1048
|
-
throw new Error("need at least 2 keys to aggregate");
|
|
1146
|
+
if (publicKeys.length < 2) throw new Error("need at least 2 keys to aggregate");
|
|
1049
1147
|
const keys = [...publicKeys];
|
|
1050
1148
|
assertPublicKeys(keys);
|
|
1051
1149
|
Object.freeze(keys);
|
|
@@ -1053,14 +1151,7 @@ var create = (privateKey, publicKeys) => {
|
|
|
1053
1151
|
const myIndex = findKeyIndex(keys, myPublicKey);
|
|
1054
1152
|
if (myIndex === -1) throw new Error("our key is not in publicKeys");
|
|
1055
1153
|
const aggPubkey = aggregateKeys(keys);
|
|
1056
|
-
return new MusigKeyAgg(
|
|
1057
|
-
privateKey,
|
|
1058
|
-
myPublicKey,
|
|
1059
|
-
keys,
|
|
1060
|
-
myIndex,
|
|
1061
|
-
aggPubkey,
|
|
1062
|
-
aggPubkey
|
|
1063
|
-
);
|
|
1154
|
+
return new MusigKeyAgg(privateKey, myPublicKey, keys, myIndex, aggPubkey, aggPubkey);
|
|
1064
1155
|
};
|
|
1065
1156
|
|
|
1066
1157
|
// src/utils/boltz-swap-tx.ts
|
|
@@ -1136,9 +1227,7 @@ var taprootHashTree = (tree) => {
|
|
|
1136
1227
|
};
|
|
1137
1228
|
var tweakMusig = (musig, tree) => {
|
|
1138
1229
|
const tweak = taprootHashTree(tree).hash;
|
|
1139
|
-
return musig.xonlyTweakAdd(
|
|
1140
|
-
import_secp256k12.schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak)
|
|
1141
|
-
);
|
|
1230
|
+
return musig.xonlyTweakAdd(import_secp256k12.schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak));
|
|
1142
1231
|
};
|
|
1143
1232
|
var toXOnly = (pubKey) => {
|
|
1144
1233
|
if (pubKey.length === 32) return pubKey;
|
|
@@ -1150,9 +1239,7 @@ var toXOnly = (pubKey) => {
|
|
|
1150
1239
|
}
|
|
1151
1240
|
return pubKey.subarray(1, 33);
|
|
1152
1241
|
}
|
|
1153
|
-
throw new Error(
|
|
1154
|
-
`Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`
|
|
1155
|
-
);
|
|
1242
|
+
throw new Error(`Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`);
|
|
1156
1243
|
};
|
|
1157
1244
|
var p2trScript = (publicKey) => import_btc_signer.Script.encode(["OP_1", toXOnly(publicKey)]);
|
|
1158
1245
|
var detectSwapOutput = (tweakedKey, transaction) => {
|
|
@@ -1167,8 +1254,7 @@ var detectSwapOutput = (tweakedKey, transaction) => {
|
|
|
1167
1254
|
};
|
|
1168
1255
|
var DUMMY_TAPROOT_SIGNATURE = new Uint8Array(64);
|
|
1169
1256
|
var constructClaimTransaction = (utxo, destinationScript, fee) => {
|
|
1170
|
-
if (fee < BigInt(0) || fee >= utxo.amount)
|
|
1171
|
-
throw new Error("fee exceeds utxo amount");
|
|
1257
|
+
if (fee < BigInt(0) || fee >= utxo.amount) throw new Error("fee exceeds utxo amount");
|
|
1172
1258
|
const tx = new import_btc_signer.Transaction({ version: 2 });
|
|
1173
1259
|
tx.addOutput({
|
|
1174
1260
|
amount: utxo.amount - fee,
|
|
@@ -1187,9 +1273,7 @@ var constructClaimTransaction = (utxo, destinationScript, fee) => {
|
|
|
1187
1273
|
};
|
|
1188
1274
|
var targetFee = (satPerVbyte, constructTx) => {
|
|
1189
1275
|
const tx = constructTx(BigInt(1));
|
|
1190
|
-
return constructTx(
|
|
1191
|
-
BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte))
|
|
1192
|
-
);
|
|
1276
|
+
return constructTx(BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte)));
|
|
1193
1277
|
};
|
|
1194
1278
|
|
|
1195
1279
|
// src/utils/decoding.ts
|
|
@@ -1197,9 +1281,7 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
|
|
|
1197
1281
|
var import_sdk2 = require("@arkade-os/sdk");
|
|
1198
1282
|
var decodeInvoice = (invoice) => {
|
|
1199
1283
|
const decoded = import_light_bolt11_decoder.default.decode(invoice);
|
|
1200
|
-
const millisats = Number(
|
|
1201
|
-
decoded.sections.find((s) => s.name === "amount")?.value ?? "0"
|
|
1202
|
-
);
|
|
1284
|
+
const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
|
|
1203
1285
|
return {
|
|
1204
1286
|
expiry: decoded.expiry ?? 3600,
|
|
1205
1287
|
amountSats: Math.floor(millisats / 1e3),
|
|
@@ -1262,10 +1344,7 @@ function extractTimeLockFromLeafOutput(scriptHex) {
|
|
|
1262
1344
|
const data = opcodes[hasCSV - 1];
|
|
1263
1345
|
if (data instanceof Uint8Array) {
|
|
1264
1346
|
const dataBytes = new Uint8Array(data).reverse();
|
|
1265
|
-
const {
|
|
1266
|
-
blocks,
|
|
1267
|
-
seconds
|
|
1268
|
-
} = import_bip68.default.decode(
|
|
1347
|
+
const { blocks, seconds } = import_bip68.default.decode(
|
|
1269
1348
|
parseInt(import_base5.hex.encode(dataBytes), 16)
|
|
1270
1349
|
);
|
|
1271
1350
|
return blocks ?? seconds ?? 0;
|
|
@@ -1298,6 +1377,13 @@ var SwapManager = class _SwapManager {
|
|
|
1298
1377
|
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1299
1378
|
*/
|
|
1300
1379
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1380
|
+
/**
|
|
1381
|
+
* Delay between re-attempts of a chain refund that left VTXOs deferred
|
|
1382
|
+
* (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
|
|
1383
|
+
* has elapsed). Boltz won't send another status update once the swap
|
|
1384
|
+
* is `swap.expired`, so the manager owns the local retry cadence.
|
|
1385
|
+
*/
|
|
1386
|
+
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1301
1387
|
swapProvider;
|
|
1302
1388
|
config;
|
|
1303
1389
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1316,6 +1402,11 @@ var SwapManager = class _SwapManager {
|
|
|
1316
1402
|
reconnectTimer = null;
|
|
1317
1403
|
initialPollTimer = null;
|
|
1318
1404
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1405
|
+
// Per-swap retry timers for chain refunds that left work undone
|
|
1406
|
+
// (refundArk returned `skipped > 0`). The swap is held in
|
|
1407
|
+
// `monitoredSwaps` past its terminal Boltz status until the local
|
|
1408
|
+
// refund completes or the manager stops.
|
|
1409
|
+
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1319
1410
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1320
1411
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1321
1412
|
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
@@ -1369,9 +1460,7 @@ var SwapManager = class _SwapManager {
|
|
|
1369
1460
|
this.wsConnectedListeners.add(config.events.onWebSocketConnected);
|
|
1370
1461
|
}
|
|
1371
1462
|
if (config.events?.onWebSocketDisconnected) {
|
|
1372
|
-
this.wsDisconnectedListeners.add(
|
|
1373
|
-
config.events.onWebSocketDisconnected
|
|
1374
|
-
);
|
|
1463
|
+
this.wsDisconnectedListeners.add(config.events.onWebSocketDisconnected);
|
|
1375
1464
|
}
|
|
1376
1465
|
this.currentReconnectDelay = this.config.reconnectDelayMs;
|
|
1377
1466
|
this.currentPollRetryDelay = this.config.pollRetryDelayMs;
|
|
@@ -1514,6 +1603,10 @@ var SwapManager = class _SwapManager {
|
|
|
1514
1603
|
clearTimeout(timer);
|
|
1515
1604
|
}
|
|
1516
1605
|
this.pollRetryTimers.clear();
|
|
1606
|
+
for (const timer of this.refundRetryTimers.values()) {
|
|
1607
|
+
clearTimeout(timer);
|
|
1608
|
+
}
|
|
1609
|
+
this.refundRetryTimers.clear();
|
|
1517
1610
|
this.notFoundCounts.clear();
|
|
1518
1611
|
}
|
|
1519
1612
|
/**
|
|
@@ -1522,9 +1615,7 @@ var SwapManager = class _SwapManager {
|
|
|
1522
1615
|
*/
|
|
1523
1616
|
setPollInterval(ms) {
|
|
1524
1617
|
if (ms <= 0) {
|
|
1525
|
-
throw new RangeError(
|
|
1526
|
-
`setPollInterval: ms must be a positive number, got ${ms}`
|
|
1527
|
-
);
|
|
1618
|
+
throw new RangeError(`setPollInterval: ms must be a positive number, got ${ms}`);
|
|
1528
1619
|
}
|
|
1529
1620
|
const cappedInterval = Math.min(ms, this.config.maxPollIntervalMs);
|
|
1530
1621
|
if (cappedInterval !== ms) {
|
|
@@ -1533,10 +1624,7 @@ var SwapManager = class _SwapManager {
|
|
|
1533
1624
|
);
|
|
1534
1625
|
}
|
|
1535
1626
|
this.config.pollInterval = cappedInterval;
|
|
1536
|
-
this.currentPollRetryDelay = Math.min(
|
|
1537
|
-
cappedInterval,
|
|
1538
|
-
this.config.pollRetryDelayMs
|
|
1539
|
-
);
|
|
1627
|
+
this.currentPollRetryDelay = Math.min(cappedInterval, this.config.pollRetryDelayMs);
|
|
1540
1628
|
if (this.isRunning) {
|
|
1541
1629
|
if (this.usePollingFallback) {
|
|
1542
1630
|
this.startPollingFallback();
|
|
@@ -1575,6 +1663,11 @@ var SwapManager = class _SwapManager {
|
|
|
1575
1663
|
clearTimeout(retryTimer);
|
|
1576
1664
|
this.pollRetryTimers.delete(swapId);
|
|
1577
1665
|
}
|
|
1666
|
+
const refundRetryTimer = this.refundRetryTimers.get(swapId);
|
|
1667
|
+
if (refundRetryTimer) {
|
|
1668
|
+
clearTimeout(refundRetryTimer);
|
|
1669
|
+
this.refundRetryTimers.delete(swapId);
|
|
1670
|
+
}
|
|
1578
1671
|
this.notFoundCounts.delete(swapId);
|
|
1579
1672
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1580
1673
|
}
|
|
@@ -1621,9 +1714,7 @@ var SwapManager = class _SwapManager {
|
|
|
1621
1714
|
}
|
|
1622
1715
|
if (this.isFinalStatus(swap)) {
|
|
1623
1716
|
if (isPendingReverseSwap(swap)) {
|
|
1624
|
-
const response = await this.swapProvider.getReverseSwapTxId(
|
|
1625
|
-
swap.id
|
|
1626
|
-
);
|
|
1717
|
+
const response = await this.swapProvider.getReverseSwapTxId(swap.id);
|
|
1627
1718
|
return { txid: response.id };
|
|
1628
1719
|
}
|
|
1629
1720
|
if (isPendingSubmarineSwap(swap)) {
|
|
@@ -1640,31 +1731,19 @@ var SwapManager = class _SwapManager {
|
|
|
1640
1731
|
if (updatedSwap.status === "invoice.settled") {
|
|
1641
1732
|
this.swapProvider.getReverseSwapTxId(updatedSwap.id).then((response) => resolve({ txid: response.id })).catch((error) => reject(error));
|
|
1642
1733
|
} else {
|
|
1643
|
-
reject(
|
|
1644
|
-
new Error(
|
|
1645
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1646
|
-
)
|
|
1647
|
-
);
|
|
1734
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1648
1735
|
}
|
|
1649
1736
|
} else if (isPendingSubmarineSwap(updatedSwap)) {
|
|
1650
1737
|
if (updatedSwap.status === "transaction.claimed") {
|
|
1651
1738
|
resolve({ txid: updatedSwap.id });
|
|
1652
1739
|
} else {
|
|
1653
|
-
reject(
|
|
1654
|
-
new Error(
|
|
1655
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1656
|
-
)
|
|
1657
|
-
);
|
|
1740
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1658
1741
|
}
|
|
1659
1742
|
} else if (isPendingChainSwap(updatedSwap)) {
|
|
1660
1743
|
if (updatedSwap.status === "transaction.claimed") {
|
|
1661
1744
|
resolve({ txid: updatedSwap.id });
|
|
1662
1745
|
} else {
|
|
1663
|
-
reject(
|
|
1664
|
-
new Error(
|
|
1665
|
-
`Swap failed with status: ${updatedSwap.status}`
|
|
1666
|
-
)
|
|
1667
|
-
);
|
|
1746
|
+
reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
|
|
1668
1747
|
}
|
|
1669
1748
|
}
|
|
1670
1749
|
};
|
|
@@ -1707,16 +1786,12 @@ var SwapManager = class _SwapManager {
|
|
|
1707
1786
|
const connectionTimeout = setTimeout(() => {
|
|
1708
1787
|
logger.error("WebSocket connection timeout");
|
|
1709
1788
|
this.websocket?.close();
|
|
1710
|
-
this.enterPollingFallback(
|
|
1711
|
-
new NetworkError("WebSocket connection failed")
|
|
1712
|
-
);
|
|
1789
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1713
1790
|
}, 1e4);
|
|
1714
1791
|
this.websocket.onerror = (error) => {
|
|
1715
1792
|
clearTimeout(connectionTimeout);
|
|
1716
1793
|
logger.error("WebSocket error:", error);
|
|
1717
|
-
this.enterPollingFallback(
|
|
1718
|
-
new NetworkError("WebSocket connection failed")
|
|
1719
|
-
);
|
|
1794
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1720
1795
|
};
|
|
1721
1796
|
this.websocket.onopen = () => {
|
|
1722
1797
|
clearTimeout(connectionTimeout);
|
|
@@ -1756,9 +1831,7 @@ var SwapManager = class _SwapManager {
|
|
|
1756
1831
|
};
|
|
1757
1832
|
} catch (error) {
|
|
1758
1833
|
logger.error("Failed to create WebSocket:", error);
|
|
1759
|
-
this.enterPollingFallback(
|
|
1760
|
-
new NetworkError("WebSocket connection failed")
|
|
1761
|
-
);
|
|
1834
|
+
this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
|
|
1762
1835
|
}
|
|
1763
1836
|
}
|
|
1764
1837
|
/**
|
|
@@ -1793,11 +1866,8 @@ var SwapManager = class _SwapManager {
|
|
|
1793
1866
|
* Schedule WebSocket reconnection with exponential backoff
|
|
1794
1867
|
*/
|
|
1795
1868
|
scheduleReconnect() {
|
|
1796
|
-
if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport())
|
|
1797
|
-
|
|
1798
|
-
logger.log(
|
|
1799
|
-
`Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`
|
|
1800
|
-
);
|
|
1869
|
+
if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport()) return;
|
|
1870
|
+
logger.log(`Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`);
|
|
1801
1871
|
this.reconnectTimer = setTimeout(() => {
|
|
1802
1872
|
this.reconnectTimer = null;
|
|
1803
1873
|
this.isReconnecting = false;
|
|
@@ -1812,8 +1882,7 @@ var SwapManager = class _SwapManager {
|
|
|
1812
1882
|
* Subscribe to a specific swap ID on the WebSocket
|
|
1813
1883
|
*/
|
|
1814
1884
|
subscribeToSwap(swapId) {
|
|
1815
|
-
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN)
|
|
1816
|
-
return;
|
|
1885
|
+
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) return;
|
|
1817
1886
|
this.websocket.send(
|
|
1818
1887
|
JSON.stringify({
|
|
1819
1888
|
op: "subscribe",
|
|
@@ -1836,9 +1905,7 @@ var SwapManager = class _SwapManager {
|
|
|
1836
1905
|
if (msg.args[0].error) {
|
|
1837
1906
|
logger.error(`Swap ${swapId} error:`, msg.args[0].error);
|
|
1838
1907
|
const error = new Error(msg.args[0].error);
|
|
1839
|
-
this.swapFailedListeners.forEach(
|
|
1840
|
-
(listener) => listener(swap, error)
|
|
1841
|
-
);
|
|
1908
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
1842
1909
|
return;
|
|
1843
1910
|
}
|
|
1844
1911
|
const newStatus = msg.args[0].status;
|
|
@@ -1856,19 +1923,14 @@ var SwapManager = class _SwapManager {
|
|
|
1856
1923
|
const oldStatus = swap.status;
|
|
1857
1924
|
if (oldStatus === newStatus) return;
|
|
1858
1925
|
swap.status = newStatus;
|
|
1859
|
-
this.swapUpdateListeners.forEach(
|
|
1860
|
-
(listener) => listener(swap, oldStatus)
|
|
1861
|
-
);
|
|
1926
|
+
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
1862
1927
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
1863
1928
|
if (subscribers) {
|
|
1864
1929
|
subscribers.forEach((callback) => {
|
|
1865
1930
|
try {
|
|
1866
1931
|
callback(swap, oldStatus);
|
|
1867
1932
|
} catch (error) {
|
|
1868
|
-
logger.error(
|
|
1869
|
-
`Error in swap subscription callback for ${swap.id}:`,
|
|
1870
|
-
error
|
|
1871
|
-
);
|
|
1933
|
+
logger.error(`Error in swap subscription callback for ${swap.id}:`, error);
|
|
1872
1934
|
}
|
|
1873
1935
|
});
|
|
1874
1936
|
}
|
|
@@ -1877,15 +1939,57 @@ var SwapManager = class _SwapManager {
|
|
|
1877
1939
|
await this.executeAutonomousAction(swap);
|
|
1878
1940
|
}
|
|
1879
1941
|
if (this.isFinalStatus(swap)) {
|
|
1880
|
-
this.
|
|
1881
|
-
|
|
1882
|
-
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1883
|
-
if (retryTimer) {
|
|
1884
|
-
clearTimeout(retryTimer);
|
|
1885
|
-
this.pollRetryTimers.delete(swap.id);
|
|
1942
|
+
if (this.refundRetryTimers.has(swap.id)) {
|
|
1943
|
+
return;
|
|
1886
1944
|
}
|
|
1887
|
-
this.
|
|
1945
|
+
this.finalizeMonitoredSwap(swap);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Drop a swap from monitoring and emit the terminal completion event.
|
|
1950
|
+
* Shared between the on-status-update finalization path and the
|
|
1951
|
+
* refund-retry finalization path (used when a previously-deferred
|
|
1952
|
+
* chain refund has finished its remaining work).
|
|
1953
|
+
*/
|
|
1954
|
+
finalizeMonitoredSwap(swap) {
|
|
1955
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1956
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1957
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1958
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1959
|
+
if (retryTimer) {
|
|
1960
|
+
clearTimeout(retryTimer);
|
|
1961
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1962
|
+
}
|
|
1963
|
+
const refundRetry = this.refundRetryTimers.get(swap.id);
|
|
1964
|
+
if (refundRetry) {
|
|
1965
|
+
clearTimeout(refundRetry);
|
|
1966
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1888
1967
|
}
|
|
1968
|
+
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Schedule another `executeAutonomousAction` run for a chain swap whose
|
|
1972
|
+
* refund left VTXOs deferred. After the retry completes, if no further
|
|
1973
|
+
* deferral was reported, finalize monitoring cleanup.
|
|
1974
|
+
*/
|
|
1975
|
+
scheduleRefundRetry(swap, delayMs) {
|
|
1976
|
+
const existing = this.refundRetryTimers.get(swap.id);
|
|
1977
|
+
if (existing) clearTimeout(existing);
|
|
1978
|
+
this.refundRetryTimers.set(
|
|
1979
|
+
swap.id,
|
|
1980
|
+
setTimeout(async () => {
|
|
1981
|
+
this.refundRetryTimers.delete(swap.id);
|
|
1982
|
+
if (!this.isRunning) return;
|
|
1983
|
+
if (!this.monitoredSwaps.has(swap.id)) return;
|
|
1984
|
+
try {
|
|
1985
|
+
await this.executeAutonomousAction(swap);
|
|
1986
|
+
} finally {
|
|
1987
|
+
if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
|
|
1988
|
+
this.finalizeMonitoredSwap(swap);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}, delayMs)
|
|
1992
|
+
);
|
|
1889
1993
|
}
|
|
1890
1994
|
/**
|
|
1891
1995
|
* Execute autonomous action based on swap status
|
|
@@ -1893,9 +1997,7 @@ var SwapManager = class _SwapManager {
|
|
|
1893
1997
|
*/
|
|
1894
1998
|
async executeAutonomousAction(swap) {
|
|
1895
1999
|
if (this.swapsInProgress.has(swap.id)) {
|
|
1896
|
-
logger.log(
|
|
1897
|
-
`Swap ${swap.id} is already being processed, skipping autonomous action`
|
|
1898
|
-
);
|
|
2000
|
+
logger.log(`Swap ${swap.id} is already being processed, skipping autonomous action`);
|
|
1899
2001
|
return;
|
|
1900
2002
|
}
|
|
1901
2003
|
try {
|
|
@@ -1910,9 +2012,7 @@ var SwapManager = class _SwapManager {
|
|
|
1910
2012
|
if (isReverseClaimableStatus(swap.status)) {
|
|
1911
2013
|
logger.log(`Auto-claiming reverse swap ${swap.id}`);
|
|
1912
2014
|
await this.executeClaimAction(swap);
|
|
1913
|
-
this.actionExecutedListeners.forEach(
|
|
1914
|
-
(listener) => listener(swap, "claim")
|
|
1915
|
-
);
|
|
2015
|
+
this.actionExecutedListeners.forEach((listener) => listener(swap, "claim"));
|
|
1916
2016
|
}
|
|
1917
2017
|
} else if (isPendingSubmarineSwap(swap)) {
|
|
1918
2018
|
if (!swap.request?.invoice || swap.request.invoice.length === 0) {
|
|
@@ -1924,9 +2024,7 @@ var SwapManager = class _SwapManager {
|
|
|
1924
2024
|
if (isSubmarineRefundableStatus(swap.status)) {
|
|
1925
2025
|
logger.log(`Auto-refunding submarine swap ${swap.id}`);
|
|
1926
2026
|
await this.executeRefundAction(swap);
|
|
1927
|
-
this.actionExecutedListeners.forEach(
|
|
1928
|
-
(listener) => listener(swap, "refund")
|
|
1929
|
-
);
|
|
2027
|
+
this.actionExecutedListeners.forEach((listener) => listener(swap, "refund"));
|
|
1930
2028
|
}
|
|
1931
2029
|
} else if (isPendingChainSwap(swap)) {
|
|
1932
2030
|
if (isChainClaimableStatus(swap.status)) {
|
|
@@ -1946,10 +2044,27 @@ var SwapManager = class _SwapManager {
|
|
|
1946
2044
|
} else if (isChainRefundableStatus(swap.status)) {
|
|
1947
2045
|
if (swap.request.from === "ARK") {
|
|
1948
2046
|
logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
(
|
|
1952
|
-
|
|
2047
|
+
try {
|
|
2048
|
+
const outcome = await this.executeRefundArkAction(swap);
|
|
2049
|
+
if (outcome && outcome.skipped > 0) {
|
|
2050
|
+
logger.log(
|
|
2051
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
2052
|
+
);
|
|
2053
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2054
|
+
}
|
|
2055
|
+
this.actionExecutedListeners.forEach(
|
|
2056
|
+
(listener) => listener(swap, "refundArk")
|
|
2057
|
+
);
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
logger.error(
|
|
2060
|
+
`Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
|
|
2061
|
+
error
|
|
2062
|
+
);
|
|
2063
|
+
this.swapFailedListeners.forEach(
|
|
2064
|
+
(listener) => listener(swap, error)
|
|
2065
|
+
);
|
|
2066
|
+
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
2067
|
+
}
|
|
1953
2068
|
}
|
|
1954
2069
|
if (swap.request.from === "BTC") {
|
|
1955
2070
|
logger.warn(
|
|
@@ -1976,13 +2091,8 @@ var SwapManager = class _SwapManager {
|
|
|
1976
2091
|
}
|
|
1977
2092
|
}
|
|
1978
2093
|
} catch (error) {
|
|
1979
|
-
logger.error(
|
|
1980
|
-
|
|
1981
|
-
error
|
|
1982
|
-
);
|
|
1983
|
-
this.swapFailedListeners.forEach(
|
|
1984
|
-
(listener) => listener(swap, error)
|
|
1985
|
-
);
|
|
2094
|
+
logger.error(`Failed to execute autonomous action for swap ${swap.id}:`, error);
|
|
2095
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
1986
2096
|
} finally {
|
|
1987
2097
|
this.swapsInProgress.delete(swap.id);
|
|
1988
2098
|
}
|
|
@@ -2035,7 +2145,7 @@ var SwapManager = class _SwapManager {
|
|
|
2035
2145
|
logger.error("refundArk callback not set");
|
|
2036
2146
|
return;
|
|
2037
2147
|
}
|
|
2038
|
-
|
|
2148
|
+
return this.refundArkCallback(swap);
|
|
2039
2149
|
}
|
|
2040
2150
|
/**
|
|
2041
2151
|
* Execute sign server claim action for chain swap.
|
|
@@ -2083,9 +2193,7 @@ var SwapManager = class _SwapManager {
|
|
|
2083
2193
|
logger.log(`Resuming chain refund for swap ${swap.id}`);
|
|
2084
2194
|
await this.executeAutonomousAction(swap);
|
|
2085
2195
|
} else if (isPendingChainSwap(swap) && swap.request.to === "ARK" && isChainSignableStatus(swap.status)) {
|
|
2086
|
-
logger.log(
|
|
2087
|
-
`Resuming server claim signing for swap ${swap.id}`
|
|
2088
|
-
);
|
|
2196
|
+
logger.log(`Resuming server claim signing for swap ${swap.id}`);
|
|
2089
2197
|
await this.executeAutonomousAction(swap);
|
|
2090
2198
|
}
|
|
2091
2199
|
} catch (error) {
|
|
@@ -2137,16 +2245,12 @@ var SwapManager = class _SwapManager {
|
|
|
2137
2245
|
*/
|
|
2138
2246
|
async pollAllSwaps() {
|
|
2139
2247
|
if (this.monitoredSwaps.size === 0) return;
|
|
2140
|
-
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2141
|
-
(swap) => this.pollSingleSwap(swap)
|
|
2142
|
-
);
|
|
2248
|
+
const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
|
|
2143
2249
|
await Promise.allSettled(pollPromises);
|
|
2144
2250
|
}
|
|
2145
2251
|
async pollSingleSwap(swap) {
|
|
2146
2252
|
try {
|
|
2147
|
-
const statusResponse = await this.swapProvider.getSwapStatus(
|
|
2148
|
-
swap.id
|
|
2149
|
-
);
|
|
2253
|
+
const statusResponse = await this.swapProvider.getSwapStatus(swap.id);
|
|
2150
2254
|
this.notFoundCounts.delete(swap.id);
|
|
2151
2255
|
if (statusResponse.status !== swap.status) {
|
|
2152
2256
|
await this.handleSwapStatusUpdate(swap, statusResponse.status);
|
|
@@ -2157,9 +2261,7 @@ var SwapManager = class _SwapManager {
|
|
|
2157
2261
|
return;
|
|
2158
2262
|
}
|
|
2159
2263
|
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
2160
|
-
logger.warn(
|
|
2161
|
-
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
2162
|
-
);
|
|
2264
|
+
logger.warn(`Rate-limited polling swap ${swap.id}, retrying in 2s`);
|
|
2163
2265
|
const existing = this.pollRetryTimers.get(swap.id);
|
|
2164
2266
|
if (existing) clearTimeout(existing);
|
|
2165
2267
|
this.pollRetryTimers.set(
|
|
@@ -2167,25 +2269,17 @@ var SwapManager = class _SwapManager {
|
|
|
2167
2269
|
setTimeout(async () => {
|
|
2168
2270
|
this.pollRetryTimers.delete(swap.id);
|
|
2169
2271
|
try {
|
|
2170
|
-
const retry = await this.swapProvider.getSwapStatus(
|
|
2171
|
-
swap.id
|
|
2172
|
-
);
|
|
2272
|
+
const retry = await this.swapProvider.getSwapStatus(swap.id);
|
|
2173
2273
|
this.notFoundCounts.delete(swap.id);
|
|
2174
2274
|
if (retry.status !== swap.status) {
|
|
2175
|
-
await this.handleSwapStatusUpdate(
|
|
2176
|
-
swap,
|
|
2177
|
-
retry.status
|
|
2178
|
-
);
|
|
2275
|
+
await this.handleSwapStatusUpdate(swap, retry.status);
|
|
2179
2276
|
}
|
|
2180
2277
|
} catch (retryError) {
|
|
2181
2278
|
if (retryError instanceof SwapNotFoundError) {
|
|
2182
2279
|
await this.handleSwapNotFound(swap);
|
|
2183
2280
|
return;
|
|
2184
2281
|
}
|
|
2185
|
-
logger.error(
|
|
2186
|
-
`Retry poll for swap ${swap.id} also failed:`,
|
|
2187
|
-
retryError
|
|
2188
|
-
);
|
|
2282
|
+
logger.error(`Retry poll for swap ${swap.id} also failed:`, retryError);
|
|
2189
2283
|
}
|
|
2190
2284
|
}, 2e3)
|
|
2191
2285
|
);
|
|
@@ -2204,6 +2298,7 @@ var SwapManager = class _SwapManager {
|
|
|
2204
2298
|
* Boltz endpoint).
|
|
2205
2299
|
*/
|
|
2206
2300
|
async handleSwapNotFound(swap) {
|
|
2301
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2207
2302
|
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2208
2303
|
this.notFoundCounts.set(swap.id, count);
|
|
2209
2304
|
logger.warn(
|
|
@@ -2224,6 +2319,7 @@ var SwapManager = class _SwapManager {
|
|
|
2224
2319
|
* 404s without recovering anything.
|
|
2225
2320
|
*/
|
|
2226
2321
|
async markSwapAsUnknownToProvider(swap) {
|
|
2322
|
+
if (this.refundRetryTimers.has(swap.id)) return;
|
|
2227
2323
|
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2228
2324
|
this.notFoundCounts.delete(swap.id);
|
|
2229
2325
|
return;
|
|
@@ -2236,10 +2332,13 @@ var SwapManager = class _SwapManager {
|
|
|
2236
2332
|
clearTimeout(retryTimer);
|
|
2237
2333
|
this.pollRetryTimers.delete(swap.id);
|
|
2238
2334
|
}
|
|
2335
|
+
const refundRetryTimer = this.refundRetryTimers.get(swap.id);
|
|
2336
|
+
if (refundRetryTimer) {
|
|
2337
|
+
clearTimeout(refundRetryTimer);
|
|
2338
|
+
this.refundRetryTimers.delete(swap.id);
|
|
2339
|
+
}
|
|
2239
2340
|
this.notFoundCounts.delete(swap.id);
|
|
2240
|
-
this.swapUpdateListeners.forEach(
|
|
2241
|
-
(listener) => listener(swap, oldStatus)
|
|
2242
|
-
);
|
|
2341
|
+
this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
|
|
2243
2342
|
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
2244
2343
|
if (subscribers) {
|
|
2245
2344
|
subscribers.forEach((callback) => {
|
|
@@ -2304,9 +2403,7 @@ async function saveSwap(swap, saver) {
|
|
|
2304
2403
|
if (saver.saveSubmarineSwap) {
|
|
2305
2404
|
await saver.saveSubmarineSwap(swap);
|
|
2306
2405
|
} else {
|
|
2307
|
-
console.warn(
|
|
2308
|
-
"No saveSubmarineSwap handler provided, swap not saved"
|
|
2309
|
-
);
|
|
2406
|
+
console.warn("No saveSubmarineSwap handler provided, swap not saved");
|
|
2310
2407
|
}
|
|
2311
2408
|
} else if (isPendingChainSwap(swap)) {
|
|
2312
2409
|
if (saver.saveChainSwap) {
|
|
@@ -2363,6 +2460,26 @@ function enrichSubmarineSwapInvoice(swap, invoice) {
|
|
|
2363
2460
|
return swap;
|
|
2364
2461
|
}
|
|
2365
2462
|
|
|
2463
|
+
// src/repositories/swap-repository.ts
|
|
2464
|
+
function hasImpossibleSwapsFilter(filter) {
|
|
2465
|
+
if (!filter) return false;
|
|
2466
|
+
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;
|
|
2467
|
+
}
|
|
2468
|
+
function matchesCriterion(value, criterion) {
|
|
2469
|
+
if (criterion === void 0) return true;
|
|
2470
|
+
return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
|
|
2471
|
+
}
|
|
2472
|
+
function applySwapsFilter(swaps, filter) {
|
|
2473
|
+
return swaps.filter(
|
|
2474
|
+
(swap) => !!swap && matchesCriterion(swap.id, filter.id) && matchesCriterion(swap.status, filter.status) && matchesCriterion(swap.type, filter.type)
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
function applyCreatedAtOrder(swaps, filter) {
|
|
2478
|
+
if (filter?.orderBy !== "createdAt") return swaps;
|
|
2479
|
+
const direction = filter.orderDirection === "asc" ? 1 : -1;
|
|
2480
|
+
return swaps.slice().sort((a, b) => (a.createdAt - b.createdAt) * direction);
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2366
2483
|
// src/repositories/IndexedDb/swap-repository.ts
|
|
2367
2484
|
var import_sdk4 = require("@arkade-os/sdk");
|
|
2368
2485
|
var DEFAULT_DB_NAME = "arkade-boltz-swap";
|
|
@@ -2378,6 +2495,10 @@ function initDatabase(db) {
|
|
|
2378
2495
|
swapStore.createIndex("createdAt", "createdAt", { unique: false });
|
|
2379
2496
|
}
|
|
2380
2497
|
}
|
|
2498
|
+
function asArray(v) {
|
|
2499
|
+
if (v === void 0) return void 0;
|
|
2500
|
+
return Array.isArray(v) ? v : [v];
|
|
2501
|
+
}
|
|
2381
2502
|
var IndexedDbSwapRepository = class {
|
|
2382
2503
|
constructor(dbName = DEFAULT_DB_NAME) {
|
|
2383
2504
|
this.dbName = dbName;
|
|
@@ -2392,10 +2513,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2392
2513
|
async saveSwap(swap) {
|
|
2393
2514
|
const db = await this.getDB();
|
|
2394
2515
|
return new Promise((resolve, reject) => {
|
|
2395
|
-
const transaction = db.transaction(
|
|
2396
|
-
[STORE_SWAPS_STATE],
|
|
2397
|
-
"readwrite"
|
|
2398
|
-
);
|
|
2516
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2399
2517
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2400
2518
|
const request = store.put(swap);
|
|
2401
2519
|
request.onsuccess = () => resolve();
|
|
@@ -2405,10 +2523,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2405
2523
|
async deleteSwap(id) {
|
|
2406
2524
|
const db = await this.getDB();
|
|
2407
2525
|
return new Promise((resolve, reject) => {
|
|
2408
|
-
const transaction = db.transaction(
|
|
2409
|
-
[STORE_SWAPS_STATE],
|
|
2410
|
-
"readwrite"
|
|
2411
|
-
);
|
|
2526
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2412
2527
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2413
2528
|
const request = store.delete(id);
|
|
2414
2529
|
request.onsuccess = () => resolve();
|
|
@@ -2421,10 +2536,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2421
2536
|
async clear() {
|
|
2422
2537
|
const db = await this.getDB();
|
|
2423
2538
|
return new Promise((resolve, reject) => {
|
|
2424
|
-
const transaction = db.transaction(
|
|
2425
|
-
[STORE_SWAPS_STATE],
|
|
2426
|
-
"readwrite"
|
|
2427
|
-
);
|
|
2539
|
+
const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
|
|
2428
2540
|
const store = transaction.objectStore(STORE_SWAPS_STATE);
|
|
2429
2541
|
const request = store.clear();
|
|
2430
2542
|
request.onsuccess = () => resolve();
|
|
@@ -2441,11 +2553,10 @@ var IndexedDbSwapRepository = class {
|
|
|
2441
2553
|
request.onsuccess = () => resolve(request.result ?? []);
|
|
2442
2554
|
})
|
|
2443
2555
|
);
|
|
2444
|
-
return Promise.all(requests).then(
|
|
2445
|
-
(results) => results.flatMap((result) => result)
|
|
2446
|
-
);
|
|
2556
|
+
return Promise.all(requests).then((results) => results.flatMap((result) => result));
|
|
2447
2557
|
}
|
|
2448
2558
|
async getAllSwapsFromStore(filter) {
|
|
2559
|
+
if (hasImpossibleSwapsFilter(filter)) return [];
|
|
2449
2560
|
const db = await this.getDB();
|
|
2450
2561
|
const store = db.transaction([STORE_SWAPS_STATE], "readonly").objectStore(STORE_SWAPS_STATE);
|
|
2451
2562
|
if (!filter || Object.keys(filter).length === 0) {
|
|
@@ -2455,9 +2566,8 @@ var IndexedDbSwapRepository = class {
|
|
|
2455
2566
|
request.onerror = () => reject(request.error);
|
|
2456
2567
|
});
|
|
2457
2568
|
}
|
|
2458
|
-
const
|
|
2459
|
-
if (
|
|
2460
|
-
const ids = normalizedFilter.get("id");
|
|
2569
|
+
const ids = asArray(filter.id);
|
|
2570
|
+
if (ids) {
|
|
2461
2571
|
const swaps = await Promise.all(
|
|
2462
2572
|
ids.map(
|
|
2463
2573
|
(id) => new Promise((resolve, reject) => {
|
|
@@ -2467,34 +2577,17 @@ var IndexedDbSwapRepository = class {
|
|
|
2467
2577
|
})
|
|
2468
2578
|
)
|
|
2469
2579
|
);
|
|
2470
|
-
return
|
|
2471
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2472
|
-
filter
|
|
2473
|
-
);
|
|
2580
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2474
2581
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const swaps = await this.getSwapsByIndexValues(
|
|
2478
|
-
|
|
2479
|
-
"type",
|
|
2480
|
-
types
|
|
2481
|
-
);
|
|
2482
|
-
return this.sortIfNeeded(
|
|
2483
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2484
|
-
filter
|
|
2485
|
-
);
|
|
2582
|
+
const types = asArray(filter.type);
|
|
2583
|
+
if (types) {
|
|
2584
|
+
const swaps = await this.getSwapsByIndexValues(store, "type", types);
|
|
2585
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2486
2586
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
const swaps = await this.getSwapsByIndexValues(
|
|
2490
|
-
|
|
2491
|
-
"status",
|
|
2492
|
-
ids
|
|
2493
|
-
);
|
|
2494
|
-
return this.sortIfNeeded(
|
|
2495
|
-
this.applySwapsFilter(swaps, normalizedFilter),
|
|
2496
|
-
filter
|
|
2497
|
-
);
|
|
2587
|
+
const statuses = asArray(filter.status);
|
|
2588
|
+
if (statuses) {
|
|
2589
|
+
const swaps = await this.getSwapsByIndexValues(store, "status", statuses);
|
|
2590
|
+
return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
|
|
2498
2591
|
}
|
|
2499
2592
|
if (filter.orderBy === "createdAt") {
|
|
2500
2593
|
return this.getAllSwapsByCreatedAt(store, filter.orderDirection);
|
|
@@ -2504,22 +2597,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2504
2597
|
request.onsuccess = () => resolve(request.result ?? []);
|
|
2505
2598
|
request.onerror = () => reject(request.error);
|
|
2506
2599
|
});
|
|
2507
|
-
return
|
|
2508
|
-
this.applySwapsFilter(allSwaps, normalizedFilter),
|
|
2509
|
-
filter
|
|
2510
|
-
);
|
|
2511
|
-
}
|
|
2512
|
-
applySwapsFilter(swaps, filter) {
|
|
2513
|
-
return swaps.filter((swap) => {
|
|
2514
|
-
if (swap === void 0) return false;
|
|
2515
|
-
if (filter.has("id") && !filter.get("id")?.includes(swap.id))
|
|
2516
|
-
return false;
|
|
2517
|
-
if (filter.has("status") && !filter.get("status")?.includes(swap.status))
|
|
2518
|
-
return false;
|
|
2519
|
-
if (filter.has("type") && !filter.get("type")?.includes(swap.type))
|
|
2520
|
-
return false;
|
|
2521
|
-
return true;
|
|
2522
|
-
});
|
|
2600
|
+
return applyCreatedAtOrder(applySwapsFilter(allSwaps, filter), filter);
|
|
2523
2601
|
}
|
|
2524
2602
|
async getAllSwapsByCreatedAt(store, orderDirection) {
|
|
2525
2603
|
const index = store.index("createdAt");
|
|
@@ -2539,30 +2617,12 @@ var IndexedDbSwapRepository = class {
|
|
|
2539
2617
|
};
|
|
2540
2618
|
});
|
|
2541
2619
|
}
|
|
2542
|
-
sortIfNeeded(swaps, filter) {
|
|
2543
|
-
if (filter?.orderBy !== "createdAt") return swaps;
|
|
2544
|
-
const direction = filter.orderDirection === "asc" ? 1 : -1;
|
|
2545
|
-
return swaps.slice().sort((a, b) => (a.createdAt - b.createdAt) * direction);
|
|
2546
|
-
}
|
|
2547
2620
|
async [Symbol.asyncDispose]() {
|
|
2548
2621
|
if (!this.db) return;
|
|
2549
2622
|
await (0, import_sdk4.closeDatabase)(this.dbName);
|
|
2550
2623
|
this.db = null;
|
|
2551
2624
|
}
|
|
2552
2625
|
};
|
|
2553
|
-
var FILTER_FIELDS = ["id", "status", "type"];
|
|
2554
|
-
function normalizeFilter(filter) {
|
|
2555
|
-
const res = /* @__PURE__ */ new Map();
|
|
2556
|
-
FILTER_FIELDS.forEach((current) => {
|
|
2557
|
-
if (!filter?.[current]) return;
|
|
2558
|
-
if (Array.isArray(filter[current])) {
|
|
2559
|
-
res.set(current, filter[current]);
|
|
2560
|
-
} else {
|
|
2561
|
-
res.set(current, [filter[current]]);
|
|
2562
|
-
}
|
|
2563
|
-
});
|
|
2564
|
-
return res;
|
|
2565
|
-
}
|
|
2566
2626
|
|
|
2567
2627
|
// src/utils/identity.ts
|
|
2568
2628
|
var import_sdk5 = require("@arkade-os/sdk");
|
|
@@ -2574,13 +2634,8 @@ function claimVHTLCIdentity(identity, preimage) {
|
|
|
2574
2634
|
let signedTx = await identity.sign(cpy, inputIndexes);
|
|
2575
2635
|
signedTx = import_sdk5.Transaction.fromPSBT(signedTx.toPSBT());
|
|
2576
2636
|
if (preimage) {
|
|
2577
|
-
for (const inputIndex of inputIndexes || Array.from(
|
|
2578
|
-
|
|
2579
|
-
(_, i) => i
|
|
2580
|
-
)) {
|
|
2581
|
-
(0, import_sdk5.setArkPsbtField)(signedTx, inputIndex, import_sdk5.ConditionWitness, [
|
|
2582
|
-
preimage
|
|
2583
|
-
]);
|
|
2637
|
+
for (const inputIndex of inputIndexes || Array.from({ length: signedTx.inputsLength }, (_, i) => i)) {
|
|
2638
|
+
(0, import_sdk5.setArkPsbtField)(signedTx, inputIndex, import_sdk5.ConditionWitness, [preimage]);
|
|
2584
2639
|
}
|
|
2585
2640
|
}
|
|
2586
2641
|
return signedTx;
|
|
@@ -2635,17 +2690,13 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2635
2690
|
if (!sweepTapTreeRoot) {
|
|
2636
2691
|
throw new Error("Sweep tap tree root not set");
|
|
2637
2692
|
}
|
|
2638
|
-
const xOnlyPublicKeys = event.cosignersPublicKeys.map(
|
|
2639
|
-
(k) => k.slice(2)
|
|
2640
|
-
);
|
|
2693
|
+
const xOnlyPublicKeys = event.cosignersPublicKeys.map((k) => k.slice(2));
|
|
2641
2694
|
const signerPublicKey = await session.getPublicKey();
|
|
2642
2695
|
const xonlySignerPublicKey = signerPublicKey.subarray(1);
|
|
2643
2696
|
if (!xOnlyPublicKeys.includes(import_base7.hex.encode(xonlySignerPublicKey))) {
|
|
2644
2697
|
return { skip: true };
|
|
2645
2698
|
}
|
|
2646
|
-
const commitmentTx = import_sdk6.Transaction.fromPSBT(
|
|
2647
|
-
import_base7.base64.decode(event.unsignedCommitmentTx)
|
|
2648
|
-
);
|
|
2699
|
+
const commitmentTx = import_sdk6.Transaction.fromPSBT(import_base7.base64.decode(event.unsignedCommitmentTx));
|
|
2649
2700
|
(0, import_sdk6.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
2650
2701
|
const sharedOutput = commitmentTx.getOutput(0);
|
|
2651
2702
|
if (!sharedOutput?.amount) {
|
|
@@ -2661,18 +2712,11 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2661
2712
|
if (!session) {
|
|
2662
2713
|
return { fullySigned: true };
|
|
2663
2714
|
}
|
|
2664
|
-
const { hasAllNonces } = await session.aggregatedNonces(
|
|
2665
|
-
event.txid,
|
|
2666
|
-
event.nonces
|
|
2667
|
-
);
|
|
2715
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
2668
2716
|
if (!hasAllNonces) return { fullySigned: false };
|
|
2669
2717
|
const signatures = await session.sign();
|
|
2670
2718
|
const pubkey = import_base7.hex.encode(await session.getPublicKey());
|
|
2671
|
-
await arkProvider.submitTreeSignatures(
|
|
2672
|
-
event.id,
|
|
2673
|
-
pubkey,
|
|
2674
|
-
signatures
|
|
2675
|
-
);
|
|
2719
|
+
await arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
2676
2720
|
return { fullySigned: true };
|
|
2677
2721
|
},
|
|
2678
2722
|
onBatchFinalization: async (event, _, connectorTree) => {
|
|
@@ -2680,9 +2724,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2680
2724
|
return;
|
|
2681
2725
|
}
|
|
2682
2726
|
if (!connectorTree) {
|
|
2683
|
-
throw new Error(
|
|
2684
|
-
"BatchFinalizationEvent: expected connector tree to be defined"
|
|
2685
|
-
);
|
|
2727
|
+
throw new Error("BatchFinalizationEvent: expected connector tree to be defined");
|
|
2686
2728
|
}
|
|
2687
2729
|
(0, import_sdk6.validateConnectorsTxGraph)(event.commitmentTx, connectorTree);
|
|
2688
2730
|
const connectors = connectorTree.leaves();
|
|
@@ -2697,9 +2739,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
|
|
|
2697
2739
|
connectors[connectorIndex]
|
|
2698
2740
|
);
|
|
2699
2741
|
const signedForfeitTx = await identity.sign(forfeitTx);
|
|
2700
|
-
await arkProvider.submitSignedForfeitTxs([
|
|
2701
|
-
import_base7.base64.encode(signedForfeitTx.toPSBT())
|
|
2702
|
-
]);
|
|
2742
|
+
await arkProvider.submitSignedForfeitTxs([import_base7.base64.encode(signedForfeitTx.toPSBT())]);
|
|
2703
2743
|
}
|
|
2704
2744
|
};
|
|
2705
2745
|
}
|
|
@@ -2759,36 +2799,22 @@ var createVHTLCScript = (args) => {
|
|
|
2759
2799
|
serverPubkey,
|
|
2760
2800
|
timeoutBlockHeights: vhtlcTimeouts
|
|
2761
2801
|
} = args;
|
|
2762
|
-
const receiverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
);
|
|
2766
|
-
const senderXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2767
|
-
import_base8.hex.decode(senderPubkey),
|
|
2768
|
-
"sender"
|
|
2769
|
-
);
|
|
2770
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
2771
|
-
import_base8.hex.decode(serverPubkey),
|
|
2772
|
-
"server"
|
|
2773
|
-
);
|
|
2802
|
+
const receiverXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(receiverPubkey), "receiver");
|
|
2803
|
+
const senderXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(senderPubkey), "sender");
|
|
2804
|
+
const serverXOnlyPublicKey = normalizeToXOnlyKey(import_base8.hex.decode(serverPubkey), "server");
|
|
2774
2805
|
const vhtlcScript = new import_sdk7.VHTLC.Script({
|
|
2775
2806
|
preimageHash: (0, import_legacy.ripemd160)(preimageHash),
|
|
2776
2807
|
sender: senderXOnlyPublicKey,
|
|
2777
2808
|
receiver: receiverXOnlyPublicKey,
|
|
2778
2809
|
server: serverXOnlyPublicKey,
|
|
2779
2810
|
refundLocktime: BigInt(vhtlcTimeouts.refund),
|
|
2780
|
-
unilateralClaimDelay: toBip68RelativeTimelock(
|
|
2781
|
-
|
|
2782
|
-
),
|
|
2783
|
-
unilateralRefundDelay: toBip68RelativeTimelock(
|
|
2784
|
-
vhtlcTimeouts.unilateralRefund
|
|
2785
|
-
),
|
|
2811
|
+
unilateralClaimDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralClaim),
|
|
2812
|
+
unilateralRefundDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralRefund),
|
|
2786
2813
|
unilateralRefundWithoutReceiverDelay: toBip68RelativeTimelock(
|
|
2787
2814
|
vhtlcTimeouts.unilateralRefundWithoutReceiver
|
|
2788
2815
|
)
|
|
2789
2816
|
});
|
|
2790
|
-
if (!vhtlcScript.claimScript)
|
|
2791
|
-
throw new Error("Failed to create VHTLC script");
|
|
2817
|
+
if (!vhtlcScript.claimScript) throw new Error("Failed to create VHTLC script");
|
|
2792
2818
|
const hrp = network === "bitcoin" ? "ark" : "tark";
|
|
2793
2819
|
const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
|
|
2794
2820
|
return { vhtlcScript, vhtlcAddress };
|
|
@@ -2822,11 +2848,7 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2822
2848
|
unknown: [import_sdk7.VtxoTaprootTree.encode(input.tapTree)],
|
|
2823
2849
|
sequence: (0, import_sdk7.getSequence)(input.tapLeafScript)
|
|
2824
2850
|
};
|
|
2825
|
-
const registerIntent = import_sdk7.Intent.create(
|
|
2826
|
-
intentMessage,
|
|
2827
|
-
[intentInput],
|
|
2828
|
-
[output]
|
|
2829
|
-
);
|
|
2851
|
+
const registerIntent = import_sdk7.Intent.create(intentMessage, [intentInput], [output]);
|
|
2830
2852
|
const deleteIntent = import_sdk7.Intent.create(deleteMessage, [intentInput]);
|
|
2831
2853
|
const [signedRegisterIntent, signedDeleteIntent] = await Promise.all([
|
|
2832
2854
|
identity.sign(registerIntent),
|
|
@@ -2850,14 +2872,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2850
2872
|
normalizeToXOnlyKey(forfeitPubkey, "forfeit"),
|
|
2851
2873
|
isRecoverable2 ? void 0 : import_btc_signer4.OutScript.encode(decodedAddress)
|
|
2852
2874
|
);
|
|
2853
|
-
const topics = [
|
|
2854
|
-
|
|
2855
|
-
`${input.txid}:${input.vout}`
|
|
2856
|
-
];
|
|
2857
|
-
const eventStream = arkProvider.getEventStream(
|
|
2858
|
-
abortController.signal,
|
|
2859
|
-
topics
|
|
2860
|
-
);
|
|
2875
|
+
const topics = [import_base8.hex.encode(signerPublicKey), `${input.txid}:${input.vout}`];
|
|
2876
|
+
const eventStream = arkProvider.getEventStream(abortController.signal, topics);
|
|
2861
2877
|
const commitmentTxid = await import_sdk7.Batch.join(eventStream, handler, {
|
|
2862
2878
|
abortController
|
|
2863
2879
|
});
|
|
@@ -2878,14 +2894,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
|
|
|
2878
2894
|
};
|
|
2879
2895
|
var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKey, input, output, arkInfo, arkProvider) => {
|
|
2880
2896
|
const rawCheckpointTapscript = import_base8.hex.decode(arkInfo.checkpointTapscript);
|
|
2881
|
-
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(
|
|
2882
|
-
|
|
2883
|
-
);
|
|
2884
|
-
const { arkTx, checkpoints } = (0, import_sdk7.buildOffchainTx)(
|
|
2885
|
-
[input],
|
|
2886
|
-
[output],
|
|
2887
|
-
serverUnrollScript
|
|
2888
|
-
);
|
|
2897
|
+
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(rawCheckpointTapscript);
|
|
2898
|
+
const { arkTx, checkpoints } = (0, import_sdk7.buildOffchainTx)([input], [output], serverUnrollScript);
|
|
2889
2899
|
const signedArkTx = await identity.sign(arkTx);
|
|
2890
2900
|
const { arkTxid, finalArkTx, signedCheckpointTxs } = await arkProvider.submitTx(
|
|
2891
2901
|
import_base8.base64.encode(signedArkTx.toPSBT()),
|
|
@@ -2893,9 +2903,7 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2893
2903
|
);
|
|
2894
2904
|
const finalTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
|
|
2895
2905
|
const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
|
|
2896
|
-
const claimLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2897
|
-
scriptFromTapLeafScript(vhtlcScript.claim())
|
|
2898
|
-
);
|
|
2906
|
+
const claimLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(vhtlcScript.claim()));
|
|
2899
2907
|
for (let i = 0; i < finalTx.inputsLength; i++) {
|
|
2900
2908
|
if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
|
|
2901
2909
|
throw new Error("Invalid final Ark transaction");
|
|
@@ -2905,13 +2913,9 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2905
2913
|
signedCheckpointTxs.map(async (c, idx) => {
|
|
2906
2914
|
const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(c));
|
|
2907
2915
|
const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
|
|
2908
|
-
const cpLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2909
|
-
scriptFromTapLeafScript(checkpointLeaf)
|
|
2910
|
-
);
|
|
2916
|
+
const cpLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(checkpointLeaf));
|
|
2911
2917
|
if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
|
|
2912
|
-
throw new Error(
|
|
2913
|
-
"Invalid server signature in checkpoint transaction"
|
|
2914
|
-
);
|
|
2918
|
+
throw new Error("Invalid server signature in checkpoint transaction");
|
|
2915
2919
|
}
|
|
2916
2920
|
const signedCheckpoint = await identity.sign(tx, [0]);
|
|
2917
2921
|
return import_base8.base64.encode(signedCheckpoint.toPSBT());
|
|
@@ -2921,61 +2925,37 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
|
|
|
2921
2925
|
};
|
|
2922
2926
|
var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfo, refundFunc) => {
|
|
2923
2927
|
const rawCheckpointTapscript = import_base8.hex.decode(arkInfo.checkpointTapscript);
|
|
2924
|
-
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(
|
|
2925
|
-
|
|
2928
|
+
const serverUnrollScript = import_sdk7.CSVMultisigTapscript.decode(rawCheckpointTapscript);
|
|
2929
|
+
const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = (0, import_sdk7.buildOffchainTx)(
|
|
2930
|
+
[input],
|
|
2931
|
+
[output],
|
|
2932
|
+
serverUnrollScript
|
|
2926
2933
|
);
|
|
2927
|
-
const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = (0, import_sdk7.buildOffchainTx)([input], [output], serverUnrollScript);
|
|
2928
2934
|
if (checkpointPtxs.length !== 1)
|
|
2929
|
-
throw new Error(
|
|
2930
|
-
`Expected one checkpoint transaction, got ${checkpointPtxs.length}`
|
|
2931
|
-
);
|
|
2935
|
+
throw new Error(`Expected one checkpoint transaction, got ${checkpointPtxs.length}`);
|
|
2932
2936
|
const unsignedCheckpointTx = checkpointPtxs[0];
|
|
2933
2937
|
let boltzSignedRefundTx;
|
|
2934
2938
|
let boltzSignedCheckpointTx;
|
|
2935
2939
|
try {
|
|
2936
|
-
const result = await refundFunc(
|
|
2937
|
-
swapId,
|
|
2938
|
-
unsignedRefundTx,
|
|
2939
|
-
unsignedCheckpointTx
|
|
2940
|
-
);
|
|
2940
|
+
const result = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
|
|
2941
2941
|
boltzSignedRefundTx = result.transaction;
|
|
2942
2942
|
boltzSignedCheckpointTx = result.checkpoint;
|
|
2943
2943
|
} catch (error) {
|
|
2944
|
-
throw new BoltzRefundError(
|
|
2945
|
-
`Boltz rejected refund for swap ${swapId}`,
|
|
2946
|
-
error
|
|
2947
|
-
);
|
|
2944
|
+
throw new BoltzRefundError(`Boltz rejected refund for swap ${swapId}`, error);
|
|
2948
2945
|
}
|
|
2949
2946
|
const boltzXOnlyPublicKeyHex = import_base8.hex.encode(boltzXOnlyPublicKey);
|
|
2950
|
-
const refundLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2951
|
-
|
|
2952
|
-
);
|
|
2953
|
-
if (!verifySignatures(
|
|
2954
|
-
boltzSignedRefundTx,
|
|
2955
|
-
0,
|
|
2956
|
-
[boltzXOnlyPublicKeyHex],
|
|
2957
|
-
refundLeafHash
|
|
2958
|
-
)) {
|
|
2947
|
+
const refundLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(input.tapLeafScript));
|
|
2948
|
+
if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex], refundLeafHash)) {
|
|
2959
2949
|
throw new Error("Invalid Boltz signature in refund transaction");
|
|
2960
2950
|
}
|
|
2961
2951
|
const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
|
|
2962
|
-
const checkpointLeafHash = (0, import_payment3.tapLeafHash)(
|
|
2963
|
-
|
|
2964
|
-
);
|
|
2965
|
-
if (!verifySignatures(
|
|
2966
|
-
boltzSignedCheckpointTx,
|
|
2967
|
-
0,
|
|
2968
|
-
[boltzXOnlyPublicKeyHex],
|
|
2969
|
-
checkpointLeafHash
|
|
2970
|
-
)) {
|
|
2952
|
+
const checkpointLeafHash = (0, import_payment3.tapLeafHash)(scriptFromTapLeafScript(checkpointLeaf));
|
|
2953
|
+
if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex], checkpointLeafHash)) {
|
|
2971
2954
|
throw new Error("Invalid Boltz signature in checkpoint transaction");
|
|
2972
2955
|
}
|
|
2973
2956
|
const signedRefundTx = await identity.sign(unsignedRefundTx);
|
|
2974
2957
|
const signedCheckpointTx = await identity.sign(unsignedCheckpointTx);
|
|
2975
|
-
const combinedSignedRefundTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
2976
|
-
boltzSignedRefundTx,
|
|
2977
|
-
signedRefundTx
|
|
2978
|
-
);
|
|
2958
|
+
const combinedSignedRefundTx = (0, import_sdk7.combineTapscriptSigs)(boltzSignedRefundTx, signedRefundTx);
|
|
2979
2959
|
const combinedSignedCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
2980
2960
|
boltzSignedCheckpointTx,
|
|
2981
2961
|
signedCheckpointTx
|
|
@@ -2999,25 +2979,16 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
|
|
|
2999
2979
|
`Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
|
|
3000
2980
|
);
|
|
3001
2981
|
}
|
|
3002
|
-
const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(
|
|
3003
|
-
import_base8.base64.decode(signedCheckpointTxs[0])
|
|
3004
|
-
);
|
|
2982
|
+
const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(signedCheckpointTxs[0]));
|
|
3005
2983
|
const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
|
|
3006
|
-
if (!verifySignatures(
|
|
3007
|
-
serverSignedCheckpointTx,
|
|
3008
|
-
0,
|
|
3009
|
-
[serverPubkeyHex],
|
|
3010
|
-
checkpointLeafHash
|
|
3011
|
-
)) {
|
|
2984
|
+
if (!verifySignatures(serverSignedCheckpointTx, 0, [serverPubkeyHex], checkpointLeafHash)) {
|
|
3012
2985
|
throw new Error("Invalid server signature in checkpoint transaction");
|
|
3013
2986
|
}
|
|
3014
2987
|
const finalCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
|
|
3015
2988
|
combinedSignedCheckpointTx,
|
|
3016
2989
|
serverSignedCheckpointTx
|
|
3017
2990
|
);
|
|
3018
|
-
await arkProvider.finalizeTx(arkTxid, [
|
|
3019
|
-
import_base8.base64.encode(finalCheckpointTx.toPSBT())
|
|
3020
|
-
]);
|
|
2991
|
+
await arkProvider.finalizeTx(arkTxid, [import_base8.base64.encode(finalCheckpointTx.toPSBT())]);
|
|
3021
2992
|
};
|
|
3022
2993
|
function scriptFromTapLeafScript(leaf) {
|
|
3023
2994
|
return leaf[1].subarray(0, leaf[1].length - 1);
|
|
@@ -3025,21 +2996,21 @@ function scriptFromTapLeafScript(leaf) {
|
|
|
3025
2996
|
|
|
3026
2997
|
// src/arkade-swaps.ts
|
|
3027
2998
|
var dedupeVtxos = (vtxos) => [
|
|
3028
|
-
...new Map(
|
|
3029
|
-
vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])
|
|
3030
|
-
).values()
|
|
2999
|
+
...new Map(vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])).values()
|
|
3031
3000
|
];
|
|
3032
3001
|
var hasNonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
3033
3002
|
var canRecoverViaBoltz3of3 = (refundableVtxos, swap) => {
|
|
3034
3003
|
const hasRequiredSwapMetadata = hasNonEmptyString(swap.id) && hasNonEmptyString(swap.request.refundPublicKey) && hasNonEmptyString(swap.response.address) && hasNonEmptyString(swap.response.claimPublicKey) && !!swap.response.timeoutBlockHeights;
|
|
3035
3004
|
if (!hasRequiredSwapMetadata) return false;
|
|
3036
|
-
return refundableVtxos.some(
|
|
3037
|
-
(vtxo) => !vtxo.isSpent && !(0, import_sdk8.isRecoverable)(vtxo)
|
|
3038
|
-
);
|
|
3005
|
+
return refundableVtxos.some((vtxo) => !vtxo.isSpent && !(0, import_sdk8.isRecoverable)(vtxo));
|
|
3039
3006
|
};
|
|
3040
3007
|
var isSubmarineRefundLocktimeReached = (refundTimestamp) => Math.floor(Date.now() / 1e3) >= refundTimestamp;
|
|
3041
3008
|
var CLAIM_VTXO_RETRY_ATTEMPTS = 3;
|
|
3042
3009
|
var CLAIM_VTXO_RETRY_DELAY_MS = 500;
|
|
3010
|
+
var quoteOptionsForSwap = (swap) => {
|
|
3011
|
+
const amount = swap.response?.claimDetails?.amount;
|
|
3012
|
+
return typeof amount === "number" ? { minAcceptableAmount: amount } : void 0;
|
|
3013
|
+
};
|
|
3043
3014
|
var ArkadeSwaps = class _ArkadeSwaps {
|
|
3044
3015
|
/** The Arkade wallet instance used for signing and address generation. */
|
|
3045
3016
|
wallet;
|
|
@@ -3075,10 +3046,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3075
3046
|
return new _ArkadeSwaps(config);
|
|
3076
3047
|
}
|
|
3077
3048
|
const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
|
|
3078
|
-
if (!arkProvider)
|
|
3079
|
-
throw new Error(
|
|
3080
|
-
"Ark provider is required either in wallet or config."
|
|
3081
|
-
);
|
|
3049
|
+
if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
|
|
3082
3050
|
const arkInfo = await arkProvider.getInfo();
|
|
3083
3051
|
const network = arkInfo.network;
|
|
3084
3052
|
const swapProvider = new BoltzSwapProvider({ network });
|
|
@@ -3089,16 +3057,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3089
3057
|
if (!config.swapProvider) throw new Error("Swap provider is required.");
|
|
3090
3058
|
this.wallet = config.wallet;
|
|
3091
3059
|
const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
|
|
3092
|
-
if (!arkProvider)
|
|
3093
|
-
throw new Error(
|
|
3094
|
-
"Ark provider is required either in wallet or config."
|
|
3095
|
-
);
|
|
3060
|
+
if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
|
|
3096
3061
|
this.arkProvider = arkProvider;
|
|
3097
3062
|
const indexerProvider = config.indexerProvider ?? config.wallet.indexerProvider;
|
|
3098
3063
|
if (!indexerProvider)
|
|
3099
|
-
throw new Error(
|
|
3100
|
-
"Indexer provider is required either in wallet or config."
|
|
3101
|
-
);
|
|
3064
|
+
throw new Error("Indexer provider is required either in wallet or config.");
|
|
3102
3065
|
this.indexerProvider = indexerProvider;
|
|
3103
3066
|
this.swapProvider = config.swapProvider;
|
|
3104
3067
|
if (config.swapRepository) {
|
|
@@ -3109,10 +3072,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3109
3072
|
if (config.swapManager !== false) {
|
|
3110
3073
|
const swapManagerConfig = !config.swapManager || config.swapManager === true ? {} : config.swapManager;
|
|
3111
3074
|
const shouldAutostart = swapManagerConfig.autoStart ?? true;
|
|
3112
|
-
this.swapManager = new SwapManager(
|
|
3113
|
-
this.swapProvider,
|
|
3114
|
-
swapManagerConfig
|
|
3115
|
-
);
|
|
3075
|
+
this.swapManager = new SwapManager(this.swapProvider, swapManagerConfig);
|
|
3116
3076
|
this.swapManager.setCallbacks({
|
|
3117
3077
|
claim: async (swap) => {
|
|
3118
3078
|
await this.claimVHTLC(swap);
|
|
@@ -3127,7 +3087,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3127
3087
|
await this.claimBtc(swap);
|
|
3128
3088
|
},
|
|
3129
3089
|
refundArk: async (swap) => {
|
|
3130
|
-
|
|
3090
|
+
return this.refundArk(swap);
|
|
3131
3091
|
},
|
|
3132
3092
|
signServerClaim: async (swap) => {
|
|
3133
3093
|
await this.signCooperativeClaimForServer(swap);
|
|
@@ -3260,19 +3220,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3260
3220
|
* @throws {SwapError} If amount is <= 0 or key retrieval fails.
|
|
3261
3221
|
*/
|
|
3262
3222
|
async createReverseSwap(args) {
|
|
3263
|
-
if (args.amount <= 0)
|
|
3264
|
-
|
|
3265
|
-
const claimPublicKey = import_base9.hex.encode(
|
|
3266
|
-
await this.wallet.identity.compressedPublicKey()
|
|
3267
|
-
);
|
|
3223
|
+
if (args.amount <= 0) throw new SwapError({ message: "Amount must be greater than 0" });
|
|
3224
|
+
const claimPublicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
3268
3225
|
if (!claimPublicKey)
|
|
3269
3226
|
throw new SwapError({
|
|
3270
3227
|
message: "Failed to get claim public key from wallet"
|
|
3271
3228
|
});
|
|
3272
3229
|
const preimage = (0, import_utils3.randomBytes)(32);
|
|
3273
3230
|
const preimageHash = import_base9.hex.encode((0, import_sha23.sha256)(preimage));
|
|
3274
|
-
if (!preimageHash)
|
|
3275
|
-
throw new SwapError({ message: "Failed to get preimage hash" });
|
|
3231
|
+
if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
|
|
3276
3232
|
const swapRequest = {
|
|
3277
3233
|
invoiceAmount: args.amount,
|
|
3278
3234
|
claimPublicKey,
|
|
@@ -3302,18 +3258,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3302
3258
|
*/
|
|
3303
3259
|
async claimVHTLC(pendingSwap) {
|
|
3304
3260
|
if (!pendingSwap.preimage)
|
|
3305
|
-
throw new Error(
|
|
3306
|
-
`Swap ${pendingSwap.id}: preimage is required to claim VHTLC`
|
|
3307
|
-
);
|
|
3261
|
+
throw new Error(`Swap ${pendingSwap.id}: preimage is required to claim VHTLC`);
|
|
3308
3262
|
const {
|
|
3309
3263
|
refundPublicKey,
|
|
3310
3264
|
lockupAddress,
|
|
3311
3265
|
timeoutBlockHeights: vhtlcTimeouts
|
|
3312
3266
|
} = pendingSwap.response;
|
|
3313
3267
|
if (!refundPublicKey || !lockupAddress || !vhtlcTimeouts)
|
|
3314
|
-
throw new Error(
|
|
3315
|
-
`Swap ${pendingSwap.id}: incomplete reverse swap response`
|
|
3316
|
-
);
|
|
3268
|
+
throw new Error(`Swap ${pendingSwap.id}: incomplete reverse swap response`);
|
|
3317
3269
|
const preimage = import_base9.hex.decode(pendingSwap.preimage);
|
|
3318
3270
|
const arkInfo = await this.arkProvider.getInfo();
|
|
3319
3271
|
const address = await this.wallet.getAddress();
|
|
@@ -3348,58 +3300,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3348
3300
|
throw new Error(
|
|
3349
3301
|
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3350
3302
|
);
|
|
3351
|
-
let
|
|
3303
|
+
let unspentVtxos = [];
|
|
3304
|
+
let rawVtxos = [];
|
|
3352
3305
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
3353
|
-
const
|
|
3306
|
+
const result = await this.indexerProvider.getVtxos({
|
|
3354
3307
|
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
3355
3308
|
});
|
|
3356
|
-
|
|
3357
|
-
|
|
3309
|
+
rawVtxos = result.vtxos;
|
|
3310
|
+
unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
3311
|
+
if (unspentVtxos.length > 0) {
|
|
3358
3312
|
break;
|
|
3359
3313
|
}
|
|
3360
3314
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
3361
|
-
await new Promise(
|
|
3362
|
-
(resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
|
|
3363
|
-
);
|
|
3315
|
+
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
3364
3316
|
}
|
|
3365
3317
|
}
|
|
3366
|
-
if (
|
|
3367
|
-
|
|
3368
|
-
`Swap ${pendingSwap.id}: no spendable virtual coins found`
|
|
3369
|
-
|
|
3370
|
-
}
|
|
3371
|
-
if (vtxo.isSpent) {
|
|
3318
|
+
if (unspentVtxos.length === 0) {
|
|
3319
|
+
if (rawVtxos.length === 0) {
|
|
3320
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
3321
|
+
}
|
|
3372
3322
|
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3373
3323
|
}
|
|
3374
|
-
const
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3324
|
+
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
3325
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3326
|
+
const claimErrors = [];
|
|
3327
|
+
let usedOffchainClaim = false;
|
|
3328
|
+
for (const vtxo of unspentVtxos) {
|
|
3329
|
+
const input = {
|
|
3330
|
+
...vtxo,
|
|
3331
|
+
tapLeafScript: vhtlcScript.claim(),
|
|
3332
|
+
tapTree: vhtlcScript.encode()
|
|
3333
|
+
};
|
|
3334
|
+
const output = {
|
|
3335
|
+
amount: BigInt(vtxo.value),
|
|
3336
|
+
script: outputScript
|
|
3337
|
+
};
|
|
3338
|
+
try {
|
|
3339
|
+
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
3340
|
+
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
3341
|
+
} else {
|
|
3342
|
+
await claimVHTLCwithOffchainTx(
|
|
3343
|
+
vhtlcIdentity,
|
|
3344
|
+
vhtlcScript,
|
|
3345
|
+
serverXOnly,
|
|
3346
|
+
input,
|
|
3347
|
+
output,
|
|
3348
|
+
arkInfo,
|
|
3349
|
+
this.arkProvider
|
|
3350
|
+
);
|
|
3351
|
+
usedOffchainClaim = true;
|
|
3352
|
+
}
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
claimErrors.push({ vtxo, error });
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
if (claimErrors.length > 0) {
|
|
3358
|
+
const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
|
|
3359
|
+
throw new Error(
|
|
3360
|
+
`Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
|
|
3400
3361
|
);
|
|
3401
|
-
finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
|
|
3402
3362
|
}
|
|
3363
|
+
const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
|
|
3403
3364
|
await updateReverseSwapStatus(
|
|
3404
3365
|
pendingSwap,
|
|
3405
3366
|
finalStatus,
|
|
@@ -3502,9 +3463,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3502
3463
|
async sendLightningPayment(args) {
|
|
3503
3464
|
const pendingSwap = await this.createSubmarineSwap(args);
|
|
3504
3465
|
if (!pendingSwap.response.address)
|
|
3505
|
-
throw new Error(
|
|
3506
|
-
`Swap ${pendingSwap.id}: missing address in submarine swap response`
|
|
3507
|
-
);
|
|
3466
|
+
throw new Error(`Swap ${pendingSwap.id}: missing address in submarine swap response`);
|
|
3508
3467
|
await this.savePendingSubmarineSwap(pendingSwap);
|
|
3509
3468
|
const txid = await this.wallet.send({
|
|
3510
3469
|
address: pendingSwap.response.address,
|
|
@@ -3537,9 +3496,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3537
3496
|
* @throws {SwapError} If invoice is missing or key retrieval fails.
|
|
3538
3497
|
*/
|
|
3539
3498
|
async createSubmarineSwap(args) {
|
|
3540
|
-
const refundPublicKey = import_base9.hex.encode(
|
|
3541
|
-
await this.wallet.identity.compressedPublicKey()
|
|
3542
|
-
);
|
|
3499
|
+
const refundPublicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
3543
3500
|
if (!refundPublicKey)
|
|
3544
3501
|
throw new SwapError({
|
|
3545
3502
|
message: "Failed to get refund public key from wallet"
|
|
@@ -3577,9 +3534,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3577
3534
|
async buildSubmarineVHTLCContext(swap, arkInfo) {
|
|
3578
3535
|
const preimageHash = swap.request.invoice ? getInvoicePaymentHash(swap.request.invoice) : swap.preimageHash;
|
|
3579
3536
|
if (!preimageHash)
|
|
3580
|
-
throw new Error(
|
|
3581
|
-
`Swap ${swap.id}: preimage hash is required to refund VHTLC`
|
|
3582
|
-
);
|
|
3537
|
+
throw new Error(`Swap ${swap.id}: preimage hash is required to refund VHTLC`);
|
|
3583
3538
|
const resolvedArkInfo = arkInfo ?? await this.arkProvider.getInfo();
|
|
3584
3539
|
const ourXOnlyPublicKey = normalizeToXOnlyKey(
|
|
3585
3540
|
await this.wallet.identity.xOnlyPublicKey(),
|
|
@@ -3593,9 +3548,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3593
3548
|
);
|
|
3594
3549
|
const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
|
|
3595
3550
|
if (!claimPublicKey || !vhtlcTimeouts)
|
|
3596
|
-
throw new Error(
|
|
3597
|
-
`Swap ${swap.id}: incomplete submarine swap response`
|
|
3598
|
-
);
|
|
3551
|
+
throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
|
|
3599
3552
|
const boltzXOnlyPublicKey = normalizeToXOnlyKey(
|
|
3600
3553
|
import_base9.hex.decode(claimPublicKey),
|
|
3601
3554
|
"boltz",
|
|
@@ -3610,9 +3563,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3610
3563
|
timeoutBlockHeights: vhtlcTimeouts
|
|
3611
3564
|
});
|
|
3612
3565
|
if (!vhtlcScript.claimScript)
|
|
3613
|
-
throw new Error(
|
|
3614
|
-
`Swap ${swap.id}: failed to create VHTLC script for submarine swap`
|
|
3615
|
-
);
|
|
3566
|
+
throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
|
|
3616
3567
|
if (vhtlcAddress !== swap.response.address)
|
|
3617
3568
|
throw new Error(
|
|
3618
3569
|
`VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
|
|
@@ -3651,10 +3602,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3651
3602
|
recoverableOnly: true
|
|
3652
3603
|
})
|
|
3653
3604
|
]);
|
|
3654
|
-
const refundableVtxos = dedupeVtxos([
|
|
3655
|
-
...spendableResult.vtxos,
|
|
3656
|
-
...recoverableResult.vtxos
|
|
3657
|
-
]);
|
|
3605
|
+
const refundableVtxos = dedupeVtxos([...spendableResult.vtxos, ...recoverableResult.vtxos]);
|
|
3658
3606
|
let diagnostic;
|
|
3659
3607
|
if (refundableVtxos.length === 0) {
|
|
3660
3608
|
const { vtxos: allVtxos } = await this.indexerProvider.getVtxos({
|
|
@@ -3674,13 +3622,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3674
3622
|
submarineRecoveryInfoFromLookup(swap, lookup) {
|
|
3675
3623
|
const { refundableVtxos, diagnostic, vhtlcTimeouts } = lookup;
|
|
3676
3624
|
if (refundableVtxos.length > 0) {
|
|
3677
|
-
const cltvSatisfied = isSubmarineRefundLocktimeReached(
|
|
3678
|
-
|
|
3679
|
-
);
|
|
3680
|
-
const amountSats = refundableVtxos.reduce(
|
|
3681
|
-
(sum, vtxo) => sum + Number(vtxo.value),
|
|
3682
|
-
0
|
|
3683
|
-
);
|
|
3625
|
+
const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
|
|
3626
|
+
const amountSats = refundableVtxos.reduce((sum, vtxo) => sum + Number(vtxo.value), 0);
|
|
3684
3627
|
const isRecoverable2 = cltvSatisfied || canRecoverViaBoltz3of3(refundableVtxos, swap);
|
|
3685
3628
|
return {
|
|
3686
3629
|
swap,
|
|
@@ -3744,19 +3687,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3744
3687
|
);
|
|
3745
3688
|
}
|
|
3746
3689
|
if (diagnostic.allSpent) {
|
|
3747
|
-
throw new Error(
|
|
3748
|
-
`Swap ${pendingSwap.id}: VHTLC is already spent`
|
|
3749
|
-
);
|
|
3690
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
3750
3691
|
}
|
|
3751
|
-
throw new Error(
|
|
3752
|
-
`Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`
|
|
3753
|
-
);
|
|
3692
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`);
|
|
3754
3693
|
}
|
|
3755
3694
|
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
3756
3695
|
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
3757
|
-
const cltvSatisfied = isSubmarineRefundLocktimeReached(
|
|
3758
|
-
vhtlcTimeouts.refund
|
|
3759
|
-
);
|
|
3696
|
+
const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
|
|
3760
3697
|
let boltzCallCount = 0;
|
|
3761
3698
|
let sweptCount = 0;
|
|
3762
3699
|
let skippedCount = 0;
|
|
@@ -3798,6 +3735,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3798
3735
|
if (boltzCallCount > 0) {
|
|
3799
3736
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3800
3737
|
}
|
|
3738
|
+
boltzCallCount++;
|
|
3801
3739
|
await refundVHTLCwithOffchainTx(
|
|
3802
3740
|
pendingSwap.id,
|
|
3803
3741
|
this.wallet.identity,
|
|
@@ -3808,11 +3746,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3808
3746
|
input,
|
|
3809
3747
|
output,
|
|
3810
3748
|
arkInfo,
|
|
3811
|
-
this.swapProvider.refundSubmarineSwap.bind(
|
|
3812
|
-
this.swapProvider
|
|
3813
|
-
)
|
|
3749
|
+
this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3814
3750
|
);
|
|
3815
|
-
boltzCallCount++;
|
|
3816
3751
|
sweptCount++;
|
|
3817
3752
|
} catch (error) {
|
|
3818
3753
|
if (!(error instanceof BoltzRefundError)) {
|
|
@@ -3833,13 +3768,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3833
3768
|
tapLeafScript: refundWithoutReceiverLeaf,
|
|
3834
3769
|
tapTree: vhtlcScript.encode()
|
|
3835
3770
|
};
|
|
3836
|
-
await this.joinBatch(
|
|
3837
|
-
this.wallet.identity,
|
|
3838
|
-
fallbackInput,
|
|
3839
|
-
output,
|
|
3840
|
-
arkInfo,
|
|
3841
|
-
false
|
|
3842
|
-
);
|
|
3771
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
3843
3772
|
sweptCount++;
|
|
3844
3773
|
}
|
|
3845
3774
|
}
|
|
@@ -3935,10 +3864,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3935
3864
|
try {
|
|
3936
3865
|
return {
|
|
3937
3866
|
swap,
|
|
3938
|
-
context: await this.buildSubmarineVHTLCContext(
|
|
3939
|
-
swap,
|
|
3940
|
-
arkInfo
|
|
3941
|
-
)
|
|
3867
|
+
context: await this.buildSubmarineVHTLCContext(swap, arkInfo)
|
|
3942
3868
|
};
|
|
3943
3869
|
} catch (err) {
|
|
3944
3870
|
return {
|
|
@@ -3951,9 +3877,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3951
3877
|
const valid = prepared.filter(
|
|
3952
3878
|
(item) => "context" in item
|
|
3953
3879
|
);
|
|
3954
|
-
const scripts = [
|
|
3955
|
-
...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))
|
|
3956
|
-
];
|
|
3880
|
+
const scripts = [...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))];
|
|
3957
3881
|
const refundableByScript = /* @__PURE__ */ new Map();
|
|
3958
3882
|
if (scripts.length > 0) {
|
|
3959
3883
|
const [spendableResult, recoverableResult] = await Promise.all([
|
|
@@ -3988,9 +3912,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3988
3912
|
error: item.error
|
|
3989
3913
|
};
|
|
3990
3914
|
}
|
|
3991
|
-
const refundableVtxos = refundableByScript.get(
|
|
3992
|
-
item.context.vhtlcPkScriptHex.toLowerCase()
|
|
3993
|
-
) ?? [];
|
|
3915
|
+
const refundableVtxos = refundableByScript.get(item.context.vhtlcPkScriptHex.toLowerCase()) ?? [];
|
|
3994
3916
|
return this.submarineRecoveryInfoFromLookup(item.swap, {
|
|
3995
3917
|
...item.context,
|
|
3996
3918
|
refundableVtxos
|
|
@@ -4169,9 +4091,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4169
4091
|
*/
|
|
4170
4092
|
async waitAndClaimBtc(pendingSwap) {
|
|
4171
4093
|
if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
|
|
4172
|
-
const { txid } = await this.swapManager.waitForSwapCompletion(
|
|
4173
|
-
pendingSwap.id
|
|
4174
|
-
);
|
|
4094
|
+
const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
|
|
4175
4095
|
return { txid };
|
|
4176
4096
|
}
|
|
4177
4097
|
return new Promise((resolve, reject) => {
|
|
@@ -4198,24 +4118,25 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4198
4118
|
}
|
|
4199
4119
|
case "transaction.claimed":
|
|
4200
4120
|
await updateSwapStatus();
|
|
4201
|
-
const claimedStatus = await this.getSwapStatus(
|
|
4202
|
-
pendingSwap.id
|
|
4203
|
-
);
|
|
4121
|
+
const claimedStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4204
4122
|
resolve({
|
|
4205
4123
|
txid: claimedStatus.transaction?.id ?? ""
|
|
4206
4124
|
});
|
|
4207
4125
|
break;
|
|
4208
4126
|
case "transaction.lockupFailed":
|
|
4209
4127
|
await updateSwapStatus();
|
|
4210
|
-
await this.quoteSwap(swap.response.id).catch(
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4128
|
+
await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
|
|
4129
|
+
(err) => {
|
|
4130
|
+
reject(
|
|
4131
|
+
new SwapError({
|
|
4132
|
+
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4133
|
+
isRefundable: true,
|
|
4134
|
+
pendingSwap: swap,
|
|
4135
|
+
cause: err
|
|
4136
|
+
})
|
|
4137
|
+
);
|
|
4138
|
+
}
|
|
4139
|
+
);
|
|
4219
4140
|
break;
|
|
4220
4141
|
case "swap.expired":
|
|
4221
4142
|
await updateSwapStatus();
|
|
@@ -4253,30 +4174,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4253
4174
|
*/
|
|
4254
4175
|
async claimBtc(pendingSwap) {
|
|
4255
4176
|
if (!pendingSwap.toAddress)
|
|
4256
|
-
throw new Error(
|
|
4257
|
-
`Swap ${pendingSwap.id}: destination address is required`
|
|
4258
|
-
);
|
|
4177
|
+
throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
|
|
4259
4178
|
if (!pendingSwap.response.claimDetails.swapTree)
|
|
4260
|
-
throw new Error(
|
|
4261
|
-
`Swap ${pendingSwap.id}: missing swap tree in claim details`
|
|
4262
|
-
);
|
|
4179
|
+
throw new Error(`Swap ${pendingSwap.id}: missing swap tree in claim details`);
|
|
4263
4180
|
if (!pendingSwap.response.claimDetails.serverPublicKey)
|
|
4264
|
-
throw new Error(
|
|
4265
|
-
`Swap ${pendingSwap.id}: missing server public key in claim details`
|
|
4266
|
-
);
|
|
4181
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
|
|
4267
4182
|
const swapStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4268
4183
|
if (!swapStatus.transaction?.hex)
|
|
4269
|
-
throw new Error(
|
|
4270
|
-
|
|
4271
|
-
);
|
|
4272
|
-
const lockupTx = import_btc_signer5.Transaction.fromRaw(
|
|
4273
|
-
import_base9.hex.decode(swapStatus.transaction.hex)
|
|
4274
|
-
);
|
|
4184
|
+
throw new Error(`Swap ${pendingSwap.id}: BTC transaction hex is required`);
|
|
4185
|
+
const lockupTx = import_btc_signer5.Transaction.fromRaw(import_base9.hex.decode(swapStatus.transaction.hex));
|
|
4275
4186
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4276
4187
|
const network = arkInfo.network === "bitcoin" ? import_utils4.NETWORK : arkInfo.network === "mutinynet" ? MUTINYNET_NETWORK : REGTEST_NETWORK;
|
|
4277
|
-
const swapTree = deserializeSwapTree(
|
|
4278
|
-
pendingSwap.response.claimDetails.swapTree
|
|
4279
|
-
);
|
|
4188
|
+
const swapTree = deserializeSwapTree(pendingSwap.response.claimDetails.swapTree);
|
|
4280
4189
|
const musig = tweakMusig(
|
|
4281
4190
|
create(import_base9.hex.decode(pendingSwap.ephemeralKey), [
|
|
4282
4191
|
import_base9.hex.decode(pendingSwap.response.claimDetails.serverPublicKey),
|
|
@@ -4297,19 +4206,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4297
4206
|
vout: swapOutput.vout,
|
|
4298
4207
|
transactionId: lockupTx.id
|
|
4299
4208
|
},
|
|
4300
|
-
import_btc_signer5.OutScript.encode(
|
|
4301
|
-
(0, import_btc_signer5.Address)(network).decode(pendingSwap.toAddress)
|
|
4302
|
-
),
|
|
4209
|
+
import_btc_signer5.OutScript.encode((0, import_btc_signer5.Address)(network).decode(pendingSwap.toAddress)),
|
|
4303
4210
|
feeToDeliverExactAmount > fee ? feeToDeliverExactAmount : fee
|
|
4304
4211
|
)
|
|
4305
4212
|
);
|
|
4306
4213
|
const musigMessage = musig.message(
|
|
4307
|
-
claimTx.preimageWitnessV1(
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
import_btc_signer5.SigHash.DEFAULT,
|
|
4311
|
-
[swapOutput.amount]
|
|
4312
|
-
)
|
|
4214
|
+
claimTx.preimageWitnessV1(0, [swapOutput.script], import_btc_signer5.SigHash.DEFAULT, [
|
|
4215
|
+
swapOutput.amount
|
|
4216
|
+
])
|
|
4313
4217
|
).generateNonce();
|
|
4314
4218
|
const signedTxData = await this.swapProvider.postChainClaimDetails(
|
|
4315
4219
|
pendingSwap.response.id,
|
|
@@ -4323,14 +4227,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4323
4227
|
}
|
|
4324
4228
|
);
|
|
4325
4229
|
if (!signedTxData.pubNonce || !signedTxData.partialSignature)
|
|
4326
|
-
throw new Error(
|
|
4327
|
-
`Swap ${pendingSwap.id}: invalid signature data from server`
|
|
4328
|
-
);
|
|
4230
|
+
throw new Error(`Swap ${pendingSwap.id}: invalid signature data from server`);
|
|
4329
4231
|
const musigSession = musigMessage.aggregateNonces([
|
|
4330
4232
|
[
|
|
4331
|
-
import_base9.hex.decode(
|
|
4332
|
-
pendingSwap.response.claimDetails.serverPublicKey
|
|
4333
|
-
),
|
|
4233
|
+
import_base9.hex.decode(pendingSwap.response.claimDetails.serverPublicKey),
|
|
4334
4234
|
import_base9.hex.decode(signedTxData.pubNonce)
|
|
4335
4235
|
]
|
|
4336
4236
|
]).initializeSession();
|
|
@@ -4345,18 +4245,23 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4345
4245
|
await this.swapProvider.postBtcTransaction(claimTx.hex);
|
|
4346
4246
|
}
|
|
4347
4247
|
/**
|
|
4348
|
-
* When an ARK to BTC swap fails, refund
|
|
4248
|
+
* When an ARK to BTC swap fails, refund every unspent VTXO at the chain
|
|
4249
|
+
* swap's ARK lockup address.
|
|
4250
|
+
*
|
|
4251
|
+
* Path selection per VTXO:
|
|
4252
|
+
* - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
|
|
4253
|
+
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
|
|
4254
|
+
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
|
|
4255
|
+
*
|
|
4349
4256
|
* @param pendingSwap - The pending chain swap to refund.
|
|
4257
|
+
* @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
|
|
4258
|
+
* the call was a no-op — callers should retry after CLTV.
|
|
4350
4259
|
*/
|
|
4351
4260
|
async refundArk(pendingSwap) {
|
|
4352
4261
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
4353
|
-
throw new Error(
|
|
4354
|
-
`Swap ${pendingSwap.id}: missing server public key in lockup details`
|
|
4355
|
-
);
|
|
4262
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
|
|
4356
4263
|
if (!pendingSwap.response.lockupDetails.timeouts)
|
|
4357
|
-
throw new Error(
|
|
4358
|
-
`Swap ${pendingSwap.id}: missing timeouts in lockup details`
|
|
4359
|
-
);
|
|
4264
|
+
throw new Error(`Swap ${pendingSwap.id}: missing timeouts in lockup details`);
|
|
4360
4265
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4361
4266
|
const address = await this.wallet.getAddress();
|
|
4362
4267
|
const ourXOnlyPublicKey = normalizeToXOnlyKey(
|
|
@@ -4374,21 +4279,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4374
4279
|
"boltz",
|
|
4375
4280
|
pendingSwap.id
|
|
4376
4281
|
);
|
|
4377
|
-
const vhtlcPkScript = import_sdk8.ArkAddress.decode(
|
|
4378
|
-
pendingSwap.response.lockupDetails.lockupAddress
|
|
4379
|
-
).pkScript;
|
|
4380
|
-
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4381
|
-
scripts: [import_base9.hex.encode(vhtlcPkScript)]
|
|
4382
|
-
});
|
|
4383
|
-
if (vtxos.length === 0) {
|
|
4384
|
-
throw new Error(
|
|
4385
|
-
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4386
|
-
);
|
|
4387
|
-
}
|
|
4388
|
-
const vtxo = vtxos[0];
|
|
4389
|
-
if (vtxo.isSpent) {
|
|
4390
|
-
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4391
|
-
}
|
|
4392
4282
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4393
4283
|
network: arkInfo.network,
|
|
4394
4284
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4398,45 +4288,111 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4398
4288
|
timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
|
|
4399
4289
|
});
|
|
4400
4290
|
if (!vhtlcScript.refundScript)
|
|
4401
|
-
throw new Error(
|
|
4402
|
-
`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
|
|
4403
|
-
);
|
|
4291
|
+
throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
|
|
4404
4292
|
if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
|
|
4405
4293
|
throw new SwapError({
|
|
4406
4294
|
message: "Unable to claim: invalid VHTLC address"
|
|
4407
4295
|
});
|
|
4408
4296
|
}
|
|
4409
|
-
const
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
const output = {
|
|
4416
|
-
amount: BigInt(vtxo.value),
|
|
4417
|
-
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4418
|
-
};
|
|
4419
|
-
if (isRecoverableVtxo) {
|
|
4420
|
-
await this.joinBatch(this.wallet.identity, input, output, arkInfo);
|
|
4421
|
-
} else {
|
|
4422
|
-
await refundVHTLCwithOffchainTx(
|
|
4423
|
-
pendingSwap.id,
|
|
4424
|
-
this.wallet.identity,
|
|
4425
|
-
this.arkProvider,
|
|
4426
|
-
boltzXOnlyPublicKey,
|
|
4427
|
-
ourXOnlyPublicKey,
|
|
4428
|
-
serverXOnlyPublicKey,
|
|
4429
|
-
input,
|
|
4430
|
-
output,
|
|
4431
|
-
arkInfo,
|
|
4432
|
-
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4297
|
+
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
4298
|
+
scripts: [import_base9.hex.encode(vhtlcScript.pkScript)]
|
|
4299
|
+
});
|
|
4300
|
+
if (vtxos.length === 0) {
|
|
4301
|
+
throw new Error(
|
|
4302
|
+
`Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
|
|
4433
4303
|
);
|
|
4434
4304
|
}
|
|
4305
|
+
const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
|
|
4306
|
+
if (unspentVtxos.length === 0) {
|
|
4307
|
+
throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
|
|
4308
|
+
}
|
|
4309
|
+
const outputScript = import_sdk8.ArkAddress.decode(address).pkScript;
|
|
4310
|
+
const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
|
|
4311
|
+
const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
|
|
4312
|
+
let boltzCallCount = 0;
|
|
4313
|
+
let sweptCount = 0;
|
|
4314
|
+
let skippedCount = 0;
|
|
4315
|
+
for (const vtxo of unspentVtxos) {
|
|
4316
|
+
const isRecoverableVtxo = (0, import_sdk8.isRecoverable)(vtxo);
|
|
4317
|
+
const output = {
|
|
4318
|
+
amount: BigInt(vtxo.value),
|
|
4319
|
+
script: outputScript
|
|
4320
|
+
};
|
|
4321
|
+
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4322
|
+
const input2 = {
|
|
4323
|
+
...vtxo,
|
|
4324
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4325
|
+
tapTree: vhtlcScript.encode()
|
|
4326
|
+
};
|
|
4327
|
+
await this.joinBatch(
|
|
4328
|
+
this.wallet.identity,
|
|
4329
|
+
input2,
|
|
4330
|
+
output,
|
|
4331
|
+
arkInfo,
|
|
4332
|
+
isRecoverableVtxo
|
|
4333
|
+
);
|
|
4334
|
+
sweptCount++;
|
|
4335
|
+
continue;
|
|
4336
|
+
}
|
|
4337
|
+
if (isRecoverableVtxo) {
|
|
4338
|
+
logger.error(
|
|
4339
|
+
`Swap ${pendingSwap.id}: recoverable VTXO ${vtxo.txid}:${vtxo.vout} cannot be refunded yet \u2014 refundWithoutReceiver locktime has not passed (refundLocktime=${refundLocktime}, currentTimestamp=${Math.floor(Date.now() / 1e3)}). Refund will be retried after locktime.`
|
|
4340
|
+
);
|
|
4341
|
+
skippedCount++;
|
|
4342
|
+
continue;
|
|
4343
|
+
}
|
|
4344
|
+
const input = {
|
|
4345
|
+
...vtxo,
|
|
4346
|
+
tapLeafScript: vhtlcScript.refund(),
|
|
4347
|
+
tapTree: vhtlcScript.encode()
|
|
4348
|
+
};
|
|
4349
|
+
try {
|
|
4350
|
+
if (boltzCallCount > 0) {
|
|
4351
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4352
|
+
}
|
|
4353
|
+
boltzCallCount++;
|
|
4354
|
+
await refundVHTLCwithOffchainTx(
|
|
4355
|
+
pendingSwap.id,
|
|
4356
|
+
this.wallet.identity,
|
|
4357
|
+
this.arkProvider,
|
|
4358
|
+
boltzXOnlyPublicKey,
|
|
4359
|
+
ourXOnlyPublicKey,
|
|
4360
|
+
serverXOnlyPublicKey,
|
|
4361
|
+
input,
|
|
4362
|
+
output,
|
|
4363
|
+
arkInfo,
|
|
4364
|
+
this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4365
|
+
);
|
|
4366
|
+
sweptCount++;
|
|
4367
|
+
} catch (error) {
|
|
4368
|
+
if (!(error instanceof BoltzRefundError)) {
|
|
4369
|
+
throw error;
|
|
4370
|
+
}
|
|
4371
|
+
if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
4372
|
+
logger.error(
|
|
4373
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint and refundWithoutReceiver locktime has not passed yet (currentTimestamp=${Math.floor(Date.now() / 1e3)}, locktime=${refundLocktime}). Refund will be retried after locktime.`
|
|
4374
|
+
);
|
|
4375
|
+
skippedCount++;
|
|
4376
|
+
continue;
|
|
4377
|
+
}
|
|
4378
|
+
logger.warn(
|
|
4379
|
+
`Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
|
|
4380
|
+
);
|
|
4381
|
+
const fallbackInput = {
|
|
4382
|
+
...vtxo,
|
|
4383
|
+
tapLeafScript: refundWithoutReceiverLeaf,
|
|
4384
|
+
tapTree: vhtlcScript.encode()
|
|
4385
|
+
};
|
|
4386
|
+
await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
|
|
4387
|
+
sweptCount++;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4435
4390
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4436
4391
|
await this.savePendingChainSwap({
|
|
4437
4392
|
...pendingSwap,
|
|
4438
4393
|
status: finalStatus.status
|
|
4439
4394
|
});
|
|
4395
|
+
return { swept: sweptCount, skipped: skippedCount };
|
|
4440
4396
|
}
|
|
4441
4397
|
// =========================================================================
|
|
4442
4398
|
// Chain swaps: BTC -> ARK
|
|
@@ -4481,9 +4437,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4481
4437
|
*/
|
|
4482
4438
|
async waitAndClaimArk(pendingSwap) {
|
|
4483
4439
|
if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
|
|
4484
|
-
const { txid } = await this.swapManager.waitForSwapCompletion(
|
|
4485
|
-
pendingSwap.id
|
|
4486
|
-
);
|
|
4440
|
+
const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
|
|
4487
4441
|
return { txid };
|
|
4488
4442
|
}
|
|
4489
4443
|
return new Promise((resolve, reject) => {
|
|
@@ -4504,37 +4458,33 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4504
4458
|
break;
|
|
4505
4459
|
case "transaction.claimed":
|
|
4506
4460
|
await updateSwapStatus();
|
|
4507
|
-
const claimedStatus = await this.getSwapStatus(
|
|
4508
|
-
pendingSwap.id
|
|
4509
|
-
);
|
|
4461
|
+
const claimedStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4510
4462
|
resolve({
|
|
4511
4463
|
txid: claimedStatus.transaction?.id ?? ""
|
|
4512
4464
|
});
|
|
4513
4465
|
break;
|
|
4514
4466
|
case "transaction.claim.pending":
|
|
4515
4467
|
await updateSwapStatus();
|
|
4516
|
-
await this.signCooperativeClaimForServer(swap).catch(
|
|
4468
|
+
await this.signCooperativeClaimForServer(swap).catch((err) => {
|
|
4469
|
+
logger.error(`Failed to sign cooperative claim for ${swap.id}:`, err);
|
|
4470
|
+
});
|
|
4471
|
+
break;
|
|
4472
|
+
case "transaction.lockupFailed":
|
|
4473
|
+
await updateSwapStatus();
|
|
4474
|
+
await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
|
|
4517
4475
|
(err) => {
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4476
|
+
reject(
|
|
4477
|
+
new SwapError({
|
|
4478
|
+
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4479
|
+
isRefundable: false,
|
|
4480
|
+
// TODO btc refund not implemented yet
|
|
4481
|
+
pendingSwap: swap,
|
|
4482
|
+
cause: err
|
|
4483
|
+
})
|
|
4521
4484
|
);
|
|
4522
4485
|
}
|
|
4523
4486
|
);
|
|
4524
4487
|
break;
|
|
4525
|
-
case "transaction.lockupFailed":
|
|
4526
|
-
await updateSwapStatus();
|
|
4527
|
-
await this.quoteSwap(swap.response.id).catch((err) => {
|
|
4528
|
-
reject(
|
|
4529
|
-
new SwapError({
|
|
4530
|
-
message: `Failed to renegotiate quote: ${err.message}`,
|
|
4531
|
-
isRefundable: false,
|
|
4532
|
-
// TODO btc refund not implemented yet
|
|
4533
|
-
pendingSwap: swap
|
|
4534
|
-
})
|
|
4535
|
-
);
|
|
4536
|
-
});
|
|
4537
|
-
break;
|
|
4538
4488
|
case "swap.expired":
|
|
4539
4489
|
await updateSwapStatus();
|
|
4540
4490
|
reject(
|
|
@@ -4574,17 +4524,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4574
4524
|
*/
|
|
4575
4525
|
async claimArk(pendingSwap) {
|
|
4576
4526
|
if (!pendingSwap.toAddress)
|
|
4577
|
-
throw new Error(
|
|
4578
|
-
`Swap ${pendingSwap.id}: destination address is required`
|
|
4579
|
-
);
|
|
4527
|
+
throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
|
|
4580
4528
|
if (!pendingSwap.response.claimDetails.serverPublicKey)
|
|
4581
|
-
throw new Error(
|
|
4582
|
-
`Swap ${pendingSwap.id}: missing server public key in claim details`
|
|
4583
|
-
);
|
|
4529
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
|
|
4584
4530
|
if (!pendingSwap.response.claimDetails.timeouts)
|
|
4585
|
-
throw new Error(
|
|
4586
|
-
`Swap ${pendingSwap.id}: missing timeouts in claim details`
|
|
4587
|
-
);
|
|
4531
|
+
throw new Error(`Swap ${pendingSwap.id}: missing timeouts in claim details`);
|
|
4588
4532
|
const arkInfo = await this.arkProvider.getInfo();
|
|
4589
4533
|
const preimage = import_base9.hex.decode(pendingSwap.preimage);
|
|
4590
4534
|
const address = await this.wallet.getAddress();
|
|
@@ -4596,10 +4540,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4596
4540
|
pendingSwap.response.claimDetails.serverPublicKey,
|
|
4597
4541
|
"sender"
|
|
4598
4542
|
);
|
|
4599
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
4600
|
-
arkInfo.signerPubkey,
|
|
4601
|
-
"server"
|
|
4602
|
-
);
|
|
4543
|
+
const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
|
|
4603
4544
|
const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
|
|
4604
4545
|
network: arkInfo.network,
|
|
4605
4546
|
preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
|
|
@@ -4609,9 +4550,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4609
4550
|
timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
|
|
4610
4551
|
});
|
|
4611
4552
|
if (!vhtlcScript.claimScript)
|
|
4612
|
-
throw new Error(
|
|
4613
|
-
`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
|
|
4614
|
-
);
|
|
4553
|
+
throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
|
|
4615
4554
|
if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
|
|
4616
4555
|
throw new SwapError({
|
|
4617
4556
|
message: "Unable to claim: invalid VHTLC address"
|
|
@@ -4628,15 +4567,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4628
4567
|
break;
|
|
4629
4568
|
}
|
|
4630
4569
|
if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
|
|
4631
|
-
await new Promise(
|
|
4632
|
-
(resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
|
|
4633
|
-
);
|
|
4570
|
+
await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
|
|
4634
4571
|
}
|
|
4635
4572
|
}
|
|
4636
4573
|
if (!vtxo) {
|
|
4637
|
-
throw new Error(
|
|
4638
|
-
`Swap ${pendingSwap.id}: no spendable virtual coins found`
|
|
4639
|
-
);
|
|
4574
|
+
throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
|
|
4640
4575
|
}
|
|
4641
4576
|
const input = {
|
|
4642
4577
|
...vtxo,
|
|
@@ -4647,10 +4582,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4647
4582
|
amount: BigInt(vtxo.value),
|
|
4648
4583
|
script: import_sdk8.ArkAddress.decode(address).pkScript
|
|
4649
4584
|
};
|
|
4650
|
-
const vhtlcIdentity = claimVHTLCIdentity(
|
|
4651
|
-
this.wallet.identity,
|
|
4652
|
-
preimage
|
|
4653
|
-
);
|
|
4585
|
+
const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
|
|
4654
4586
|
if ((0, import_sdk8.isRecoverable)(vtxo)) {
|
|
4655
4587
|
await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
|
|
4656
4588
|
} else {
|
|
@@ -4676,16 +4608,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4676
4608
|
*/
|
|
4677
4609
|
async signCooperativeClaimForServer(pendingSwap) {
|
|
4678
4610
|
if (!pendingSwap.response.lockupDetails.swapTree)
|
|
4679
|
-
throw new Error(
|
|
4680
|
-
`Swap ${pendingSwap.id}: missing swap tree in lockup details`
|
|
4681
|
-
);
|
|
4611
|
+
throw new Error(`Swap ${pendingSwap.id}: missing swap tree in lockup details`);
|
|
4682
4612
|
if (!pendingSwap.response.lockupDetails.serverPublicKey)
|
|
4683
|
-
throw new Error(
|
|
4684
|
-
|
|
4685
|
-
);
|
|
4686
|
-
const claimDetails = await this.swapProvider.getChainClaimDetails(
|
|
4687
|
-
pendingSwap.id
|
|
4688
|
-
);
|
|
4613
|
+
throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
|
|
4614
|
+
const claimDetails = await this.swapProvider.getChainClaimDetails(pendingSwap.id);
|
|
4689
4615
|
const serverPubKey = pendingSwap.response.lockupDetails.serverPublicKey;
|
|
4690
4616
|
if (claimDetails.publicKey !== serverPubKey) {
|
|
4691
4617
|
throw new Error(
|
|
@@ -4701,9 +4627,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4701
4627
|
);
|
|
4702
4628
|
const musigNonces = musig.message(import_base9.hex.decode(claimDetails.transactionHash)).generateNonce().aggregateNonces([
|
|
4703
4629
|
[
|
|
4704
|
-
import_base9.hex.decode(
|
|
4705
|
-
pendingSwap.response.lockupDetails.serverPublicKey
|
|
4706
|
-
),
|
|
4630
|
+
import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
|
|
4707
4631
|
import_base9.hex.decode(claimDetails.pubNonce)
|
|
4708
4632
|
]
|
|
4709
4633
|
]).initializeSession();
|
|
@@ -4722,10 +4646,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4722
4646
|
* @returns The transaction ID of the claim.
|
|
4723
4647
|
*/
|
|
4724
4648
|
async waitAndClaimChain(pendingSwap) {
|
|
4725
|
-
if (pendingSwap.request.to === "ARK")
|
|
4726
|
-
|
|
4727
|
-
if (pendingSwap.request.to === "BTC")
|
|
4728
|
-
return this.waitAndClaimBtc(pendingSwap);
|
|
4649
|
+
if (pendingSwap.request.to === "ARK") return this.waitAndClaimArk(pendingSwap);
|
|
4650
|
+
if (pendingSwap.request.to === "BTC") return this.waitAndClaimBtc(pendingSwap);
|
|
4729
4651
|
throw new SwapError({
|
|
4730
4652
|
message: `Unsupported swap destination: ${pendingSwap.request.to}`
|
|
4731
4653
|
});
|
|
@@ -4740,11 +4662,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4740
4662
|
*/
|
|
4741
4663
|
async createChainSwap(args) {
|
|
4742
4664
|
const { to, from, receiverLockAmount, senderLockAmount, toAddress } = args;
|
|
4743
|
-
if (!toAddress)
|
|
4744
|
-
throw new SwapError({ message: "Destination address is required" });
|
|
4665
|
+
if (!toAddress) throw new SwapError({ message: "Destination address is required" });
|
|
4745
4666
|
const feeSatsPerByte = args.feeSatsPerByte ?? 1;
|
|
4746
|
-
if (feeSatsPerByte <= 0)
|
|
4747
|
-
throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
4667
|
+
if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
|
|
4748
4668
|
let amount, serverLockAmount, userLockAmount;
|
|
4749
4669
|
if (receiverLockAmount) {
|
|
4750
4670
|
amount = receiverLockAmount;
|
|
@@ -4759,8 +4679,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4759
4679
|
}
|
|
4760
4680
|
const preimage = (0, import_utils3.randomBytes)(32);
|
|
4761
4681
|
const preimageHash = import_base9.hex.encode((0, import_sha23.sha256)(preimage));
|
|
4762
|
-
if (!preimageHash)
|
|
4763
|
-
throw new SwapError({ message: "Failed to get preimage hash" });
|
|
4682
|
+
if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
|
|
4764
4683
|
const ephemeralKey = import_secp256k13.secp256k1.utils.randomSecretKey();
|
|
4765
4684
|
const refundPublicKey = to === "ARK" ? import_base9.hex.encode(import_secp256k13.secp256k1.getPublicKey(ephemeralKey)) : import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
4766
4685
|
if (!refundPublicKey)
|
|
@@ -4809,30 +4728,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4809
4728
|
const { to, from, swap, arkInfo } = args;
|
|
4810
4729
|
if (from === "ARK") {
|
|
4811
4730
|
if (!swap.response.lockupDetails.serverPublicKey)
|
|
4812
|
-
throw new Error(
|
|
4813
|
-
`Swap ${swap.id}: missing serverPublicKey in lockup details`
|
|
4814
|
-
);
|
|
4731
|
+
throw new Error(`Swap ${swap.id}: missing serverPublicKey in lockup details`);
|
|
4815
4732
|
if (!swap.response.lockupDetails.timeouts)
|
|
4816
|
-
throw new Error(
|
|
4817
|
-
`Swap ${swap.id}: missing timeouts in lockup details`
|
|
4818
|
-
);
|
|
4733
|
+
throw new Error(`Swap ${swap.id}: missing timeouts in lockup details`);
|
|
4819
4734
|
}
|
|
4820
4735
|
if (to === "ARK") {
|
|
4821
4736
|
if (!swap.response.claimDetails.serverPublicKey)
|
|
4822
|
-
throw new Error(
|
|
4823
|
-
`Swap ${swap.id}: missing serverPublicKey in claim details`
|
|
4824
|
-
);
|
|
4737
|
+
throw new Error(`Swap ${swap.id}: missing serverPublicKey in claim details`);
|
|
4825
4738
|
if (!swap.response.claimDetails.timeouts)
|
|
4826
|
-
throw new Error(
|
|
4827
|
-
`Swap ${swap.id}: missing timeouts in claim details`
|
|
4828
|
-
);
|
|
4739
|
+
throw new Error(`Swap ${swap.id}: missing timeouts in claim details`);
|
|
4829
4740
|
}
|
|
4830
4741
|
const lockupAddress = to === "ARK" ? swap.response.claimDetails.lockupAddress : swap.response.lockupDetails.lockupAddress;
|
|
4831
4742
|
const receiverPubkey = to === "ARK" ? swap.request.claimPublicKey : swap.response.lockupDetails.serverPublicKey;
|
|
4832
4743
|
const senderPubkey = to === "ARK" ? swap.response.claimDetails.serverPublicKey : swap.request.refundPublicKey;
|
|
4833
|
-
const serverPubkey = import_base9.hex.encode(
|
|
4834
|
-
normalizeToXOnlyKey(arkInfo.signerPubkey, "server")
|
|
4835
|
-
);
|
|
4744
|
+
const serverPubkey = import_base9.hex.encode(normalizeToXOnlyKey(arkInfo.signerPubkey, "server"));
|
|
4836
4745
|
const vhtlcTimeouts = to === "ARK" ? swap.response.claimDetails.timeouts : swap.response.lockupDetails.timeouts;
|
|
4837
4746
|
const { vhtlcAddress } = this.createVHTLCScript({
|
|
4838
4747
|
network: arkInfo.network,
|
|
@@ -4850,15 +4759,122 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4850
4759
|
return true;
|
|
4851
4760
|
}
|
|
4852
4761
|
/**
|
|
4853
|
-
* Renegotiates the quote for an existing swap.
|
|
4762
|
+
* Renegotiates the quote for an existing chain swap. Convenience wrapper
|
|
4763
|
+
* over `getSwapQuote` + `acceptSwapQuote` with a safety floor.
|
|
4764
|
+
*
|
|
4765
|
+
* The floor is resolved in order:
|
|
4766
|
+
* 1. `options.minAcceptableAmount` if provided.
|
|
4767
|
+
* 2. The original `response.claimDetails.amount` of the stored
|
|
4768
|
+
* pending swap (Boltz-confirmed server-lock amount at creation).
|
|
4769
|
+
* 3. Otherwise throws `QuoteRejectedError({ reason: "no_baseline" })`.
|
|
4770
|
+
*
|
|
4771
|
+
* `options.maxSlippageBps` (default 0) relaxes the floor by basis points.
|
|
4772
|
+
* Quotes ≤ 0 are always rejected. On rejection the acceptance is NOT
|
|
4773
|
+
* posted to Boltz.
|
|
4774
|
+
*
|
|
4775
|
+
* Prefer `getSwapQuote` / `acceptSwapQuote` for callers that want to
|
|
4776
|
+
* inspect the quote before committing.
|
|
4777
|
+
*
|
|
4854
4778
|
* @param swapId - The ID of the swap.
|
|
4779
|
+
* @param options - Optional floor and slippage configuration.
|
|
4855
4780
|
* @returns The accepted quote amount.
|
|
4781
|
+
* @throws QuoteRejectedError if the quote is non-positive, below the
|
|
4782
|
+
* effective floor, or no baseline is available.
|
|
4783
|
+
*/
|
|
4784
|
+
async quoteSwap(swapId, options) {
|
|
4785
|
+
const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
|
|
4786
|
+
const amount = await this.getSwapQuote(swapId);
|
|
4787
|
+
this.validateQuote(amount, effectiveFloor);
|
|
4788
|
+
await this.swapProvider.postChainQuote(swapId, { amount });
|
|
4789
|
+
return amount;
|
|
4790
|
+
}
|
|
4791
|
+
/**
|
|
4792
|
+
* Fetches a renegotiated quote from Boltz without accepting it.
|
|
4793
|
+
* Pair with `acceptSwapQuote` to commit a specific value.
|
|
4856
4794
|
*/
|
|
4857
|
-
async
|
|
4795
|
+
async getSwapQuote(swapId) {
|
|
4858
4796
|
const { amount } = await this.swapProvider.getChainQuote(swapId);
|
|
4797
|
+
return amount;
|
|
4798
|
+
}
|
|
4799
|
+
/**
|
|
4800
|
+
* Accepts a quote amount for an existing chain swap, after validating it
|
|
4801
|
+
* against the configured floor. See `quoteSwap` for floor-resolution rules.
|
|
4802
|
+
*
|
|
4803
|
+
* @throws QuoteRejectedError if `amount` ≤ 0, below the effective floor,
|
|
4804
|
+
* or no baseline is available.
|
|
4805
|
+
*/
|
|
4806
|
+
async acceptSwapQuote(swapId, amount, options) {
|
|
4807
|
+
const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
|
|
4808
|
+
this.validateQuote(amount, effectiveFloor);
|
|
4859
4809
|
await this.swapProvider.postChainQuote(swapId, { amount });
|
|
4860
4810
|
return amount;
|
|
4861
4811
|
}
|
|
4812
|
+
async resolveEffectiveFloor(swapId, options) {
|
|
4813
|
+
this.validateQuoteOptions(options);
|
|
4814
|
+
const floor = await this.resolveQuoteFloor(swapId, options);
|
|
4815
|
+
const slippageBps = options?.maxSlippageBps ?? 0;
|
|
4816
|
+
const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
|
|
4817
|
+
if (effectiveFloor < 1) {
|
|
4818
|
+
throw new TypeError(
|
|
4819
|
+
`Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
return effectiveFloor;
|
|
4823
|
+
}
|
|
4824
|
+
async resolveQuoteFloor(swapId, options) {
|
|
4825
|
+
if (options?.minAcceptableAmount !== void 0) {
|
|
4826
|
+
return options.minAcceptableAmount;
|
|
4827
|
+
}
|
|
4828
|
+
const swaps = await this.swapRepository.getAllSwaps({
|
|
4829
|
+
id: swapId,
|
|
4830
|
+
type: "chain"
|
|
4831
|
+
});
|
|
4832
|
+
const stored = swaps[0];
|
|
4833
|
+
const amount = stored?.response?.claimDetails?.amount;
|
|
4834
|
+
if (typeof amount !== "number") {
|
|
4835
|
+
throw new QuoteRejectedError({ reason: "no_baseline" });
|
|
4836
|
+
}
|
|
4837
|
+
return amount;
|
|
4838
|
+
}
|
|
4839
|
+
validateQuoteOptions(options) {
|
|
4840
|
+
if (options?.minAcceptableAmount !== void 0) {
|
|
4841
|
+
const v = options.minAcceptableAmount;
|
|
4842
|
+
if (!Number.isInteger(v) || v <= 0) {
|
|
4843
|
+
throw new TypeError(
|
|
4844
|
+
`Invalid minAcceptableAmount: ${v} \u2014 must be a positive integer`
|
|
4845
|
+
);
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
if (options?.maxSlippageBps !== void 0) {
|
|
4849
|
+
const v = options.maxSlippageBps;
|
|
4850
|
+
if (!Number.isInteger(v) || v < 0 || v > 1e4) {
|
|
4851
|
+
throw new TypeError(
|
|
4852
|
+
`Invalid maxSlippageBps: ${v} \u2014 must be an integer in [0, 10000]`
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
validateQuote(amount, effectiveFloor) {
|
|
4858
|
+
if (!Number.isSafeInteger(amount)) {
|
|
4859
|
+
throw new QuoteRejectedError({
|
|
4860
|
+
reason: "non_safe_integer",
|
|
4861
|
+
quotedAmount: amount
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
if (amount <= 0) {
|
|
4865
|
+
throw new QuoteRejectedError({
|
|
4866
|
+
reason: "non_positive",
|
|
4867
|
+
quotedAmount: amount
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
if (amount < effectiveFloor) {
|
|
4871
|
+
throw new QuoteRejectedError({
|
|
4872
|
+
reason: "below_floor",
|
|
4873
|
+
quotedAmount: amount,
|
|
4874
|
+
floor: effectiveFloor
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4862
4878
|
// =========================================================================
|
|
4863
4879
|
// Shared utilities
|
|
4864
4880
|
// =========================================================================
|
|
@@ -4872,14 +4888,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4872
4888
|
* @returns The commitment transaction ID.
|
|
4873
4889
|
*/
|
|
4874
4890
|
async joinBatch(identity, input, output, arkInfo, isRecoverable2 = true) {
|
|
4875
|
-
return joinBatch(
|
|
4876
|
-
this.arkProvider,
|
|
4877
|
-
identity,
|
|
4878
|
-
input,
|
|
4879
|
-
output,
|
|
4880
|
-
arkInfo,
|
|
4881
|
-
isRecoverable2
|
|
4882
|
-
);
|
|
4891
|
+
return joinBatch(this.arkProvider, identity, input, output, arkInfo, isRecoverable2);
|
|
4883
4892
|
}
|
|
4884
4893
|
/**
|
|
4885
4894
|
* Creates a VHTLC script for the swap.
|
|
@@ -4917,9 +4926,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4917
4926
|
async getPendingSubmarineSwaps() {
|
|
4918
4927
|
const swaps = await this.getPendingSubmarineSwapsFromStorage();
|
|
4919
4928
|
if (!swaps) return [];
|
|
4920
|
-
return swaps.filter(
|
|
4921
|
-
(swap) => swap.status === "invoice.set"
|
|
4922
|
-
);
|
|
4929
|
+
return swaps.filter((swap) => swap.status === "invoice.set");
|
|
4923
4930
|
}
|
|
4924
4931
|
/**
|
|
4925
4932
|
* Returns pending reverse swaps (those with status `swap.created`).
|
|
@@ -4927,9 +4934,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4927
4934
|
async getPendingReverseSwaps() {
|
|
4928
4935
|
const swaps = await this.getPendingReverseSwapsFromStorage();
|
|
4929
4936
|
if (!swaps) return [];
|
|
4930
|
-
return swaps.filter(
|
|
4931
|
-
(swap) => swap.status === "swap.created"
|
|
4932
|
-
);
|
|
4937
|
+
return swaps.filter((swap) => swap.status === "swap.created");
|
|
4933
4938
|
}
|
|
4934
4939
|
/**
|
|
4935
4940
|
* Returns pending chain swaps (those with status `swap.created`).
|
|
@@ -4968,10 +4973,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4968
4973
|
this.savePendingReverseSwap.bind(this)
|
|
4969
4974
|
)
|
|
4970
4975
|
).catch((error) => {
|
|
4971
|
-
logger.error(
|
|
4972
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
4973
|
-
error
|
|
4974
|
-
);
|
|
4976
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
4975
4977
|
})
|
|
4976
4978
|
);
|
|
4977
4979
|
}
|
|
@@ -4985,23 +4987,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4985
4987
|
this.savePendingSubmarineSwap.bind(this)
|
|
4986
4988
|
)
|
|
4987
4989
|
).catch((error) => {
|
|
4988
|
-
logger.error(
|
|
4989
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
4990
|
-
error
|
|
4991
|
-
);
|
|
4990
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
4992
4991
|
})
|
|
4993
4992
|
);
|
|
4994
4993
|
}
|
|
4995
4994
|
for (const swap of await this.getPendingChainSwapsFromStorage()) {
|
|
4996
4995
|
if (isChainFinalStatus(swap.status)) continue;
|
|
4997
4996
|
promises.push(
|
|
4998
|
-
this.getSwapStatus(swap.id).then(
|
|
4999
|
-
(
|
|
5000
|
-
).catch((error) => {
|
|
5001
|
-
logger.error(
|
|
5002
|
-
`Failed to refresh swap status for ${swap.id}:`,
|
|
5003
|
-
error
|
|
5004
|
-
);
|
|
4997
|
+
this.getSwapStatus(swap.id).then(({ status }) => this.savePendingChainSwap({ ...swap, status })).catch((error) => {
|
|
4998
|
+
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
5005
4999
|
})
|
|
5006
5000
|
);
|
|
5007
5001
|
}
|
|
@@ -5018,9 +5012,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5018
5012
|
* display/monitoring and are not automatically wired into the SwapManager.
|
|
5019
5013
|
*/
|
|
5020
5014
|
async restoreSwaps(boltzFees) {
|
|
5021
|
-
const publicKey = import_base9.hex.encode(
|
|
5022
|
-
await this.wallet.identity.compressedPublicKey()
|
|
5023
|
-
);
|
|
5015
|
+
const publicKey = import_base9.hex.encode(await this.wallet.identity.compressedPublicKey());
|
|
5024
5016
|
if (!publicKey) throw new Error("Failed to get public key from wallet");
|
|
5025
5017
|
const fees = boltzFees ?? await this.swapProvider.getFees();
|
|
5026
5018
|
const chainSwaps = [];
|
|
@@ -5072,25 +5064,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5072
5064
|
preimage: ""
|
|
5073
5065
|
});
|
|
5074
5066
|
} else if (isRestoredSubmarineSwap(swap)) {
|
|
5075
|
-
const {
|
|
5076
|
-
amount,
|
|
5077
|
-
lockupAddress,
|
|
5078
|
-
serverPublicKey,
|
|
5079
|
-
tree,
|
|
5080
|
-
timeoutBlockHeights
|
|
5081
|
-
} = swap.refundDetails;
|
|
5067
|
+
const { amount, lockupAddress, serverPublicKey, tree, timeoutBlockHeights } = swap.refundDetails;
|
|
5082
5068
|
let preimage = "";
|
|
5083
5069
|
if (!isSubmarineFinalStatus(status)) {
|
|
5084
5070
|
try {
|
|
5085
|
-
const data = await this.swapProvider.getSwapPreimage(
|
|
5086
|
-
swap.id
|
|
5087
|
-
);
|
|
5071
|
+
const data = await this.swapProvider.getSwapPreimage(swap.id);
|
|
5088
5072
|
preimage = data.preimage;
|
|
5089
5073
|
} catch (error) {
|
|
5090
|
-
logger.warn(
|
|
5091
|
-
`Failed to restore preimage for submarine swap ${id}`,
|
|
5092
|
-
error
|
|
5093
|
-
);
|
|
5074
|
+
logger.warn(`Failed to restore preimage for submarine swap ${id}`, error);
|
|
5094
5075
|
}
|
|
5095
5076
|
}
|
|
5096
5077
|
submarineSwaps.push({
|
|
@@ -5128,12 +5109,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5128
5109
|
} else if (isRestoredChainSwap(swap)) {
|
|
5129
5110
|
const refundDetails = swap.refundDetails;
|
|
5130
5111
|
if (!refundDetails) continue;
|
|
5131
|
-
const {
|
|
5132
|
-
amount,
|
|
5133
|
-
lockupAddress,
|
|
5134
|
-
serverPublicKey,
|
|
5135
|
-
timeoutBlockHeight
|
|
5136
|
-
} = refundDetails;
|
|
5112
|
+
const { amount, lockupAddress, serverPublicKey, timeoutBlockHeight } = refundDetails;
|
|
5137
5113
|
chainSwaps.push({
|
|
5138
5114
|
id,
|
|
5139
5115
|
type: "chain",
|
|
@@ -5185,9 +5161,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5185
5161
|
var SWAP_POLL_TASK_TYPE = "swap-poll";
|
|
5186
5162
|
|
|
5187
5163
|
// src/expo/arkade-lightning.ts
|
|
5188
|
-
|
|
5189
|
-
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
5190
|
-
}
|
|
5164
|
+
var import_sdk9 = require("@arkade-os/sdk");
|
|
5191
5165
|
function warnOnRemovedBackgroundFields(bg) {
|
|
5192
5166
|
if (!bg || typeof bg !== "object") return;
|
|
5193
5167
|
const removed = [];
|
|
@@ -5243,9 +5217,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
|
|
|
5243
5217
|
const instance = new _ExpoArkadeSwaps(inner, config);
|
|
5244
5218
|
await instance.seedSwapPollTask();
|
|
5245
5219
|
if (config.background.foregroundIntervalMs && config.background.foregroundIntervalMs > 0) {
|
|
5246
|
-
instance.startForegroundPolling(
|
|
5247
|
-
config.background.foregroundIntervalMs
|
|
5248
|
-
);
|
|
5220
|
+
instance.startForegroundPolling(config.background.foregroundIntervalMs);
|
|
5249
5221
|
}
|
|
5250
5222
|
return instance;
|
|
5251
5223
|
}
|
|
@@ -5259,9 +5231,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
|
|
|
5259
5231
|
const { taskQueue } = this.config.background;
|
|
5260
5232
|
const results = await taskQueue.getResults();
|
|
5261
5233
|
if (results.length > 0) {
|
|
5262
|
-
await taskQueue.acknowledgeResults(
|
|
5263
|
-
results.map((r) => r.id)
|
|
5264
|
-
);
|
|
5234
|
+
await taskQueue.acknowledgeResults(results.map((r) => r.id));
|
|
5265
5235
|
}
|
|
5266
5236
|
await this.seedSwapPollTask();
|
|
5267
5237
|
}
|
|
@@ -5270,7 +5240,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
|
|
|
5270
5240
|
const existing = await taskQueue.getTasks(SWAP_POLL_TASK_TYPE);
|
|
5271
5241
|
if (existing.length > 0) return;
|
|
5272
5242
|
const task = {
|
|
5273
|
-
id: getRandomId(),
|
|
5243
|
+
id: (0, import_sdk9.getRandomId)(),
|
|
5274
5244
|
type: SWAP_POLL_TASK_TYPE,
|
|
5275
5245
|
data: {},
|
|
5276
5246
|
createdAt: Date.now()
|
|
@@ -5387,17 +5357,17 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
|
|
|
5387
5357
|
verifyChainSwap(args) {
|
|
5388
5358
|
return this.inner.verifyChainSwap(args);
|
|
5389
5359
|
}
|
|
5390
|
-
quoteSwap(swapId) {
|
|
5391
|
-
return this.inner.quoteSwap(swapId);
|
|
5360
|
+
quoteSwap(swapId, options) {
|
|
5361
|
+
return this.inner.quoteSwap(swapId, options);
|
|
5362
|
+
}
|
|
5363
|
+
getSwapQuote(swapId) {
|
|
5364
|
+
return this.inner.getSwapQuote(swapId);
|
|
5365
|
+
}
|
|
5366
|
+
acceptSwapQuote(swapId, amount, options) {
|
|
5367
|
+
return this.inner.acceptSwapQuote(swapId, amount, options);
|
|
5392
5368
|
}
|
|
5393
5369
|
joinBatch(identity, input, output, arkInfo, isRecoverable2) {
|
|
5394
|
-
return this.inner.joinBatch(
|
|
5395
|
-
identity,
|
|
5396
|
-
input,
|
|
5397
|
-
output,
|
|
5398
|
-
arkInfo,
|
|
5399
|
-
isRecoverable2
|
|
5400
|
-
);
|
|
5370
|
+
return this.inner.joinBatch(identity, input, output, arkInfo, isRecoverable2);
|
|
5401
5371
|
}
|
|
5402
5372
|
createVHTLCScript(params) {
|
|
5403
5373
|
return this.inner.createVHTLCScript(params);
|