@arkade-os/swap 0.0.11 → 0.1.0-rc.0
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 +42 -15
- package/dist/{chunk-RIC5ZTZK.js → chunk-AM3NNUMR.js} +59 -53
- package/dist/index.cjs +585 -202
- package/dist/index.d.cts +127 -36
- package/dist/index.d.ts +127 -36
- package/dist/index.js +528 -157
- package/dist/nostr.cjs +4 -1
- package/dist/nostr.d.cts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/nostr.js +1 -1
- package/dist/repositories/realm/index.d.cts +4 -4
- package/dist/repositories/realm/index.d.ts +4 -4
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/{repository-CfE18Fif.d.ts → repository-BWP1UstE.d.cts} +84 -56
- package/dist/{repository-B02vsgcV.d.cts → repository-DLpH41ZO.d.ts} +84 -56
- package/dist/{rfq-DkckzRKK.d.ts → rfq-CzCGjICq.d.cts} +42 -32
- package/dist/{rfq-DkckzRKK.d.cts → rfq-CzCGjICq.d.ts} +42 -32
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -38,9 +38,10 @@ import {
|
|
|
38
38
|
deriveOnchainSend,
|
|
39
39
|
extractPreimage,
|
|
40
40
|
httpTransport,
|
|
41
|
+
lightningReceiveContract,
|
|
41
42
|
lightningReceiveRequest,
|
|
43
|
+
lightningSendContract,
|
|
42
44
|
lightningSendRequest,
|
|
43
|
-
lightningSendVtxoScript,
|
|
44
45
|
lockupContractParams,
|
|
45
46
|
newPreimage,
|
|
46
47
|
newRfqId,
|
|
@@ -49,7 +50,6 @@ import {
|
|
|
49
50
|
onchainReceiveRequest,
|
|
50
51
|
onchainSendRequest,
|
|
51
52
|
paymentHashOf,
|
|
52
|
-
receiveVtxoScript,
|
|
53
53
|
registerLockupContract,
|
|
54
54
|
relayTransport,
|
|
55
55
|
requestLightningReceive,
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
unilateralRefundWithoutReceiverDelay,
|
|
64
64
|
verifyLockupAddress,
|
|
65
65
|
verifyReceiveInvoice
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-AM3NNUMR.js";
|
|
67
67
|
import {
|
|
68
68
|
InMemoryAssetSwapRepository,
|
|
69
69
|
marketsCacheKey
|
|
@@ -74,11 +74,9 @@ import { hex as hex2 } from "@scure/base";
|
|
|
74
74
|
import { concatBytes } from "@scure/btc-signer/utils.js";
|
|
75
75
|
import {
|
|
76
76
|
ArkAddress,
|
|
77
|
-
RestArkProvider,
|
|
78
|
-
RestIndexerProvider,
|
|
79
77
|
arkade,
|
|
80
78
|
asset,
|
|
81
|
-
|
|
79
|
+
networkFromArkadeInfo,
|
|
82
80
|
resolveEmulatorPubkey,
|
|
83
81
|
toXOnlySignerHex
|
|
84
82
|
} from "@arkade-os/sdk";
|
|
@@ -344,7 +342,7 @@ function withExitClosure(program, exit) {
|
|
|
344
342
|
}
|
|
345
343
|
};
|
|
346
344
|
}
|
|
347
|
-
function swapProgramBinding(offer,
|
|
345
|
+
function swapProgramBinding(offer, operatorPubkey) {
|
|
348
346
|
if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
|
|
349
347
|
throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
|
|
350
348
|
}
|
|
@@ -356,7 +354,7 @@ function swapProgramBinding(offer, serverPubkey) {
|
|
|
356
354
|
args: {
|
|
357
355
|
makerWP: offer.makerPkScript.subarray(2),
|
|
358
356
|
wantAmount: offer.wantAmount,
|
|
359
|
-
server:
|
|
357
|
+
server: operatorPubkey,
|
|
360
358
|
user: offer.makerPublicKey,
|
|
361
359
|
// internal byte order
|
|
362
360
|
...offer.wantAsset && {
|
|
@@ -366,14 +364,14 @@ function swapProgramBinding(offer, serverPubkey) {
|
|
|
366
364
|
...offer.exitDelay && { exitDelay: offer.exitDelay.value }
|
|
367
365
|
},
|
|
368
366
|
keys: {
|
|
369
|
-
serverKey:
|
|
367
|
+
serverKey: operatorPubkey,
|
|
370
368
|
userKey: offer.makerPublicKey,
|
|
371
369
|
emulatorKey: offer.emulatorPubkey
|
|
372
370
|
}
|
|
373
371
|
};
|
|
374
372
|
}
|
|
375
|
-
function
|
|
376
|
-
const { program, args, keys } = swapProgramBinding(offer,
|
|
373
|
+
function offerContract(offer, operatorPubkey) {
|
|
374
|
+
const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
|
|
377
375
|
return new arkade.ArkadeProgramScript(program, args, keys);
|
|
378
376
|
}
|
|
379
377
|
var OFFER_PACKET_TYPE = 3;
|
|
@@ -487,14 +485,14 @@ function decodeOffer(data) {
|
|
|
487
485
|
throw new Error(`missing/invalid ${name}`);
|
|
488
486
|
}
|
|
489
487
|
}
|
|
490
|
-
const
|
|
488
|
+
const need2 = (name) => {
|
|
491
489
|
const v = fields[name];
|
|
492
490
|
const len = FIELDS[name].width;
|
|
493
491
|
if (!v || len !== void 0 && v.length !== len)
|
|
494
492
|
throw new Error(`missing/invalid ${name}`);
|
|
495
493
|
return v;
|
|
496
494
|
};
|
|
497
|
-
const amount =
|
|
495
|
+
const amount = need2("wantAmount");
|
|
498
496
|
if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
|
|
499
497
|
throw new Error("offer must carry exactly one of wantAsset or offerAsset");
|
|
500
498
|
}
|
|
@@ -511,13 +509,13 @@ function decodeOffer(data) {
|
|
|
511
509
|
throw new Error("offer must carry both ratioNum and ratioDen, or neither");
|
|
512
510
|
}
|
|
513
511
|
return {
|
|
514
|
-
swapPkScript:
|
|
512
|
+
swapPkScript: need2("swapPkScript"),
|
|
515
513
|
wantAmount: readU64(amount),
|
|
516
514
|
...fields.wantAsset && { wantAsset: asset.AssetId.fromBytes(fields.wantAsset) },
|
|
517
515
|
...fields.offerAsset && { offerAsset: asset.AssetId.fromBytes(fields.offerAsset) },
|
|
518
|
-
makerPkScript:
|
|
519
|
-
makerPublicKey:
|
|
520
|
-
emulatorPubkey:
|
|
516
|
+
makerPkScript: need2("makerPkScript"),
|
|
517
|
+
makerPublicKey: need2("makerPublicKey"),
|
|
518
|
+
emulatorPubkey: need2("emulatorPubkey"),
|
|
521
519
|
...ratioNum !== void 0 && { ratioNum },
|
|
522
520
|
...ratioDen !== void 0 && { ratioDen },
|
|
523
521
|
...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
|
|
@@ -530,17 +528,21 @@ function decodeExitDelay(value) {
|
|
|
530
528
|
}
|
|
531
529
|
var OFFER_CONTRACT_LABEL = "Arkade swap offer";
|
|
532
530
|
var OFFER_CONTRACT_KIND = "asset-swap-offer";
|
|
533
|
-
async function registerOfferContract(wallet,
|
|
534
|
-
const { program, args, keys } = swapProgramBinding(binding,
|
|
531
|
+
async function registerOfferContract(wallet, info, binding, operatorPubkey, expectedPkScript) {
|
|
532
|
+
const { program, args, keys } = swapProgramBinding(binding, operatorPubkey);
|
|
535
533
|
const contractManager = await wallet.getContractManager();
|
|
536
534
|
const client = await arkade.Arkade.connect({
|
|
537
|
-
|
|
538
|
-
|
|
535
|
+
// Registration derives and persists; it never broadcasts and never
|
|
536
|
+
// reads UTXOs, so the only thing the client needs off the server is the
|
|
537
|
+
// info the caller already resolved. Handing that back — rather than a
|
|
538
|
+
// provider built from a URL — is what lets `createOffer` take just a
|
|
539
|
+
// wallet, and spares a second `/v1/info` round-trip.
|
|
540
|
+
arkade: { getInfo: async () => info },
|
|
539
541
|
identity: wallet.identity,
|
|
540
542
|
// without this the row's `address` would be derived against the SDK's
|
|
541
543
|
// default network while its script is right — a row that disagrees with
|
|
542
544
|
// the address the user is about to fund
|
|
543
|
-
network:
|
|
545
|
+
network: networkFromArkadeInfo(info),
|
|
544
546
|
contractManager
|
|
545
547
|
});
|
|
546
548
|
const contract = new arkade.ArkadeContract(client, program, args, keys);
|
|
@@ -561,17 +563,21 @@ function serverExitDelay(delay) {
|
|
|
561
563
|
}
|
|
562
564
|
return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
|
|
563
565
|
}
|
|
564
|
-
async function createOffer(wallet,
|
|
566
|
+
async function createOffer(wallet, params) {
|
|
565
567
|
if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
|
|
566
568
|
throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
|
|
567
569
|
}
|
|
568
570
|
const [info, makerAddress, makerPublicKey] = await Promise.all([
|
|
569
|
-
|
|
571
|
+
// requireLive: this info binds signerPubkey into the covenant — a
|
|
572
|
+
// snapshot could derive an address the operator no longer co-signs
|
|
573
|
+
// for, so an unreachable operator fails the call instead (fail closed,
|
|
574
|
+
// the same behaviour the pre-#734 caller-held provider had)
|
|
575
|
+
wallet.getArkadeInfo({ requireLive: true }),
|
|
570
576
|
wallet.getAddress(),
|
|
571
577
|
wallet.identity.xOnlyPublicKey()
|
|
572
578
|
]);
|
|
573
|
-
const
|
|
574
|
-
const network =
|
|
579
|
+
const operatorPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
|
|
580
|
+
const network = networkFromArkadeInfo(info);
|
|
575
581
|
const emuKey = hex2.decode(
|
|
576
582
|
toXOnlySignerHex(resolveEmulatorPubkey(network, params.emulatorPubkey))
|
|
577
583
|
);
|
|
@@ -587,33 +593,33 @@ async function createOffer(wallet, arkServerUrl, params) {
|
|
|
587
593
|
// an offer that then fails to encode. @see assertExitDelay
|
|
588
594
|
exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
|
|
589
595
|
};
|
|
590
|
-
const script =
|
|
596
|
+
const script = offerContract(binding, operatorPubKey);
|
|
591
597
|
const offer = { ...binding, swapPkScript: script.pkScript };
|
|
592
|
-
await registerOfferContract(
|
|
593
|
-
wallet,
|
|
594
|
-
arkServerUrl,
|
|
595
|
-
info.network,
|
|
596
|
-
binding,
|
|
597
|
-
serverPubKey,
|
|
598
|
-
script.pkScript
|
|
599
|
-
);
|
|
598
|
+
await registerOfferContract(wallet, info, binding, operatorPubKey, script.pkScript);
|
|
600
599
|
const payload = encodeOffer(offer);
|
|
601
600
|
return {
|
|
602
601
|
offerHex: hex2.encode(payload),
|
|
603
602
|
extension: { type: OFFER_PACKET_TYPE, payload },
|
|
604
|
-
//
|
|
603
|
+
// the contract's .address() builds the address; assembling an ArkAddress
|
|
605
604
|
// from tweakedPublicKey here would silently miss any future step it gains
|
|
606
|
-
address: script.address(network.hrp,
|
|
605
|
+
address: script.address(network.hrp, operatorPubKey).encode(),
|
|
607
606
|
swapPkScript: script.pkScript
|
|
608
607
|
};
|
|
609
608
|
}
|
|
610
|
-
async function cancelOffer(wallet,
|
|
609
|
+
async function cancelOffer(wallet, offerHex, opts) {
|
|
611
610
|
const { repository, fundingTxid, swapAddress } = opts;
|
|
612
611
|
const offer = decodeOffer(hex2.decode(offerHex));
|
|
613
|
-
const contractManager = await
|
|
612
|
+
const [contractManager, info, reader, broadcaster] = await Promise.all([
|
|
613
|
+
wallet.getContractManager(),
|
|
614
|
+
wallet.getArkadeInfo({ requireLive: true }),
|
|
615
|
+
wallet.getArkadeReader(),
|
|
616
|
+
wallet.getArkadeBroadcaster()
|
|
617
|
+
]);
|
|
614
618
|
const client = await arkade.Arkade.connect({
|
|
615
|
-
|
|
616
|
-
|
|
619
|
+
// info the wallet already holds, plus its broadcast pair — the shape
|
|
620
|
+
// `ArkadeServerProvider` describes, without a second `/v1/info`
|
|
621
|
+
arkade: { getInfo: async () => info, ...broadcaster },
|
|
622
|
+
indexer: reader,
|
|
617
623
|
identity: wallet.identity,
|
|
618
624
|
// registered offers resolve their VTXOs from the contract repository
|
|
619
625
|
// instead of a direct indexer query; the indexer above stays as the
|
|
@@ -628,7 +634,7 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
|
|
|
628
634
|
const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
|
|
629
635
|
if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
|
|
630
636
|
throw new Error(
|
|
631
|
-
"rebuilt covenant does not match the offer's swapPkScript \u2014 the
|
|
637
|
+
"rebuilt covenant does not match the offer's swapPkScript \u2014 the operator signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
|
|
632
638
|
);
|
|
633
639
|
}
|
|
634
640
|
const contract = new arkade.ArkadeContract(client, program, args, keys);
|
|
@@ -1036,7 +1042,7 @@ var LightningReceiveCorridor = {
|
|
|
1036
1042
|
const receive = swap;
|
|
1037
1043
|
return {
|
|
1038
1044
|
expectedAmount: receive.expectedAmount,
|
|
1039
|
-
...receive.
|
|
1045
|
+
...receive.claimTxid ? { claimTxid: receive.claimTxid } : {}
|
|
1040
1046
|
};
|
|
1041
1047
|
},
|
|
1042
1048
|
hydrate(profile) {
|
|
@@ -1046,13 +1052,13 @@ var LightningReceiveCorridor = {
|
|
|
1046
1052
|
return {
|
|
1047
1053
|
...hydrateHashlock(profile),
|
|
1048
1054
|
expectedAmount: profile.expectedAmount,
|
|
1049
|
-
...profile.
|
|
1055
|
+
...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
|
|
1050
1056
|
};
|
|
1051
1057
|
},
|
|
1052
1058
|
// We are the claimant here, so the preimage material on the hashlock is
|
|
1053
1059
|
// ours to use.
|
|
1054
1060
|
claimSecret: (profile) => ({ ...profile.signer, ...profile.hashlock }),
|
|
1055
|
-
activityTxids: (profile) => profile.
|
|
1061
|
+
activityTxids: (profile) => profile.claimTxid ? [profile.claimTxid] : []
|
|
1056
1062
|
};
|
|
1057
1063
|
function onchainSendProfile(result) {
|
|
1058
1064
|
return {
|
|
@@ -1136,13 +1142,32 @@ var isRfqSwapTerminal = (state) => RFQ_SWAP_TERMINAL_STATES.includes(state);
|
|
|
1136
1142
|
|
|
1137
1143
|
// src/rfqRecord.ts
|
|
1138
1144
|
var RFQ_SWAP_RETENTION_SECONDS = 30 * 24 * 60 * 60;
|
|
1145
|
+
function renameLegacyClaimTxid(profile) {
|
|
1146
|
+
const { claimArkTxid, ...rest } = profile;
|
|
1147
|
+
return { ...rest, claimTxid: rest.claimTxid ?? claimArkTxid };
|
|
1148
|
+
}
|
|
1149
|
+
function normalizeRfqSwapRecord(record) {
|
|
1150
|
+
const { fundingArkTxid, refundArkTxid, lockupSpendArkTxids, ...rest } = record;
|
|
1151
|
+
const legacyClaim = record.kind === "lightning_receive" && record.profile.claimArkTxid !== void 0;
|
|
1152
|
+
if (!fundingArkTxid && !refundArkTxid && !lockupSpendArkTxids && !legacyClaim) return record;
|
|
1153
|
+
const fundingTxid = rest.fundingTxid ?? fundingArkTxid;
|
|
1154
|
+
const refundTxid = rest.refundTxid ?? refundArkTxid;
|
|
1155
|
+
const lockupSpendTxids = rest.lockupSpendTxids ?? lockupSpendArkTxids;
|
|
1156
|
+
return {
|
|
1157
|
+
...rest,
|
|
1158
|
+
...fundingTxid ? { fundingTxid } : {},
|
|
1159
|
+
...refundTxid ? { refundTxid } : {},
|
|
1160
|
+
...lockupSpendTxids?.length ? { lockupSpendTxids } : {},
|
|
1161
|
+
...legacyClaim ? { profile: renameLegacyClaimTxid(record.profile) } : {}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1139
1164
|
var managerState = (swap) => ({
|
|
1140
1165
|
rfqId: swap.rfqId,
|
|
1141
1166
|
state: swap.state,
|
|
1142
1167
|
createdAt: swap.createdAt,
|
|
1143
1168
|
updatedAt: swap.updatedAt,
|
|
1144
|
-
...swap.
|
|
1145
|
-
...swap.
|
|
1169
|
+
...swap.refundTxid ? { refundTxid: swap.refundTxid } : {},
|
|
1170
|
+
...swap.lockupSpendTxids?.length ? { lockupSpendTxids: [...swap.lockupSpendTxids] } : {},
|
|
1146
1171
|
...swap.failure ? { failure: swap.failure } : {},
|
|
1147
1172
|
...swap.blockedReason ? { blockedReason: swap.blockedReason } : {}
|
|
1148
1173
|
});
|
|
@@ -1173,27 +1198,29 @@ function createRfqSwapRecord(origin, swap) {
|
|
|
1173
1198
|
}
|
|
1174
1199
|
function updateRfqSwapRecord(record, swap) {
|
|
1175
1200
|
assertSameSwap(record, swap);
|
|
1201
|
+
const stored = normalizeRfqSwapRecord(record);
|
|
1176
1202
|
const {
|
|
1177
|
-
|
|
1178
|
-
|
|
1203
|
+
refundTxid: _refundTxid,
|
|
1204
|
+
lockupSpendTxids: _lockupSpendTxids,
|
|
1179
1205
|
failure: _failure,
|
|
1180
1206
|
blockedReason: _blockedReason,
|
|
1181
1207
|
...origin
|
|
1182
|
-
} =
|
|
1183
|
-
const handler = rfqCorridorHandlers.getOrThrow(
|
|
1208
|
+
} = stored;
|
|
1209
|
+
const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
|
|
1184
1210
|
return {
|
|
1185
1211
|
...origin,
|
|
1186
1212
|
...managerState(swap),
|
|
1187
|
-
profile: { ...
|
|
1213
|
+
profile: { ...stored.profile, ...handler.project(swap) }
|
|
1188
1214
|
};
|
|
1189
1215
|
}
|
|
1190
1216
|
function rfqSwapOriginOf(record) {
|
|
1217
|
+
const stored = normalizeRfqSwapRecord(record);
|
|
1191
1218
|
return {
|
|
1192
|
-
kind:
|
|
1193
|
-
lockupAddress:
|
|
1194
|
-
profile: { ...
|
|
1195
|
-
...
|
|
1196
|
-
...
|
|
1219
|
+
kind: stored.kind,
|
|
1220
|
+
lockupAddress: stored.lockupAddress,
|
|
1221
|
+
profile: { ...stored.profile },
|
|
1222
|
+
...stored.amount !== void 0 ? { amount: stored.amount } : {},
|
|
1223
|
+
...stored.fundingTxid ? { fundingTxid: stored.fundingTxid } : {}
|
|
1197
1224
|
};
|
|
1198
1225
|
}
|
|
1199
1226
|
function lockupScript(params, lockupAddress) {
|
|
@@ -1207,27 +1234,28 @@ function lockupScript(params, lockupAddress) {
|
|
|
1207
1234
|
return script;
|
|
1208
1235
|
}
|
|
1209
1236
|
function rebuildRfqSwap(record, params) {
|
|
1210
|
-
const
|
|
1237
|
+
const stored = normalizeRfqSwapRecord(record);
|
|
1238
|
+
const script = lockupScript(params, stored.lockupAddress);
|
|
1211
1239
|
const common = {
|
|
1212
|
-
rfqId:
|
|
1213
|
-
state:
|
|
1240
|
+
rfqId: stored.rfqId,
|
|
1241
|
+
state: stored.state,
|
|
1214
1242
|
lockupPkScript: script.pkScript,
|
|
1215
|
-
lockup: { script, address:
|
|
1243
|
+
lockup: { script, address: stored.lockupAddress },
|
|
1216
1244
|
// From the covenant, which binds it: the record's own copy would be a
|
|
1217
1245
|
// second source for the deadline the refund is gated on.
|
|
1218
1246
|
refundLocktime: Number(script.options.refundLocktime),
|
|
1219
|
-
createdAt:
|
|
1220
|
-
updatedAt:
|
|
1221
|
-
...
|
|
1222
|
-
...
|
|
1223
|
-
...
|
|
1224
|
-
...
|
|
1247
|
+
createdAt: stored.createdAt,
|
|
1248
|
+
updatedAt: stored.updatedAt,
|
|
1249
|
+
...stored.refundTxid ? { refundTxid: stored.refundTxid } : {},
|
|
1250
|
+
...stored.lockupSpendTxids?.length ? { lockupSpendTxids: [...stored.lockupSpendTxids] } : {},
|
|
1251
|
+
...stored.failure ? { failure: stored.failure } : {},
|
|
1252
|
+
...stored.blockedReason ? { blockedReason: stored.blockedReason } : {}
|
|
1225
1253
|
};
|
|
1226
|
-
const handler = rfqCorridorHandlers.getOrThrow(
|
|
1254
|
+
const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
|
|
1227
1255
|
return {
|
|
1228
1256
|
...common,
|
|
1229
|
-
kind:
|
|
1230
|
-
...handler.hydrate(
|
|
1257
|
+
kind: stored.kind,
|
|
1258
|
+
...handler.hydrate(stored.profile, { lockup: script })
|
|
1231
1259
|
};
|
|
1232
1260
|
}
|
|
1233
1261
|
function shouldRetainRfqSwap(record, now) {
|
|
@@ -1268,10 +1296,10 @@ async function fetchParsedTxs(indexer, txids) {
|
|
|
1268
1296
|
var unscannedSwapCandidates = (txs, existingIds, scanned) => txs.filter(
|
|
1269
1297
|
(tx) => tx.type === "sent" && tx.redeemTxid && !existingIds.has(tx.redeemTxid) && !scanned.has(tx.redeemTxid)
|
|
1270
1298
|
);
|
|
1271
|
-
function classifySpend(offer,
|
|
1299
|
+
function classifySpend(offer, operatorPubkey, spendTx, deposit) {
|
|
1272
1300
|
let leaves;
|
|
1273
1301
|
try {
|
|
1274
|
-
const script =
|
|
1302
|
+
const script = offerContract(offer, operatorPubkey);
|
|
1275
1303
|
if (hex5.encode(script.pkScript) !== hex5.encode(offer.swapPkScript)) return "indeterminate";
|
|
1276
1304
|
leaves = {
|
|
1277
1305
|
// both routes that hand the deposit back; `exit` is absent on an
|
|
@@ -1295,15 +1323,15 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
|
|
|
1295
1323
|
return "indeterminate";
|
|
1296
1324
|
}
|
|
1297
1325
|
var spendTxidsOf = (vtxo) => [vtxo.spentBy, vtxo.arkTxId].filter((id) => Boolean(id));
|
|
1298
|
-
function classifyDepositSpend(offer,
|
|
1326
|
+
function classifyDepositSpend(offer, operatorPubkey, spendTxs, deposit) {
|
|
1299
1327
|
for (const tx of spendTxs) {
|
|
1300
|
-
const kind = classifySpend(offer,
|
|
1328
|
+
const kind = classifySpend(offer, operatorPubkey, tx, deposit);
|
|
1301
1329
|
if (kind !== "indeterminate") return kind;
|
|
1302
1330
|
}
|
|
1303
1331
|
return "indeterminate";
|
|
1304
1332
|
}
|
|
1305
1333
|
async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
|
|
1306
|
-
const {
|
|
1334
|
+
const { operatorPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
|
|
1307
1335
|
const candidates = unscannedSwapCandidates(txs, existingIds, scanned);
|
|
1308
1336
|
if (candidates.length === 0) return { restored: [], scannedTxids: [] };
|
|
1309
1337
|
const byTxid = new Map(candidates.map((tx) => [tx.redeemTxid, tx]));
|
|
@@ -1372,7 +1400,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
|
|
|
1372
1400
|
if (state === "swept") status = "recoverable";
|
|
1373
1401
|
else if (state === "spent") {
|
|
1374
1402
|
const spendTxs = spendTxidsOf(vtxo).map((id) => spendTxByTxid.get(id)).filter((tx) => tx !== void 0);
|
|
1375
|
-
const kind = classifyDepositSpend(offer,
|
|
1403
|
+
const kind = classifyDepositSpend(offer, operatorPubkey, spendTxs, {
|
|
1376
1404
|
txid: vtxo.txid,
|
|
1377
1405
|
vout: vtxo.vout
|
|
1378
1406
|
});
|
|
@@ -1389,7 +1417,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
|
|
|
1389
1417
|
fromAmount,
|
|
1390
1418
|
toAmount: offer.wantAmount.toString(),
|
|
1391
1419
|
// ponytail(arkade-os/ts-sdk#680): empty address makes cancel fall back
|
|
1392
|
-
// to the current
|
|
1420
|
+
// to the current operator key; store the funded address if operator-key
|
|
1393
1421
|
// rotations become real (cancelOffer now at least diagnoses the
|
|
1394
1422
|
// mismatch instead of reporting a missing VTXO)
|
|
1395
1423
|
swapAddress: "",
|
|
@@ -1411,7 +1439,6 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
|
|
|
1411
1439
|
import { base64 as base642, hex as hex6 } from "@scure/base";
|
|
1412
1440
|
import {
|
|
1413
1441
|
ArkAddress as ArkAddress3,
|
|
1414
|
-
RestIndexerProvider as RestIndexerProvider3,
|
|
1415
1442
|
Transaction as Transaction2
|
|
1416
1443
|
} from "@arkade-os/sdk";
|
|
1417
1444
|
var TERMINAL = ["fulfilled", "cancelled", "recoverable"];
|
|
@@ -1428,13 +1455,15 @@ function spendUpdate(swap, spend) {
|
|
|
1428
1455
|
}
|
|
1429
1456
|
async function watchOfferSwaps({
|
|
1430
1457
|
wallet,
|
|
1431
|
-
arkServerUrl,
|
|
1432
1458
|
repository,
|
|
1433
1459
|
onUpdate
|
|
1434
1460
|
}) {
|
|
1435
|
-
const manager = await
|
|
1436
|
-
|
|
1437
|
-
|
|
1461
|
+
const [manager, address, indexer] = await Promise.all([
|
|
1462
|
+
wallet.getContractManager(),
|
|
1463
|
+
wallet.getAddress(),
|
|
1464
|
+
wallet.getArkadeReader()
|
|
1465
|
+
]);
|
|
1466
|
+
const operatorPubkey = ArkAddress3.decode(address).serverPubKey;
|
|
1438
1467
|
let queue = Promise.resolve();
|
|
1439
1468
|
const enqueue = (task) => {
|
|
1440
1469
|
queue = queue.then(task).catch(() => {
|
|
@@ -1448,7 +1477,7 @@ async function watchOfferSwaps({
|
|
|
1448
1477
|
const { txs } = await indexer.getVirtualTxs(candidates);
|
|
1449
1478
|
return classifyDepositSpend(
|
|
1450
1479
|
decodeOffer(hex6.decode(swap.offerHex)),
|
|
1451
|
-
|
|
1480
|
+
operatorPubkey,
|
|
1452
1481
|
txs.map((psbt) => Transaction2.fromPSBT(base642.decode(psbt))),
|
|
1453
1482
|
{ txid: vtxo.txid, vout: vtxo.vout }
|
|
1454
1483
|
);
|
|
@@ -1543,11 +1572,10 @@ var LockupNeedsRecoveryError = class extends Error {
|
|
|
1543
1572
|
* into one settlement with no CLTV awareness, so an early attempt can fail
|
|
1544
1573
|
* the whole batch — including unrelated outputs that were otherwise fine.
|
|
1545
1574
|
*
|
|
1546
|
-
* Exposed as a value, not only inside the message, so a caller can
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
* not an exact alarm.
|
|
1575
|
+
* Exposed as a value, not only inside the message, so a caller can skip
|
|
1576
|
+
* pre-CLTV recoverable outputs without parsing prose. Seconds-based
|
|
1577
|
+
* locktimes mature against the chain tip's timestamp rather than wall
|
|
1578
|
+
* clock, so treat this as a floor to wait past, not an exact alarm.
|
|
1551
1579
|
*/
|
|
1552
1580
|
recoverableAfter;
|
|
1553
1581
|
constructor(outpoints, recoverableAfter) {
|
|
@@ -1608,7 +1636,7 @@ async function readLockupFate(indexer, input) {
|
|
|
1608
1636
|
if (vtxo.spentBy)
|
|
1609
1637
|
spentBy.set(vtxo.spentBy, {
|
|
1610
1638
|
checkpointTxid: vtxo.spentBy,
|
|
1611
|
-
|
|
1639
|
+
txid: vtxo.arkTxId
|
|
1612
1640
|
});
|
|
1613
1641
|
else everySpendNamed = false;
|
|
1614
1642
|
}
|
|
@@ -1637,32 +1665,32 @@ async function readLockupFate(indexer, input) {
|
|
|
1637
1665
|
}
|
|
1638
1666
|
return everySpendNamed && observed.size === spentBy.size ? { fate: "returned", spends } : { fate: "unknown" };
|
|
1639
1667
|
}
|
|
1640
|
-
async function pushRefundWithoutReceiver(
|
|
1668
|
+
async function pushRefundWithoutReceiver(operator, input) {
|
|
1641
1669
|
if (input.vtxos.length === 0) throw new Error("nothing to refund: no funded outputs");
|
|
1642
1670
|
const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
|
|
1643
1671
|
if (swept.length > 0) {
|
|
1644
1672
|
throw new LockupNeedsRecoveryError(
|
|
1645
1673
|
swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
|
|
1646
|
-
input.
|
|
1674
|
+
input.contract.options.refundLocktime
|
|
1647
1675
|
);
|
|
1648
1676
|
}
|
|
1649
|
-
const refundPkScript = input.refundPkScript ?? input.
|
|
1677
|
+
const refundPkScript = input.refundPkScript ?? input.contract.options.nonInteractiveParameters?.senderPkScript;
|
|
1650
1678
|
if (!refundPkScript) {
|
|
1651
1679
|
throw new Error(
|
|
1652
1680
|
"no refund destination: the contract carries no emulator covenant suite, so pass refundPkScript explicitly"
|
|
1653
1681
|
);
|
|
1654
1682
|
}
|
|
1655
|
-
const info = await
|
|
1656
|
-
let
|
|
1683
|
+
const info = await operator.getInfo();
|
|
1684
|
+
let operatorUnrollScript;
|
|
1657
1685
|
try {
|
|
1658
|
-
|
|
1686
|
+
operatorUnrollScript = CSVMultisigTapscript.decode(hex7.decode(info.checkpointTapscript));
|
|
1659
1687
|
} catch {
|
|
1660
|
-
throw new Error("invalid checkpointTapscript from the
|
|
1688
|
+
throw new Error("invalid checkpointTapscript from the operator");
|
|
1661
1689
|
}
|
|
1662
|
-
const leaf = input.
|
|
1663
|
-
const tapTree = input.
|
|
1690
|
+
const leaf = input.contract.refundWithoutReceiver();
|
|
1691
|
+
const tapTree = input.contract.encode();
|
|
1664
1692
|
const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
1665
|
-
const { arkTx, checkpoints } = buildOffchainTx(
|
|
1693
|
+
const { arkTx: tx, checkpoints } = buildOffchainTx(
|
|
1666
1694
|
input.vtxos.map((vtxo) => ({
|
|
1667
1695
|
txid: vtxo.txid,
|
|
1668
1696
|
vout: vtxo.vout,
|
|
@@ -1671,14 +1699,14 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
1671
1699
|
tapTree
|
|
1672
1700
|
})),
|
|
1673
1701
|
[{ script: refundPkScript, amount: BigInt(amount) }],
|
|
1674
|
-
|
|
1702
|
+
operatorUnrollScript
|
|
1675
1703
|
);
|
|
1676
|
-
const
|
|
1677
|
-
const submitted = await
|
|
1678
|
-
base643.encode(
|
|
1704
|
+
const signedTx = await input.sender.sign(tx);
|
|
1705
|
+
const submitted = await operator.submitTx(
|
|
1706
|
+
base643.encode(signedTx.toPSBT()),
|
|
1679
1707
|
checkpoints.map((c) => base643.encode(c.toPSBT()))
|
|
1680
1708
|
);
|
|
1681
|
-
assertSubmittedArkTxid(submitted,
|
|
1709
|
+
assertSubmittedArkTxid(submitted, signedTx, "refundWithoutReceiver");
|
|
1682
1710
|
const matched = matchServerCheckpoints(
|
|
1683
1711
|
submitted.signedCheckpointTxs,
|
|
1684
1712
|
checkpoints,
|
|
@@ -1689,11 +1717,11 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
1689
1717
|
async ({ server }) => base643.encode((await input.sender.sign(server, [0])).toPSBT())
|
|
1690
1718
|
)
|
|
1691
1719
|
);
|
|
1692
|
-
await
|
|
1693
|
-
return {
|
|
1720
|
+
await operator.finalizeTx(submitted.arkTxid, finalCheckpoints);
|
|
1721
|
+
return { txid: submitted.arkTxid, amount };
|
|
1694
1722
|
}
|
|
1695
1723
|
var REFUND_MTP_LAG_SECONDS = 2 * 60 * 60;
|
|
1696
|
-
async function refundIfUnresolved(transport,
|
|
1724
|
+
async function refundIfUnresolved(transport, operator, indexer, input) {
|
|
1697
1725
|
const pollMs = input.pollMs ?? 5e3;
|
|
1698
1726
|
const now = input.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
1699
1727
|
const attemptDeadline = input.attemptDeadline ?? input.refundLocktime + REFUND_MTP_LAG_SECONDS;
|
|
@@ -1704,7 +1732,7 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
|
|
|
1704
1732
|
let fate = { fate: "unknown" };
|
|
1705
1733
|
try {
|
|
1706
1734
|
fate = await readLockupFate(indexer, {
|
|
1707
|
-
swapPkScript: input.
|
|
1735
|
+
swapPkScript: input.contract.pkScript,
|
|
1708
1736
|
paymentHash: input.paymentHash
|
|
1709
1737
|
});
|
|
1710
1738
|
} catch {
|
|
@@ -1716,11 +1744,11 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
|
|
|
1716
1744
|
status
|
|
1717
1745
|
};
|
|
1718
1746
|
}
|
|
1719
|
-
const vtxos = await findLockupVtxos(indexer, input.
|
|
1747
|
+
const vtxos = await findLockupVtxos(indexer, input.contract.pkScript);
|
|
1720
1748
|
if (vtxos.length === 0) return { outcome: "nothing_to_refund", status };
|
|
1721
1749
|
try {
|
|
1722
|
-
const pushed = await pushRefundWithoutReceiver(
|
|
1723
|
-
|
|
1750
|
+
const pushed = await pushRefundWithoutReceiver(operator, {
|
|
1751
|
+
contract: input.contract,
|
|
1724
1752
|
sender: input.sender,
|
|
1725
1753
|
vtxos,
|
|
1726
1754
|
refundPkScript: input.refundPkScript
|
|
@@ -1763,13 +1791,13 @@ var assertFiniteAmount = (value, reason, label) => {
|
|
|
1763
1791
|
error.reason = reason;
|
|
1764
1792
|
throw error;
|
|
1765
1793
|
};
|
|
1766
|
-
async function pushClaim(
|
|
1794
|
+
async function pushClaim(operator, input) {
|
|
1767
1795
|
if (input.vtxos.length === 0) throw new Error("nothing to claim: no funded outputs");
|
|
1768
1796
|
const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
|
|
1769
1797
|
if (swept.length > 0) {
|
|
1770
1798
|
throw new LockupNeedsRecoveryError(
|
|
1771
1799
|
swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
|
|
1772
|
-
input.
|
|
1800
|
+
input.contract.options.refundLocktime
|
|
1773
1801
|
);
|
|
1774
1802
|
}
|
|
1775
1803
|
const locked = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
@@ -1780,22 +1808,22 @@ async function pushClaim(ark, input) {
|
|
|
1780
1808
|
throw new LockupAmountMismatchError(input.expectedAmount, locked);
|
|
1781
1809
|
}
|
|
1782
1810
|
}
|
|
1783
|
-
const committed = input.
|
|
1811
|
+
const committed = input.contract.options.preimageHash;
|
|
1784
1812
|
if (hex8.encode(ripemd160(sha2563(input.preimage))) !== hex8.encode(committed)) {
|
|
1785
1813
|
throw new Error("preimage does not match the covenant's payment hash");
|
|
1786
1814
|
}
|
|
1787
|
-
const info = await
|
|
1788
|
-
let
|
|
1815
|
+
const info = await operator.getInfo();
|
|
1816
|
+
let operatorUnrollScript;
|
|
1789
1817
|
try {
|
|
1790
|
-
|
|
1818
|
+
operatorUnrollScript = CSVMultisigTapscript2.decode(hex8.decode(info.checkpointTapscript));
|
|
1791
1819
|
} catch {
|
|
1792
|
-
throw new Error("invalid checkpointTapscript from the
|
|
1820
|
+
throw new Error("invalid checkpointTapscript from the operator");
|
|
1793
1821
|
}
|
|
1794
|
-
const leaf = input.
|
|
1795
|
-
const tapTree = input.
|
|
1796
|
-
const
|
|
1822
|
+
const leaf = input.contract.claim();
|
|
1823
|
+
const tapTree = input.contract.encode();
|
|
1824
|
+
const txid = await signAndSubmitOffchainTx({
|
|
1797
1825
|
identity: claimWithPreimageIdentity(input.receiver, input.preimage),
|
|
1798
|
-
provider:
|
|
1826
|
+
provider: operator,
|
|
1799
1827
|
inputs: input.vtxos.map((vtxo) => ({
|
|
1800
1828
|
txid: vtxo.txid,
|
|
1801
1829
|
vout: vtxo.vout,
|
|
@@ -1806,10 +1834,10 @@ async function pushClaim(ark, input) {
|
|
|
1806
1834
|
// One aggregate output: unlike the covenant refund, this leaf inspects
|
|
1807
1835
|
// nothing about the output set.
|
|
1808
1836
|
outputs: [{ script: input.destinationPkScript, amount: BigInt(locked) }],
|
|
1809
|
-
serverUnrollScript,
|
|
1810
|
-
verifyServerSignatures: { serverPubkey: input.
|
|
1837
|
+
serverUnrollScript: operatorUnrollScript,
|
|
1838
|
+
verifyServerSignatures: { serverPubkey: input.contract.options.server }
|
|
1811
1839
|
});
|
|
1812
|
-
return {
|
|
1840
|
+
return { txid, amount: locked };
|
|
1813
1841
|
}
|
|
1814
1842
|
async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
|
|
1815
1843
|
const pollMs = options.pollMs ?? 5e3;
|
|
@@ -1824,13 +1852,13 @@ async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
|
|
|
1824
1852
|
await sleep2(pollMs);
|
|
1825
1853
|
}
|
|
1826
1854
|
}
|
|
1827
|
-
async function claimReceiveLockup(indexer,
|
|
1855
|
+
async function claimReceiveLockup(indexer, operator, input) {
|
|
1828
1856
|
const vtxos = await awaitLockupFunding(indexer, input.swapPkScript, {
|
|
1829
1857
|
pollMs: input.pollMs,
|
|
1830
1858
|
deadline: input.deadline
|
|
1831
1859
|
});
|
|
1832
|
-
return pushClaim(
|
|
1833
|
-
|
|
1860
|
+
return pushClaim(operator, {
|
|
1861
|
+
contract: input.contract,
|
|
1834
1862
|
receiver: input.receiver,
|
|
1835
1863
|
preimage: input.preimage,
|
|
1836
1864
|
vtxos,
|
|
@@ -1885,8 +1913,8 @@ async function senderIdentityForSwapRecord(wallet, record) {
|
|
|
1885
1913
|
// src/arkadeRefunder.ts
|
|
1886
1914
|
function arkadeRefunder(deps) {
|
|
1887
1915
|
return async (swap) => {
|
|
1888
|
-
const
|
|
1889
|
-
if (!
|
|
1916
|
+
const contract = swap.lockup?.script;
|
|
1917
|
+
if (!contract) {
|
|
1890
1918
|
throw new Error(
|
|
1891
1919
|
`swap ${swap.rfqId} carries no lockup covenant, so its refund cannot be built`
|
|
1892
1920
|
);
|
|
@@ -1901,7 +1929,7 @@ function arkadeRefunder(deps) {
|
|
|
1901
1929
|
);
|
|
1902
1930
|
}
|
|
1903
1931
|
const sender = await senderIdentityForSwapRecord(deps.wallet, rfqSignerOf(record) ?? {});
|
|
1904
|
-
return pushRefundWithoutReceiver(deps.
|
|
1932
|
+
return pushRefundWithoutReceiver(deps.operator, { contract, sender, vtxos });
|
|
1905
1933
|
};
|
|
1906
1934
|
}
|
|
1907
1935
|
|
|
@@ -2297,6 +2325,19 @@ var RfqSwapManager = class {
|
|
|
2297
2325
|
async getPendingSwaps() {
|
|
2298
2326
|
return [...this.monitored.values()];
|
|
2299
2327
|
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Every swap this manager holds — {@link getPendingSwaps} plus the ones
|
|
2330
|
+
* that already ended. The two sets are disjoint: a swap leaves `monitored`
|
|
2331
|
+
* as it enters `finished`.
|
|
2332
|
+
*
|
|
2333
|
+
* The finished half is what this process has seen, which after
|
|
2334
|
+
* {@link restoreFromRepository} is the stored history minus what retention
|
|
2335
|
+
* pruned. A manager that has restored nothing answers with the live swaps
|
|
2336
|
+
* alone.
|
|
2337
|
+
*/
|
|
2338
|
+
async getAllSwaps() {
|
|
2339
|
+
return [...this.monitored.values(), ...this.finished.values()];
|
|
2340
|
+
}
|
|
2300
2341
|
async hasSwap(rfqId) {
|
|
2301
2342
|
return this.monitored.has(rfqId);
|
|
2302
2343
|
}
|
|
@@ -2575,14 +2616,14 @@ var RfqSwapManager = class {
|
|
|
2575
2616
|
return this.claimIfFunded(swap, vtxos);
|
|
2576
2617
|
}
|
|
2577
2618
|
if (now < swap.refundLocktime + REFUND_MTP_LAG_SECONDS) {
|
|
2578
|
-
if (swap.
|
|
2619
|
+
if (swap.claimTxid) return;
|
|
2579
2620
|
return this.block(
|
|
2580
2621
|
swap,
|
|
2581
2622
|
"the claim window closed with the lockup unclaimed \u2014 only the solver can act now"
|
|
2582
2623
|
);
|
|
2583
2624
|
}
|
|
2584
2625
|
const failure = this.lastClaimError.get(swap.rfqId);
|
|
2585
|
-
if (failure && !swap.
|
|
2626
|
+
if (failure && !swap.claimTxid) {
|
|
2586
2627
|
return this.fail(swap, new Error(failure));
|
|
2587
2628
|
}
|
|
2588
2629
|
this.setState(swap, "refunded");
|
|
@@ -2597,7 +2638,7 @@ var RfqSwapManager = class {
|
|
|
2597
2638
|
*/
|
|
2598
2639
|
async claimIfFunded(swap, vtxos) {
|
|
2599
2640
|
if (vtxos.length === 0) return this.unblock(swap);
|
|
2600
|
-
const partiallyClaimed = swap.
|
|
2641
|
+
const partiallyClaimed = swap.claimTxid !== void 0;
|
|
2601
2642
|
if (partiallyClaimed && !this.hasUnclaimedOutpoint(swap.rfqId, vtxos)) {
|
|
2602
2643
|
return;
|
|
2603
2644
|
}
|
|
@@ -2625,10 +2666,10 @@ var RfqSwapManager = class {
|
|
|
2625
2666
|
this.setState(swap, "claimable");
|
|
2626
2667
|
if (!this.config.enableAutoActions) return;
|
|
2627
2668
|
try {
|
|
2628
|
-
const {
|
|
2669
|
+
const { txid } = await this.callbacks.claimLockup(swap, vtxos, { partiallyClaimed });
|
|
2629
2670
|
this.lastClaimError.delete(swap.rfqId);
|
|
2630
2671
|
this.rememberClaimed(swap.rfqId, vtxos);
|
|
2631
|
-
swap.
|
|
2672
|
+
swap.claimTxid = txid;
|
|
2632
2673
|
this.touch(swap);
|
|
2633
2674
|
this.setState(swap, "claimed");
|
|
2634
2675
|
this.emitAction(swap, "claimLockup");
|
|
@@ -2720,7 +2761,7 @@ var RfqSwapManager = class {
|
|
|
2720
2761
|
try {
|
|
2721
2762
|
const pushed = await this.callbacks.refundArkade(swap);
|
|
2722
2763
|
if (pushed) {
|
|
2723
|
-
swap.
|
|
2764
|
+
swap.refundTxid = pushed.txid;
|
|
2724
2765
|
this.touch(swap);
|
|
2725
2766
|
}
|
|
2726
2767
|
this.setState(swap, "refunded");
|
|
@@ -2793,7 +2834,7 @@ var RfqSwapManager = class {
|
|
|
2793
2834
|
/**
|
|
2794
2835
|
* Record which ark transactions ended the lockup.
|
|
2795
2836
|
*
|
|
2796
|
-
* Only the ones the indexer actually named: `LockupSpend.
|
|
2837
|
+
* Only the ones the indexer actually named: `LockupSpend.txid` is
|
|
2797
2838
|
* optional, and a checkpoint txid is not what history correlates on — a
|
|
2798
2839
|
* record carrying one would name a transaction the wallet's own activity
|
|
2799
2840
|
* never shows. Fewer txids is the right failure here.
|
|
@@ -2803,9 +2844,9 @@ var RfqSwapManager = class {
|
|
|
2803
2844
|
* verdict once.
|
|
2804
2845
|
*/
|
|
2805
2846
|
stampLockupSpends(swap, spends) {
|
|
2806
|
-
const
|
|
2807
|
-
if (
|
|
2808
|
-
swap.
|
|
2847
|
+
const txids = spends.map((spend) => spend.txid).filter((txid) => txid !== void 0);
|
|
2848
|
+
if (txids.length === 0) return;
|
|
2849
|
+
swap.lockupSpendTxids = txids;
|
|
2809
2850
|
this.touch(swap);
|
|
2810
2851
|
}
|
|
2811
2852
|
touch(swap) {
|
|
@@ -2886,10 +2927,9 @@ var RfqSwapManager = class {
|
|
|
2886
2927
|
/**
|
|
2887
2928
|
* Drop a terminal swap from monitoring and report it exactly once.
|
|
2888
2929
|
*
|
|
2889
|
-
* `onSwapCompleted` and `onSwapFailed` are mutually exclusive here
|
|
2890
|
-
*
|
|
2891
|
-
*
|
|
2892
|
-
* also fires on failure is a trap worth not inheriting.
|
|
2930
|
+
* `onSwapCompleted` and `onSwapFailed` are mutually exclusive here: a
|
|
2931
|
+
* listener named "completed" that also fires on failure is a trap, so a
|
|
2932
|
+
* swap that leaves monitoring reports through exactly one of them.
|
|
2893
2933
|
*/
|
|
2894
2934
|
finalize(swap) {
|
|
2895
2935
|
if (!this.monitored.has(swap.rfqId)) return;
|
|
@@ -2925,7 +2965,7 @@ var traderClaimTxid = (swap) => {
|
|
|
2925
2965
|
case "onchain_send":
|
|
2926
2966
|
return swap.claimTxid;
|
|
2927
2967
|
case "lightning_receive":
|
|
2928
|
-
return swap.
|
|
2968
|
+
return swap.claimTxid;
|
|
2929
2969
|
default:
|
|
2930
2970
|
return void 0;
|
|
2931
2971
|
}
|
|
@@ -2935,7 +2975,7 @@ var outcomeOf = (swap) => {
|
|
|
2935
2975
|
const lostReceive = swap.kind === "lightning_receive" && swap.state === "refunded";
|
|
2936
2976
|
return {
|
|
2937
2977
|
state: swap.state,
|
|
2938
|
-
txid: lostReceive ? swap.
|
|
2978
|
+
txid: lostReceive ? swap.refundTxid : traderClaimTxid(swap) ?? swap.refundTxid
|
|
2939
2979
|
};
|
|
2940
2980
|
};
|
|
2941
2981
|
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -3002,16 +3042,17 @@ async function rfqSwapActivityInputs(deps) {
|
|
|
3002
3042
|
const records = await deps.repository.getAllRfqSwaps();
|
|
3003
3043
|
return Promise.all(records.map((record) => activityInputOf(record, deps.indexer)));
|
|
3004
3044
|
}
|
|
3005
|
-
async function activityInputOf(
|
|
3045
|
+
async function activityInputOf(stored, indexer) {
|
|
3046
|
+
const record = normalizeRfqSwapRecord(stored);
|
|
3006
3047
|
const txids = /* @__PURE__ */ new Set();
|
|
3007
|
-
if (record.
|
|
3008
|
-
if (record.
|
|
3048
|
+
if (record.fundingTxid) txids.add(record.fundingTxid);
|
|
3049
|
+
if (record.refundTxid) txids.add(record.refundTxid);
|
|
3009
3050
|
const handler = rfqCorridorHandlers.getOrThrow(record.kind);
|
|
3010
3051
|
for (const txid of handler.activityTxids?.(record.profile) ?? []) txids.add(txid);
|
|
3011
|
-
for (const txid of record.
|
|
3012
|
-
const spendUnknown = isRfqSwapTerminal(record.state) && !record.
|
|
3013
|
-
if (indexer && (!record.
|
|
3014
|
-
for (const txid of await lockupTxids(indexer, record, !record.
|
|
3052
|
+
for (const txid of record.lockupSpendTxids ?? []) txids.add(txid);
|
|
3053
|
+
const spendUnknown = isRfqSwapTerminal(record.state) && !record.refundTxid && !record.lockupSpendTxids?.length;
|
|
3054
|
+
if (indexer && (!record.fundingTxid || spendUnknown)) {
|
|
3055
|
+
for (const txid of await lockupTxids(indexer, record, !record.fundingTxid)) {
|
|
3015
3056
|
txids.add(txid);
|
|
3016
3057
|
}
|
|
3017
3058
|
}
|
|
@@ -3036,6 +3077,334 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
3036
3077
|
return [];
|
|
3037
3078
|
}
|
|
3038
3079
|
}
|
|
3080
|
+
|
|
3081
|
+
// src/swapClient.ts
|
|
3082
|
+
import { hex as hex11 } from "@scure/base";
|
|
3083
|
+
import { ArkAddress as ArkAddress5, asset as asset2, contractSigner as contractSigner2 } from "@arkade-os/sdk";
|
|
3084
|
+
import { quoteOffer } from "@arkade-os/solver-discovery";
|
|
3085
|
+
var sideCorridorsOf = (market) => {
|
|
3086
|
+
const read = (value) => value === "lightning" || value === "onchain" ? value : "arkade";
|
|
3087
|
+
const m = market;
|
|
3088
|
+
return { base: read(m.base_corridor), quote: read(m.quote_corridor) };
|
|
3089
|
+
};
|
|
3090
|
+
var resolveKind = (market, give) => {
|
|
3091
|
+
const corridors = sideCorridorsOf(market);
|
|
3092
|
+
const giveCorridor = corridors[give];
|
|
3093
|
+
const receiveCorridor = corridors[give === "base" ? "quote" : "base"];
|
|
3094
|
+
if (giveCorridor === "arkade" && receiveCorridor === "arkade") return "spot";
|
|
3095
|
+
if (receiveCorridor === "lightning") return "ln_send";
|
|
3096
|
+
if (giveCorridor === "lightning") return "ln_receive";
|
|
3097
|
+
if (receiveCorridor === "onchain") return "onchain_send";
|
|
3098
|
+
return "onchain_receive";
|
|
3099
|
+
};
|
|
3100
|
+
var need = (value, what, leg) => {
|
|
3101
|
+
if (value === void 0) throw new Error(`a ${leg} quote needs ${what}`);
|
|
3102
|
+
return value;
|
|
3103
|
+
};
|
|
3104
|
+
var corridorAmount = (input, leg) => {
|
|
3105
|
+
const raw = need(input.amount, "an amount", leg);
|
|
3106
|
+
const amountOn = need(input.amountOn, "amountOn ('give' or 'receive')", leg);
|
|
3107
|
+
const amount = Number(raw);
|
|
3108
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
3109
|
+
throw new Error(`a ${leg} amount must be a positive integer of sats, got ${String(raw)}`);
|
|
3110
|
+
}
|
|
3111
|
+
return { amount, amountSide: amountOn === "give" ? "from" : "to" };
|
|
3112
|
+
};
|
|
3113
|
+
function createSwapClient(deps) {
|
|
3114
|
+
const { wallet, repository, transportFor } = deps;
|
|
3115
|
+
let broadcaster;
|
|
3116
|
+
const broadcasting = () => broadcaster ??= wallet.getArkadeBroadcaster();
|
|
3117
|
+
const ark = deps.ark ?? {
|
|
3118
|
+
getInfo: () => wallet.getArkadeInfo({ requireLive: true }),
|
|
3119
|
+
submitTx: async (...args) => (await broadcasting()).submitTx(...args),
|
|
3120
|
+
finalizeTx: async (...args) => (await broadcasting()).finalizeTx(...args)
|
|
3121
|
+
};
|
|
3122
|
+
let reader;
|
|
3123
|
+
const reading = () => reader ??= wallet.getArkadeReader();
|
|
3124
|
+
const indexer = deps.indexer ?? {
|
|
3125
|
+
getVtxos: async (opts) => {
|
|
3126
|
+
if (!opts)
|
|
3127
|
+
throw new Error("getVtxos on the swap indexer requires scripts or outpoints");
|
|
3128
|
+
return (await reading()).getVtxos(opts);
|
|
3129
|
+
},
|
|
3130
|
+
getVirtualTxs: async (...args) => (await reading()).getVirtualTxs(...args)
|
|
3131
|
+
};
|
|
3132
|
+
const managerDeps = { indexer, chain: deps.chain, repository };
|
|
3133
|
+
const manager = new RfqSwapManager(managerDeps);
|
|
3134
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3135
|
+
const notify2 = (swap) => {
|
|
3136
|
+
for (const listener of listeners) {
|
|
3137
|
+
try {
|
|
3138
|
+
listener(swap);
|
|
3139
|
+
} catch {
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
manager.onSwapUpdate((swap) => notify2({ family: "rfq", swap }));
|
|
3144
|
+
const claimLockup = async (swap, vtxos, { partiallyClaimed }) => {
|
|
3145
|
+
const record = await repository.getRfqSwap(swap.rfqId);
|
|
3146
|
+
if (!record) throw new Error(`rfq swap ${swap.rfqId} has no stored record to claim from`);
|
|
3147
|
+
const secret = rfqClaimSecretOf(record);
|
|
3148
|
+
if (!secret) throw new Error(`rfq swap ${swap.rfqId} carries no claim secret`);
|
|
3149
|
+
const script = swap.lockup?.script;
|
|
3150
|
+
if (!script) throw new Error(`rfq swap ${swap.rfqId} carries no lockup covenant`);
|
|
3151
|
+
const payoutAddress = record.profile.payoutAddress;
|
|
3152
|
+
if (!payoutAddress) throw new Error(`rfq swap ${swap.rfqId} carries no payoutAddress`);
|
|
3153
|
+
return pushClaim(ark, {
|
|
3154
|
+
contract: script,
|
|
3155
|
+
receiver: await contractSigner2(wallet, secret.signingDescriptor),
|
|
3156
|
+
preimage: await preimageForSwapRecord(wallet, secret),
|
|
3157
|
+
vtxos,
|
|
3158
|
+
destinationPkScript: ArkAddress5.decode(payoutAddress).pkScript,
|
|
3159
|
+
expectedAmount: swap.expectedAmount,
|
|
3160
|
+
partiallyClaimed
|
|
3161
|
+
});
|
|
3162
|
+
};
|
|
3163
|
+
manager.setCallbacks({
|
|
3164
|
+
refundArkade: arkadeRefunder({ operator: ark, indexer, wallet, repository }),
|
|
3165
|
+
claimLockup,
|
|
3166
|
+
...deps.claimOnchain ? { claimOnchain: deps.claimOnchain } : {}
|
|
3167
|
+
});
|
|
3168
|
+
let watcher;
|
|
3169
|
+
const quote = async (market, input) => {
|
|
3170
|
+
const kind = resolveKind(market, input.give);
|
|
3171
|
+
switch (kind) {
|
|
3172
|
+
case "spot": {
|
|
3173
|
+
const raw = need(input.amount, "an amount", kind);
|
|
3174
|
+
const amountOn = need(input.amountOn, "amountOn ('give' or 'receive')", kind);
|
|
3175
|
+
const plan = await quoteOffer(market, {
|
|
3176
|
+
give: input.give,
|
|
3177
|
+
...amountOn === "give" ? { giveAmount: raw } : { wantAmount: raw },
|
|
3178
|
+
fetchImpl: deps.discovery.fetchImpl
|
|
3179
|
+
});
|
|
3180
|
+
return { kind, market, plan };
|
|
3181
|
+
}
|
|
3182
|
+
case "ln_send": {
|
|
3183
|
+
const invoice = need(input.invoice, "the invoice to pay", kind);
|
|
3184
|
+
const request = await requestLightningSend(wallet, transportFor(market), {
|
|
3185
|
+
invoice,
|
|
3186
|
+
emulatorPubkey: deps.emulatorPubkey
|
|
3187
|
+
});
|
|
3188
|
+
return { kind, market, request };
|
|
3189
|
+
}
|
|
3190
|
+
case "ln_receive": {
|
|
3191
|
+
const params = {
|
|
3192
|
+
...corridorAmount(input, kind),
|
|
3193
|
+
covclaimdPubkey: need(deps.covclaimdPubkey, "deps.covclaimdPubkey", kind),
|
|
3194
|
+
decodeInvoice: need(deps.decodeInvoice, "deps.decodeInvoice", kind),
|
|
3195
|
+
maxPayAmount: input.maxPayAmount,
|
|
3196
|
+
emulatorPubkey: deps.emulatorPubkey
|
|
3197
|
+
};
|
|
3198
|
+
const request = await requestLightningReceive(wallet, transportFor(market), params);
|
|
3199
|
+
return { kind, market, request, invoice: request.invoice };
|
|
3200
|
+
}
|
|
3201
|
+
case "onchain_send": {
|
|
3202
|
+
need(deps.chain, "deps.chain (L1 access)", kind);
|
|
3203
|
+
const params = {
|
|
3204
|
+
...corridorAmount(input, kind),
|
|
3205
|
+
payoutPubkey: need(input.payoutPubkey, "a payoutPubkey", kind),
|
|
3206
|
+
preimage: input.preimage,
|
|
3207
|
+
emulatorPubkey: deps.emulatorPubkey
|
|
3208
|
+
};
|
|
3209
|
+
const request = await requestOnchainSend(wallet, transportFor(market), params);
|
|
3210
|
+
return { kind, market, request };
|
|
3211
|
+
}
|
|
3212
|
+
case "onchain_receive":
|
|
3213
|
+
throw new Error(
|
|
3214
|
+
"onchain->arkade is not driven by RfqSwapManager yet; quote it directly with requestOnchainReceive"
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
};
|
|
3218
|
+
const admit = async (swap, origin) => {
|
|
3219
|
+
let announced = false;
|
|
3220
|
+
const heard = manager.onSwapUpdate((updated) => {
|
|
3221
|
+
if (updated.rfqId === swap.rfqId) announced = true;
|
|
3222
|
+
});
|
|
3223
|
+
try {
|
|
3224
|
+
await manager.addSwap(swap, origin);
|
|
3225
|
+
} finally {
|
|
3226
|
+
heard();
|
|
3227
|
+
}
|
|
3228
|
+
const unified = { family: "rfq", swap };
|
|
3229
|
+
if (!announced) notify2(unified);
|
|
3230
|
+
return unified;
|
|
3231
|
+
};
|
|
3232
|
+
const fundPersisted = async (swap, origin, funding) => {
|
|
3233
|
+
await repository.saveRfqSwap(createRfqSwapRecord(origin, swap));
|
|
3234
|
+
return wallet.send({ address: funding.address, amount: funding.amount });
|
|
3235
|
+
};
|
|
3236
|
+
const accept = async (accepted) => {
|
|
3237
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3238
|
+
switch (accepted.kind) {
|
|
3239
|
+
case "spot": {
|
|
3240
|
+
const { plan } = accepted;
|
|
3241
|
+
const depositIsBtc = plan.deposit.asset.id === BTC_ASSET_ID;
|
|
3242
|
+
const offer = await createOffer(wallet, {
|
|
3243
|
+
wantAmount: plan.receive.atomic,
|
|
3244
|
+
...plan.receive.asset.id === BTC_ASSET_ID ? { offerAsset: asset2.AssetId.fromString(plan.deposit.asset.id) } : { wantAsset: asset2.AssetId.fromString(plan.receive.asset.id) },
|
|
3245
|
+
emulatorPubkey: deps.emulatorPubkey
|
|
3246
|
+
});
|
|
3247
|
+
const txid = await wallet.send({
|
|
3248
|
+
address: offer.address,
|
|
3249
|
+
// asset deposits ride the SDK's dust-sat carrier
|
|
3250
|
+
amount: depositIsBtc ? Number(plan.deposit.atomic) : void 0,
|
|
3251
|
+
assets: depositIsBtc ? void 0 : [{ assetId: plan.deposit.asset.id, amount: plan.deposit.atomic }],
|
|
3252
|
+
extensions: [offer.extension]
|
|
3253
|
+
});
|
|
3254
|
+
const swap = {
|
|
3255
|
+
id: txid,
|
|
3256
|
+
fromAsset: plan.deposit.asset.id,
|
|
3257
|
+
toAsset: plan.receive.asset.id,
|
|
3258
|
+
fromAmount: plan.deposit.atomic.toString(),
|
|
3259
|
+
toAmount: plan.receive.atomic.toString(),
|
|
3260
|
+
swapAddress: offer.address,
|
|
3261
|
+
swapPkScript: hex11.encode(offer.swapPkScript),
|
|
3262
|
+
offerHex: offer.offerHex,
|
|
3263
|
+
fundingTxid: txid,
|
|
3264
|
+
status: "pending",
|
|
3265
|
+
createdAt: Date.now()
|
|
3266
|
+
};
|
|
3267
|
+
await addAssetSwap(repository, swap);
|
|
3268
|
+
const unified = { family: "offer", swap };
|
|
3269
|
+
notify2(unified);
|
|
3270
|
+
return unified;
|
|
3271
|
+
}
|
|
3272
|
+
case "ln_send": {
|
|
3273
|
+
const { request } = accepted;
|
|
3274
|
+
const swap = {
|
|
3275
|
+
kind: "lightning_send",
|
|
3276
|
+
rfqId: request.rfqId,
|
|
3277
|
+
state: "pending",
|
|
3278
|
+
lockupPkScript: request.swapPkScript,
|
|
3279
|
+
lockup: { script: request.script, address: request.address },
|
|
3280
|
+
// the trader's own decode, never the solver's echo
|
|
3281
|
+
paymentHash: request.contractParams.paymentHash,
|
|
3282
|
+
refundLocktime: request.contractParams.refundLocktime,
|
|
3283
|
+
createdAt: now,
|
|
3284
|
+
updatedAt: now
|
|
3285
|
+
};
|
|
3286
|
+
const origin = {
|
|
3287
|
+
kind: "lightning_send",
|
|
3288
|
+
lockupAddress: request.address,
|
|
3289
|
+
profile: rfqSecretsProfile(request.secrets, swap.paymentHash),
|
|
3290
|
+
amount: request.fundAmount
|
|
3291
|
+
};
|
|
3292
|
+
const txid = await fundPersisted(swap, origin, {
|
|
3293
|
+
address: request.address,
|
|
3294
|
+
amount: request.fundAmount
|
|
3295
|
+
});
|
|
3296
|
+
return admit(swap, { ...origin, fundingTxid: txid });
|
|
3297
|
+
}
|
|
3298
|
+
case "ln_receive": {
|
|
3299
|
+
const { request } = accepted;
|
|
3300
|
+
const paymentHash = hex11.encode(request.secrets.paymentHash);
|
|
3301
|
+
const swap = {
|
|
3302
|
+
kind: "lightning_receive",
|
|
3303
|
+
rfqId: request.rfqId,
|
|
3304
|
+
state: "pending",
|
|
3305
|
+
lockupPkScript: request.swapPkScript,
|
|
3306
|
+
lockup: { script: request.script, address: request.address },
|
|
3307
|
+
paymentHash,
|
|
3308
|
+
refundLocktime: request.contractParams.refundLocktime,
|
|
3309
|
+
expectedAmount: request.expectedAmount,
|
|
3310
|
+
createdAt: now,
|
|
3311
|
+
updatedAt: now
|
|
3312
|
+
};
|
|
3313
|
+
return admit(swap, {
|
|
3314
|
+
kind: "lightning_receive",
|
|
3315
|
+
lockupAddress: request.address,
|
|
3316
|
+
profile: {
|
|
3317
|
+
...rfqSecretsProfile(request.secrets, paymentHash),
|
|
3318
|
+
expectedAmount: request.expectedAmount,
|
|
3319
|
+
payoutAddress: request.payoutAddress
|
|
3320
|
+
},
|
|
3321
|
+
amount: request.expectedAmount
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
case "onchain_send": {
|
|
3325
|
+
const { request } = accepted;
|
|
3326
|
+
const paymentHash = hex11.encode(request.secrets.paymentHash);
|
|
3327
|
+
const swap = {
|
|
3328
|
+
kind: "onchain_send",
|
|
3329
|
+
rfqId: request.rfqId,
|
|
3330
|
+
state: "pending",
|
|
3331
|
+
lockupPkScript: request.swapPkScript,
|
|
3332
|
+
lockup: { script: request.script, address: request.address },
|
|
3333
|
+
paymentHash,
|
|
3334
|
+
refundLocktime: request.refundLocktime,
|
|
3335
|
+
htlc: request.htlc,
|
|
3336
|
+
minConfirmations: request.minConfirmations,
|
|
3337
|
+
createdAt: now,
|
|
3338
|
+
updatedAt: now
|
|
3339
|
+
};
|
|
3340
|
+
const origin = {
|
|
3341
|
+
kind: "onchain_send",
|
|
3342
|
+
lockupAddress: request.address,
|
|
3343
|
+
profile: {
|
|
3344
|
+
...rfqSecretsProfile(request.secrets, paymentHash),
|
|
3345
|
+
...onchainSendProfile(request)
|
|
3346
|
+
},
|
|
3347
|
+
amount: request.fundAmount
|
|
3348
|
+
};
|
|
3349
|
+
const txid = await fundPersisted(swap, origin, {
|
|
3350
|
+
address: request.address,
|
|
3351
|
+
amount: request.fundAmount
|
|
3352
|
+
});
|
|
3353
|
+
return admit(swap, { ...origin, fundingTxid: txid });
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
return {
|
|
3358
|
+
manager,
|
|
3359
|
+
markets: (useCache) => discoverMarkets({ ...deps.discovery, repository, useCache }),
|
|
3360
|
+
quote,
|
|
3361
|
+
accept,
|
|
3362
|
+
cancel: async (fundingTxid) => {
|
|
3363
|
+
const swaps = await getAssetSwapsOrThrow(repository);
|
|
3364
|
+
const swap = swaps.find((s) => s.id === fundingTxid);
|
|
3365
|
+
if (!swap) throw new Error(`no offer swap with funding txid ${fundingTxid}`);
|
|
3366
|
+
if (swap.status !== "pending") {
|
|
3367
|
+
throw new Error(`offer swap ${fundingTxid} is ${swap.status}, not cancellable`);
|
|
3368
|
+
}
|
|
3369
|
+
await updateAssetSwap(repository, fundingTxid, { status: "cancelling" });
|
|
3370
|
+
await cancelOffer(wallet, swap.offerHex, {
|
|
3371
|
+
repository,
|
|
3372
|
+
fundingTxid
|
|
3373
|
+
});
|
|
3374
|
+
},
|
|
3375
|
+
// Both families, live AND terminal — the offer half reads the whole
|
|
3376
|
+
// stored history, and the RFQ half is every swap the manager holds.
|
|
3377
|
+
// Its history is what `start()` restored plus what this session
|
|
3378
|
+
// accepted; records past `RFQ_SWAP_RETENTION_SECONDS` are pruned.
|
|
3379
|
+
swaps: async () => {
|
|
3380
|
+
const offers = await getAssetSwaps(repository);
|
|
3381
|
+
const rfq = await manager.getAllSwaps();
|
|
3382
|
+
return [
|
|
3383
|
+
...offers.map((swap) => ({ family: "offer", swap })),
|
|
3384
|
+
...rfq.map((swap) => ({ family: "rfq", swap }))
|
|
3385
|
+
];
|
|
3386
|
+
},
|
|
3387
|
+
onUpdate: (listener) => {
|
|
3388
|
+
listeners.add(listener);
|
|
3389
|
+
return () => listeners.delete(listener);
|
|
3390
|
+
},
|
|
3391
|
+
start: async () => {
|
|
3392
|
+
managerDeps.contracts ??= await wallet.getContractManager();
|
|
3393
|
+
await manager.restoreFromRepository();
|
|
3394
|
+
await manager.start();
|
|
3395
|
+
watcher ??= await watchOfferSwaps({
|
|
3396
|
+
wallet,
|
|
3397
|
+
repository,
|
|
3398
|
+
onUpdate: (swap) => notify2({ family: "offer", swap })
|
|
3399
|
+
});
|
|
3400
|
+
},
|
|
3401
|
+
stop: async () => {
|
|
3402
|
+
await manager.stop();
|
|
3403
|
+
watcher?.stop();
|
|
3404
|
+
watcher = void 0;
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3039
3408
|
export {
|
|
3040
3409
|
ARKADE_ASSET,
|
|
3041
3410
|
ARKADE_BTC,
|
|
@@ -3096,6 +3465,7 @@ export {
|
|
|
3096
3465
|
classifySpend,
|
|
3097
3466
|
createOffer,
|
|
3098
3467
|
createRfqSwapRecord,
|
|
3468
|
+
createSwapClient,
|
|
3099
3469
|
decodeOffer,
|
|
3100
3470
|
deriveLightningReceive,
|
|
3101
3471
|
deriveOnchainReceive,
|
|
@@ -3110,16 +3480,18 @@ export {
|
|
|
3110
3480
|
httpTransport,
|
|
3111
3481
|
isRfqSwapTerminal,
|
|
3112
3482
|
isRfqTerminal,
|
|
3483
|
+
lightningReceiveContract,
|
|
3113
3484
|
lightningReceiveRequest,
|
|
3485
|
+
lightningSendContract,
|
|
3114
3486
|
lightningSendRequest,
|
|
3115
|
-
lightningSendVtxoScript,
|
|
3116
3487
|
lockupContractParams,
|
|
3117
3488
|
makeCachedFeedFetch,
|
|
3118
3489
|
newPreimage,
|
|
3119
3490
|
newRfqId,
|
|
3120
3491
|
nextOnchainAction,
|
|
3492
|
+
normalizeRfqSwapRecord,
|
|
3493
|
+
offerContract,
|
|
3121
3494
|
offerTermsFromQuote,
|
|
3122
|
-
offerVtxoScript,
|
|
3123
3495
|
onchainHtlcScript,
|
|
3124
3496
|
onchainReceiveRequest,
|
|
3125
3497
|
onchainSendProfile,
|
|
@@ -3130,7 +3502,6 @@ export {
|
|
|
3130
3502
|
pushRefundWithoutReceiver,
|
|
3131
3503
|
readLockupFate,
|
|
3132
3504
|
rebuildRfqSwap,
|
|
3133
|
-
receiveVtxoScript,
|
|
3134
3505
|
refundIfUnresolved,
|
|
3135
3506
|
registerLockupContract,
|
|
3136
3507
|
relayTransport,
|