@arkade-os/swap 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ARKADE_ASSET,
3
3
  ARKADE_BTC,
4
+ ASSET_CARRIER_SATS,
4
5
  AddressMismatch,
6
+ BTC_ASSET_ID,
5
7
  CLAIM_PACKET_TYPE,
6
8
  L1_NETWORKS,
7
9
  LIGHTNING_BTC,
@@ -13,6 +15,8 @@ import {
13
15
  MAX_MIN_CONFIRMATIONS,
14
16
  MIN_CLAIM_WINDOW_SECONDS,
15
17
  MIN_HEADROOM_SECONDS,
18
+ OFFER_CONTRACT_KIND,
19
+ OFFER_PACKET_TYPE,
16
20
  ONCHAIN_BTC,
17
21
  ONCHAIN_CLAIM_MARGIN_SECONDS,
18
22
  ONCHAIN_DUST_SATS,
@@ -20,6 +24,8 @@ import {
20
24
  ONCHAIN_RECEIVE_PAIR,
21
25
  ONCHAIN_SECONDS_PER_BLOCK,
22
26
  ONCHAIN_SEND_PAIR,
27
+ PreimageNotRecoverableError,
28
+ RETIRABLE,
23
29
  RFQ_REFUSAL_ERROR_CODES,
24
30
  RFQ_TERMINAL_STATES,
25
31
  SEALED_CIPHERTEXT_LENGTH,
@@ -28,21 +34,31 @@ import {
28
34
  SWAP_LOCKUP_CONTRACT_LABEL,
29
35
  SWAP_LOCKUP_CONTRACT_TYPE,
30
36
  SwapRefusal,
37
+ addAssetSwap,
31
38
  appendArkadeScript,
32
39
  arkadeAssetLeg,
33
40
  arkadeSwapRequest,
41
+ assertArkadeFundable,
34
42
  assertFundable,
35
43
  assertReceivable,
36
44
  awaitOnchainFill,
37
45
  buildHtlcClaim,
38
46
  buildHtlcRefund,
47
+ cancelOffer,
48
+ canonicalAssetAmount,
39
49
  claimOnchainFill,
40
50
  claimPacketShape,
41
51
  classifyOnchainHtlc,
52
+ createOffer,
53
+ decodeOffer,
42
54
  deriveLightningReceive,
43
55
  deriveOnchainReceive,
44
56
  deriveOnchainSend,
57
+ encodeOffer,
45
58
  extractPreimage,
59
+ fillOffer,
60
+ getAssetSwaps,
61
+ getAssetSwapsOrThrow,
46
62
  httpTransport,
47
63
  isRfqRefusalErrorCode,
48
64
  l1ScriptForAddress,
@@ -52,806 +68,51 @@ import {
52
68
  lockupContractParams,
53
69
  newPreimage,
54
70
  newRfqId,
71
+ normalizeMakerPkScript,
72
+ normalizeMakerPublicKey,
55
73
  offerTermsFromQuote,
74
+ offerVtxoScript,
56
75
  onchainHtlcScript,
57
76
  onchainReceiveRequest,
58
77
  onchainSendRequest,
59
78
  paymentHashOf,
79
+ preimageForSwapRecord,
60
80
  receiveVtxoScript,
61
81
  registerLockupContract,
62
82
  relayTransport,
83
+ requestArkadeSwap,
63
84
  requestLightningReceive,
64
85
  requestLightningSend,
65
86
  requestOnchainReceive,
66
87
  requestOnchainSend,
88
+ restoreOfferCoverage,
89
+ retireOfferContract,
90
+ retireSettledOfferContracts,
67
91
  rfqPair,
68
92
  sealClaimPacket,
93
+ swapPrograms,
94
+ swapSecretsToRecord,
69
95
  unilateralClaimDelay,
70
96
  unilateralRefundDelay,
71
97
  unilateralRefundWithoutReceiverDelay,
98
+ updateAssetSwap,
99
+ updateAssetSwapBestEffort,
72
100
  verifyLockupAddress,
101
+ verifyOfferAddress,
73
102
  verifyReceiveInvoice
74
- } from "./chunk-N3W6LXH6.js";
103
+ } from "./chunk-QJKMTPRQ.js";
75
104
  import {
76
105
  InMemoryAssetSwapRepository,
77
106
  marketsCacheKey
78
107
  } from "./chunk-6ZUS47GA.js";
79
108
 
80
- // src/offer.ts
81
- import { hex as hex2 } from "@scure/base";
82
- import { concatBytes } from "@scure/btc-signer/utils.js";
83
- import {
84
- ASSET_CARRIER_SATS as SDK_ASSET_CARRIER_SATS,
85
- ArkAddress,
86
- RestArkProvider,
87
- RestEmulatorProvider,
88
- RestIndexerProvider,
89
- arkade,
90
- asset,
91
- getNetwork,
92
- resolveEmulatorPubkey,
93
- toXOnlySignerHex
94
- } from "@arkade-os/sdk";
95
-
96
- // src/swap-want-asset.program.json
97
- var swap_want_asset_program_default = {
98
- version: 0,
99
- name: "banco-btc-to-asset",
100
- params: [
101
- { name: "makerWP", type: "pubkey" },
102
- { name: "wantAmount", type: "int" },
103
- { name: "wantAssetTxid", type: "hash" },
104
- { name: "wantAssetGroupIndex", type: "int" },
105
- { name: "server", type: "pubkey" },
106
- { name: "user", type: "pubkey" }
107
- ],
108
- functions: {
109
- fulfill: {
110
- tapscript: { signers: ["$server"] },
111
- arkadeScript: {
112
- asm: [
113
- 0,
114
- "$wantAssetTxid",
115
- "$wantAssetGroupIndex",
116
- "INSPECTOUTASSETLOOKUP",
117
- "VERIFY",
118
- "$wantAmount",
119
- "GREATERTHANOREQUAL",
120
- "VERIFY",
121
- 0,
122
- "INSPECTOUTPUTSCRIPTPUBKEY",
123
- 1,
124
- "EQUALVERIFY",
125
- "$makerWP",
126
- "EQUAL"
127
- ]
128
- }
129
- },
130
- cancel: {
131
- tapscript: { signers: ["$user", "$server"] }
132
- }
133
- }
134
- };
135
-
136
- // src/swap-want-btc.program.json
137
- var swap_want_btc_program_default = {
138
- version: 0,
139
- name: "banco-asset-to-btc",
140
- params: [
141
- { name: "makerWP", type: "pubkey" },
142
- { name: "wantAmount", type: "int" },
143
- { name: "server", type: "pubkey" },
144
- { name: "user", type: "pubkey" }
145
- ],
146
- functions: {
147
- fulfill: {
148
- tapscript: { signers: ["$server"] },
149
- arkadeScript: {
150
- asm: [
151
- 0,
152
- "INSPECTOUTPUTVALUE",
153
- "$wantAmount",
154
- "GREATERTHANOREQUAL",
155
- "VERIFY",
156
- 0,
157
- "INSPECTOUTPUTSCRIPTPUBKEY",
158
- 1,
159
- "EQUALVERIFY",
160
- "$makerWP",
161
- "EQUAL"
162
- ]
163
- }
164
- },
165
- cancel: {
166
- tapscript: { signers: ["$user", "$server"] }
167
- }
168
- }
169
- };
170
-
171
- // src/coverage.ts
172
- var RETIRABLE = ["fulfilled", "cancelled"];
173
- var issuedAt = /* @__PURE__ */ new Map();
174
- var inFlight = /* @__PURE__ */ new Map();
175
- async function serialize(script, task) {
176
- const previous = inFlight.get(script) ?? Promise.resolve();
177
- const result = previous.then(task, task);
178
- const settled = result.then(
179
- () => {
180
- },
181
- () => {
182
- }
183
- );
184
- inFlight.set(script, settled);
185
- void settled.then(() => {
186
- if (inFlight.get(script) === settled) inFlight.delete(script);
187
- });
188
- return result;
189
- }
190
- function addressOutstanding(swaps, script) {
191
- const issued = issuedAt.get(script);
192
- if (issued === void 0) return false;
193
- if (swaps.some((s) => s.swapPkScript === script && s.createdAt >= issued)) {
194
- issuedAt.delete(script);
195
- return false;
196
- }
197
- return true;
198
- }
199
- async function promoteOfferContract(manager, script, issued = Date.now()) {
200
- await serialize(script, async () => {
201
- await manager.setContractWatchState(script, "watched");
202
- issuedAt.set(script, issued);
203
- });
204
- }
205
- async function retireOfferContract(manager, swaps, script) {
206
- await serialize(script, async () => {
207
- if (swaps.some((s) => s.swapPkScript === script && !RETIRABLE.includes(s.status))) return;
208
- if (addressOutstanding(swaps, script)) return;
209
- try {
210
- await manager.setContractWatchState(script, "retained");
211
- } catch (err) {
212
- console.warn(`[swap] could not retire offer contract ${script}`, err);
213
- }
214
- });
215
- }
216
- async function retireSettledOfferContracts(manager, swaps) {
217
- const settled = new Set(
218
- swaps.filter((s) => RETIRABLE.includes(s.status)).map((s) => s.swapPkScript)
219
- );
220
- for (const script of settled) await retireOfferContract(manager, swaps, script);
221
- }
222
-
223
- // src/store.ts
224
- import { hex } from "@scure/base";
225
- import { sha256 } from "@noble/hashes/sha2.js";
226
- import { contractPreimage } from "@arkade-os/sdk";
227
- var BTC_ASSET_ID = "btc";
228
- var byNewest = (a, b) => b.createdAt - a.createdAt;
229
- var getAssetSwapsOrThrow = async (repository) => {
230
- return (await repository.getAllSwaps()).filter(
231
- (s) => s && typeof s.id === "string" && // offer swaps carry the TLV; onchain-corridor swaps carry
232
- // the payment hash instead — either marks a valid record
233
- (typeof s.offerHex === "string" || typeof s.paymentHash === "string")
234
- ).sort(byNewest);
235
- };
236
- var getAssetSwaps = async (repository) => {
237
- try {
238
- return await getAssetSwapsOrThrow(repository);
239
- } catch {
240
- return [];
241
- }
242
- };
243
- var saveSwapOrThrow = async (repository, swap) => {
244
- try {
245
- await repository.saveSwap(swap);
246
- } catch (error) {
247
- const reason = error instanceof Error ? error.message : String(error);
248
- throw new Error(`failed to save swap ${swap.id}: ${reason}`);
249
- }
250
- };
251
- var addAssetSwap = async (repository, swap) => {
252
- const swaps = await getAssetSwapsOrThrow(repository);
253
- if (swaps.some((s) => s.id === swap.id)) return swaps;
254
- await saveSwapOrThrow(repository, swap);
255
- const at = swaps.findIndex((s) => byNewest(swap, s) <= 0);
256
- const merged = [...swaps];
257
- merged.splice(at === -1 ? merged.length : at, 0, swap);
258
- return merged;
259
- };
260
- var updateAssetSwap = async (repository, id, changes) => {
261
- const swaps = (await getAssetSwapsOrThrow(repository)).map(
262
- (s) => s.id === id ? { ...s, ...changes } : s
263
- );
264
- const updated = swaps.find((s) => s.id === id);
265
- if (updated) await saveSwapOrThrow(repository, updated);
266
- return swaps;
267
- };
268
- var updateAssetSwapBestEffort = async (repository, id, changes) => {
269
- try {
270
- return { swaps: await updateAssetSwap(repository, id, changes), persisted: true };
271
- } catch (error) {
272
- console.warn(`[swap] failed to persist update for swap ${id}`, error);
273
- const swaps = (await getAssetSwaps(repository)).map(
274
- (s) => s.id === id ? { ...s, ...changes } : s
275
- );
276
- return { swaps, persisted: false };
277
- }
278
- };
279
- var swapSecretsToRecord = (secrets) => ({
280
- signingDescriptor: secrets.descriptor,
281
- ..."mustPersistPreimage" in secrets && secrets.mustPersistPreimage ? { preimageHex: hex.encode(secrets.preimage) } : {},
282
- ..."preimageSalt" in secrets && secrets.preimageSalt ? { preimageSaltHex: hex.encode(secrets.preimageSalt) } : {}
283
- });
284
- var decodeHex32 = (value, field) => {
285
- const bytes = hex.decode(value);
286
- if (bytes.length !== 32) {
287
- throw new Error(`${field} must be 32 bytes, got ${bytes.length}`);
288
- }
289
- return bytes;
290
- };
291
- var PreimageNotRecoverableError = class extends Error {
292
- constructor(reason, message, options) {
293
- super(message, options);
294
- this.reason = reason;
295
- }
296
- reason;
297
- name = "PreimageNotRecoverableError";
298
- };
299
- var preimageForSwapRecord = async (wallet, record) => {
300
- if (!record.signingDescriptor) {
301
- throw new PreimageNotRecoverableError(
302
- "no-secrets",
303
- "this swap record carries no signing descriptor"
304
- );
305
- }
306
- let stored;
307
- let salt;
308
- try {
309
- stored = record.preimageHex ? decodeHex32(record.preimageHex, "preimageHex") : void 0;
310
- salt = record.preimageSaltHex ? decodeHex32(record.preimageSaltHex, "preimageSaltHex") : void 0;
311
- } catch (cause) {
312
- throw new PreimageNotRecoverableError(
313
- "malformed-record",
314
- `this swap record's secrets projection is unreadable: ${String(cause)}`,
315
- { cause }
316
- );
317
- }
318
- let preimage;
319
- try {
320
- preimage = await contractPreimage(wallet, record.signingDescriptor, { stored, salt });
321
- } catch (cause) {
322
- throw new PreimageNotRecoverableError(
323
- "not-derivable",
324
- `this wallet cannot produce the preimage for ${record.signingDescriptor}`,
325
- { cause }
326
- );
327
- }
328
- if (record.paymentHash && hex.encode(sha256(preimage)) !== record.paymentHash.toLowerCase()) {
329
- throw new PreimageNotRecoverableError(
330
- "hash-mismatch",
331
- "the derived preimage does not match this swap's payment hash: wrong wallet, or a tampered salt"
332
- );
333
- }
334
- return preimage;
335
- };
336
-
337
- // src/offer.ts
338
- var swapPrograms = {
339
- wantAsset: arkade.parseArtifact(swap_want_asset_program_default),
340
- wantBtc: arkade.parseArtifact(swap_want_btc_program_default)
341
- };
342
- function withExitClosure(program, exit) {
343
- if (!exit) return program;
344
- return {
345
- ...program,
346
- // typed params are authoritative: an undeclared `$exitDelay` fails
347
- // validateProgram instead of compiling against an unbound value
348
- params: [...program.params ?? [], { name: "exitDelay", type: "int" }],
349
- functions: {
350
- ...program.functions,
351
- exit: {
352
- tapscript: { signers: ["$user"], csv: { type: exit.type, value: "$exitDelay" } }
353
- }
354
- }
355
- };
356
- }
357
- function swapProgramBinding(offer, serverPubkey) {
358
- if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
359
- throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
360
- }
361
- return {
362
- program: withExitClosure(
363
- offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
364
- offer.exitDelay
365
- ),
366
- args: {
367
- makerWP: offer.makerPkScript.subarray(2),
368
- wantAmount: offer.wantAmount,
369
- server: serverPubkey,
370
- user: offer.makerPublicKey,
371
- // internal byte order
372
- ...offer.wantAsset && {
373
- wantAssetTxid: offer.wantAsset.txid.slice().reverse(),
374
- wantAssetGroupIndex: offer.wantAsset.groupIndex
375
- },
376
- ...offer.exitDelay && { exitDelay: offer.exitDelay.value }
377
- },
378
- keys: {
379
- serverKey: serverPubkey,
380
- userKey: offer.makerPublicKey,
381
- emulatorKey: offer.emulatorPubkey
382
- }
383
- };
384
- }
385
- function offerVtxoScript(offer, serverPubkey) {
386
- const { program, args, keys } = swapProgramBinding(offer, serverPubkey);
387
- return new arkade.ArkadeProgramScript(program, args, keys);
388
- }
389
- var OFFER_PACKET_TYPE = 3;
390
- var FIELDS = {
391
- swapPkScript: { tag: 1, width: 34 },
392
- wantAmount: { tag: 2, width: 8 },
393
- wantAsset: { tag: 3, width: void 0 },
394
- makerPkScript: { tag: 5, width: 34 },
395
- makerPublicKey: { tag: 7, width: 32 },
396
- emulatorPubkey: { tag: 8, width: 32 },
397
- ratioNum: { tag: 9, width: 8 },
398
- ratioDen: { tag: 10, width: 8 },
399
- offerAsset: { tag: 11, width: void 0 },
400
- exitTimelock: { tag: 12, width: 9 }
401
- };
402
- var EXIT_TYPES = ["blocks", "seconds"];
403
- var NAMES = Object.fromEntries(Object.entries(FIELDS).map(([k, f]) => [f.tag, k]));
404
- function u64(name, value) {
405
- if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
406
- throw new Error(`${name} does not fit the offer wire format (u64)`);
407
- }
408
- const out = new Uint8Array(8);
409
- new DataView(out.buffer).setBigUint64(0, value, false);
410
- return out;
411
- }
412
- var readU64 = (value) => new DataView(value.buffer, value.byteOffset).getBigUint64(0, false);
413
- var setRatio = (name, value) => {
414
- if (value === void 0 || value === BigInt(0)) return void 0;
415
- if (value < BigInt(0)) throw new Error(`${name} does not fit the offer wire format (u64)`);
416
- return value;
417
- };
418
- function tlv(type, value) {
419
- if (value.length > 65535) throw new Error("TLV value exceeds the u16 length field");
420
- return concatBytes(Uint8Array.of(type, value.length >> 8 & 255, value.length & 255), value);
421
- }
422
- function encodeOffer(offer) {
423
- if (Boolean(offer.wantAsset) === Boolean(offer.offerAsset)) {
424
- throw new Error("offer must carry exactly one of wantAsset or offerAsset");
425
- }
426
- for (const name of [
427
- "swapPkScript",
428
- "makerPkScript",
429
- "makerPublicKey",
430
- "emulatorPubkey"
431
- ]) {
432
- if (offer[name].length !== FIELDS[name].width) {
433
- throw new Error(`${name} must be ${FIELDS[name].width} bytes`);
434
- }
435
- }
436
- const ratioNum = setRatio("ratioNum", offer.ratioNum);
437
- const ratioDen = setRatio("ratioDen", offer.ratioDen);
438
- if (ratioNum === void 0 !== (ratioDen === void 0)) {
439
- throw new Error("offer must carry both ratioNum and ratioDen, or neither");
440
- }
441
- const recs = [
442
- tlv(FIELDS.swapPkScript.tag, offer.swapPkScript),
443
- tlv(FIELDS.wantAmount.tag, u64("wantAmount", offer.wantAmount))
444
- ];
445
- if (offer.wantAsset) recs.push(tlv(FIELDS.wantAsset.tag, offer.wantAsset.serialize()));
446
- if (ratioNum !== void 0) recs.push(tlv(FIELDS.ratioNum.tag, u64("ratioNum", ratioNum)));
447
- if (ratioDen !== void 0) recs.push(tlv(FIELDS.ratioDen.tag, u64("ratioDen", ratioDen)));
448
- if (offer.offerAsset) recs.push(tlv(FIELDS.offerAsset.tag, offer.offerAsset.serialize()));
449
- recs.push(
450
- tlv(FIELDS.makerPkScript.tag, offer.makerPkScript),
451
- tlv(FIELDS.makerPublicKey.tag, offer.makerPublicKey),
452
- tlv(FIELDS.emulatorPubkey.tag, offer.emulatorPubkey)
453
- );
454
- if (offer.exitDelay) recs.push(tlv(FIELDS.exitTimelock.tag, encodeExitDelay(offer.exitDelay)));
455
- return concatBytes(...recs);
456
- }
457
- function encodeExitDelay(exit) {
458
- return concatBytes(
459
- Uint8Array.of(EXIT_TYPES.indexOf(assertExitDelay(exit).type)),
460
- u64("exitDelay", exit.value)
461
- );
462
- }
463
- function assertExitDelay(exit) {
464
- if (EXIT_TYPES.indexOf(exit.type) < 0) {
465
- throw new Error(`unknown exitDelay locktime type: ${exit.type}`);
466
- }
467
- if (exit.value <= BigInt(0)) {
468
- throw new Error("exitDelay must be a positive relative locktime");
469
- }
470
- if (exit.value >> BigInt(32) > BigInt(0)) {
471
- throw new Error("exitDelay does not fit the locktime field (u32)");
472
- }
473
- return exit;
474
- }
475
- function decodeOffer(data) {
476
- const fields = {};
477
- let off = 0;
478
- while (off < data.length) {
479
- if (off + 3 > data.length) throw new Error("truncated TLV header");
480
- const type = data[off];
481
- const length = data[off + 1] << 8 | data[off + 2];
482
- off += 3;
483
- if (off + length > data.length)
484
- throw new Error(`truncated TLV value for type 0x${type.toString(16)}`);
485
- const name = NAMES[type];
486
- if (!name) throw new Error(`unknown TLV type: 0x${type.toString(16)}`);
487
- if (fields[name] !== void 0) throw new Error(`duplicate TLV record: ${name}`);
488
- fields[name] = data.slice(off, off + length);
489
- off += length;
490
- }
491
- for (const name of ["wantAsset", "offerAsset"]) {
492
- if (fields[name]?.length === 0) throw new Error(`missing/invalid ${name}`);
493
- }
494
- for (const [name, value] of Object.entries(fields)) {
495
- const width = FIELDS[name].width;
496
- if (width !== void 0 && value.length !== width) {
497
- throw new Error(`missing/invalid ${name}`);
498
- }
499
- }
500
- const need = (name) => {
501
- const v = fields[name];
502
- const len = FIELDS[name].width;
503
- if (!v || len !== void 0 && v.length !== len)
504
- throw new Error(`missing/invalid ${name}`);
505
- return v;
506
- };
507
- const amount = need("wantAmount");
508
- if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
509
- throw new Error("offer must carry exactly one of wantAsset or offerAsset");
510
- }
511
- const readRatio = (name) => {
512
- const raw = fields[name];
513
- if (!raw) return void 0;
514
- const value = readU64(raw);
515
- if (value === BigInt(0)) throw new Error(`missing/invalid ${name}`);
516
- return value;
517
- };
518
- const ratioNum = readRatio("ratioNum");
519
- const ratioDen = readRatio("ratioDen");
520
- if (ratioNum === void 0 !== (ratioDen === void 0)) {
521
- throw new Error("offer must carry both ratioNum and ratioDen, or neither");
522
- }
523
- return {
524
- swapPkScript: need("swapPkScript"),
525
- wantAmount: readU64(amount),
526
- ...fields.wantAsset && { wantAsset: asset.AssetId.fromBytes(fields.wantAsset) },
527
- ...fields.offerAsset && { offerAsset: asset.AssetId.fromBytes(fields.offerAsset) },
528
- makerPkScript: need("makerPkScript"),
529
- makerPublicKey: need("makerPublicKey"),
530
- emulatorPubkey: need("emulatorPubkey"),
531
- ...ratioNum !== void 0 && { ratioNum },
532
- ...ratioDen !== void 0 && { ratioDen },
533
- ...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
534
- };
535
- }
536
- function decodeExitDelay(value) {
537
- const type = EXIT_TYPES[value[0]];
538
- if (!type) throw new Error(`unknown exitDelay locktime type: 0x${value[0].toString(16)}`);
539
- return { type, value: readU64(value.subarray(1)) };
540
- }
541
- var OFFER_CONTRACT_LABEL = "Arkade swap offer";
542
- var OFFER_CONTRACT_KIND = "asset-swap-offer";
543
- async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript, opts = {}) {
544
- const contractManager = opts.contractManager ?? await wallet.getContractManager();
545
- const client = opts.client ?? await arkade.Arkade.connect({
546
- arkade: new RestArkProvider(arkServerUrl),
547
- indexer: new RestIndexerProvider(arkServerUrl),
548
- identity: wallet.identity,
549
- // without this the row's `address` would be derived against the SDK's
550
- // default network while its script is right — a row that disagrees with
551
- // the address the user is about to fund
552
- network: getNetwork(network),
553
- contractManager
554
- });
555
- await contractManager.createContract(
556
- offerContractParams(client, binding, serverPubkey, expectedPkScript)
557
- );
558
- await promoteOfferContract(contractManager, hex2.encode(expectedPkScript), opts.issued);
559
- }
560
- function offerContractParams(client, binding, serverPubkey, expectedPkScript) {
561
- const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
562
- const contract = new arkade.ArkadeContract(client, program, args, keys);
563
- if (hex2.encode(contract.pkScript) !== hex2.encode(expectedPkScript)) {
564
- throw new Error("derived covenant does not match the offer's swapPkScript");
565
- }
566
- return {
567
- ...contract.toContractParams(),
568
- label: OFFER_CONTRACT_LABEL,
569
- metadata: { genericallySpendable: false, kind: OFFER_CONTRACT_KIND }
570
- };
571
- }
572
- async function restoreOfferCoverage(wallet, arkServerUrl, swaps) {
573
- const live = swaps.filter((s) => !RETIRABLE.includes(s.status));
574
- if (live.length === 0) return;
575
- const arkProvider = new RestArkProvider(arkServerUrl);
576
- const info = await arkProvider.getInfo();
577
- const serverPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
578
- const contractManager = await wallet.getContractManager();
579
- const client = await arkade.Arkade.connect({
580
- arkade: arkProvider,
581
- indexer: new RestIndexerProvider(arkServerUrl),
582
- identity: wallet.identity,
583
- network: getNetwork(info.network),
584
- contractManager
585
- });
586
- const done = /* @__PURE__ */ new Set();
587
- const covenants = [];
588
- for (const swap of live) {
589
- if (done.has(swap.swapPkScript)) continue;
590
- done.add(swap.swapPkScript);
591
- try {
592
- const { swapPkScript: _, ...binding } = decodeOffer(hex2.decode(swap.offerHex));
593
- covenants.push({
594
- script: swap.swapPkScript,
595
- issued: swap.createdAt,
596
- params: offerContractParams(
597
- client,
598
- binding,
599
- serverPubKey,
600
- hex2.decode(swap.swapPkScript)
601
- )
602
- });
603
- } catch (err) {
604
- console.warn(`[swap] could not restore coverage for ${swap.swapPkScript}`, err);
605
- }
606
- }
607
- if (covenants.length === 0) return;
608
- const paramsList = covenants.map((c) => c.params);
609
- if (contractManager.createContracts) {
610
- await contractManager.createContracts(paramsList);
611
- } else {
612
- for (const params of paramsList) await contractManager.createContract(params);
613
- }
614
- for (const c of covenants) await promoteOfferContract(contractManager, c.script, c.issued);
615
- }
616
- function serverExitDelay(delay) {
617
- if (typeof delay !== "bigint" || delay <= BigInt(0)) {
618
- throw new Error(
619
- "the server reports no usable unilateralExitDelay; pass `exitDelay` to set the offer's exit closure explicitly, or `noExit: true` to publish without one"
620
- );
621
- }
622
- return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
623
- }
624
- async function createOffer(wallet, arkServerUrl, params) {
625
- if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
626
- throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
627
- }
628
- const [info, makerAddress, makerPublicKey] = await Promise.all([
629
- new RestArkProvider(arkServerUrl).getInfo(),
630
- wallet.getAddress(),
631
- wallet.identity.xOnlyPublicKey()
632
- ]);
633
- const serverPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
634
- const network = getNetwork(info.network);
635
- const emuKey = hex2.decode(
636
- toXOnlySignerHex(resolveEmulatorPubkey(network, params.emulatorPubkey))
637
- );
638
- const binding = {
639
- wantAmount: params.wantAmount,
640
- wantAsset: params.wantAsset,
641
- offerAsset: params.offerAsset,
642
- makerPkScript: ArkAddress.decode(makerAddress).pkScript,
643
- makerPublicKey,
644
- emulatorPubkey: emuKey,
645
- // checked HERE, before the covenant is derived and registered below:
646
- // deferring it to `encodeOffer` leaves a registered contract behind for
647
- // an offer that then fails to encode. @see assertExitDelay
648
- exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
649
- };
650
- const script = offerVtxoScript(binding, serverPubKey);
651
- const offer = { ...binding, swapPkScript: script.pkScript };
652
- await registerOfferContract(
653
- wallet,
654
- arkServerUrl,
655
- info.network,
656
- binding,
657
- serverPubKey,
658
- script.pkScript
659
- );
660
- const payload = encodeOffer(offer);
661
- return {
662
- offerHex: hex2.encode(payload),
663
- extension: { type: OFFER_PACKET_TYPE, payload },
664
- // VtxoScript.address owns address construction; assembling an ArkAddress
665
- // from tweakedPublicKey here would silently miss any future step it gains
666
- address: script.address(network.hrp, serverPubKey).encode(),
667
- swapPkScript: script.pkScript
668
- };
669
- }
670
- async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
671
- const { repository, fundingTxid, swapAddress } = opts;
672
- const offer = decodeOffer(hex2.decode(offerHex));
673
- const contractManager = await wallet.getContractManager();
674
- const client = await arkade.Arkade.connect({
675
- arkade: new RestArkProvider(arkServerUrl),
676
- indexer: new RestIndexerProvider(arkServerUrl),
677
- identity: wallet.identity,
678
- // registered offers resolve their VTXOs from the contract repository
679
- // instead of a direct indexer query; the indexer above stays as the
680
- // fallback for offers created before registration existed
681
- contractManager
682
- // no `network`, unlike registerOfferContract: the row lookup is by
683
- // script and the payout script comes from wallet.getAddress(), so the
684
- // client's network (which only shapes address derivation) is unused here
685
- });
686
- const operatorPubkey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
687
- const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
688
- const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
689
- if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
690
- throw new Error(
691
- "rebuilt covenant does not match the offer's swapPkScript \u2014 the server signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
692
- );
693
- }
694
- const contract = new arkade.ArkadeContract(client, program, args, keys);
695
- const [vtxos, makerAddress] = await Promise.all([contract.getUtxos(), wallet.getAddress()]);
696
- if (!fundingTxid && vtxos.length > 1) {
697
- throw new Error(
698
- "multiple spendable deposits at the swap address \u2014 pass fundingTxid to select one"
699
- );
700
- }
701
- const vtxo = fundingTxid ? vtxos.find((v) => v.txid === fundingTxid) : vtxos[0];
702
- if (!vtxo) throw new Error("no spendable VTXO at the swap address");
703
- const makerPkScript = ArkAddress.decode(makerAddress).pkScript;
704
- const cancel = contract.functions.cancel().from({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value }).to(makerPkScript, BigInt(vtxo.value));
705
- for (const a of vtxo.assets ?? []) {
706
- cancel.withAsset({
707
- assetId: a.assetId,
708
- inputs: [{ vin: 0, amount: BigInt(a.amount) }],
709
- outputs: [{ vout: 0, amount: BigInt(a.amount) }]
710
- });
711
- }
712
- const swapId = fundingTxid ?? vtxo.txid;
713
- const hasLocalRecord = (await getAssetSwapsOrThrow(repository)).some((s) => s.id === swapId);
714
- if (hasLocalRecord) await updateAssetSwap(repository, swapId, { status: "cancelling" });
715
- const { txid } = await cancel.send();
716
- if (hasLocalRecord) {
717
- const { persisted, swaps } = await updateAssetSwapBestEffort(repository, swapId, {
718
- status: "cancelled",
719
- spentTxid: txid
720
- });
721
- if (persisted) {
722
- await retireOfferContract(contractManager, swaps, hex2.encode(offer.swapPkScript));
723
- }
724
- }
725
- return txid;
726
- }
727
- var ASSET_CARRIER_SATS = BigInt(SDK_ASSET_CARRIER_SATS);
728
- async function fillOffer(wallet, arkServerUrl, offerHex, opts) {
729
- const {
730
- fund,
731
- payoutScript,
732
- fundingTxid,
733
- swapAddress,
734
- assetCarrierSats = ASSET_CARRIER_SATS,
735
- emulator,
736
- emulatorPubkey
737
- } = opts;
738
- const offer = decodeOffer(hex2.decode(offerHex));
739
- const wantedAssetId = offer.wantAsset?.toString();
740
- if (fund.length === 0) {
741
- throw new Error("fillOffer needs coins to pay wantAmount with \u2014 `fund` is empty");
742
- }
743
- if (wantedAssetId !== void 0) {
744
- const supplied = fund.reduce(
745
- (sum, coin) => sum + amountOfAsset(coin.assets, wantedAssetId),
746
- BigInt(0)
747
- );
748
- if (supplied < offer.wantAmount) {
749
- throw new Error(
750
- `fillOffer needs ${offer.wantAmount} of ${wantedAssetId} to pay the maker, but \`fund\` declares ${supplied} \u2014 pass coins carrying it, and declare their assets`
751
- );
752
- }
753
- }
754
- const contractManager = await wallet.getContractManager();
755
- const client = await arkade.Arkade.connect({
756
- arkade: new RestArkProvider(arkServerUrl),
757
- indexer: new RestIndexerProvider(arkServerUrl),
758
- identity: wallet.identity,
759
- contractManager,
760
- // `fulfill` is a covenant path, so the emulator executes the arkade
761
- // script and finalizes with arkd. Without it the spend is built and then
762
- // refused at submission — there is no default to fall back on.
763
- emulator: typeof emulator === "string" ? new RestEmulatorProvider(emulator) : emulator,
764
- // Only reached for the client's own emulatorKey, which this fill never
765
- // derives against — the offer's own key is bound below. It still has to
766
- // resolve, and on a network with no pinned key it throws without this.
767
- ...emulatorPubkey ? { emulatorPubkey } : {}
768
- });
769
- const serverKey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
770
- const { program, args, keys } = swapProgramBinding(offer, serverKey);
771
- const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
772
- if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
773
- throw new Error(
774
- "rebuilt covenant does not match the offer's swapPkScript \u2014 the server signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
775
- );
776
- }
777
- const contract = new arkade.ArkadeContract(client, program, args, keys);
778
- const [vtxos, takerAddress] = await Promise.all([contract.getUtxos(), wallet.getAddress()]);
779
- if (!fundingTxid && vtxos.length > 1) {
780
- throw new Error(
781
- "multiple spendable deposits at the swap address \u2014 pass fundingTxid to select one"
782
- );
783
- }
784
- const vtxo = fundingTxid ? vtxos.find((v) => v.txid === fundingTxid) : vtxos[0];
785
- if (!vtxo) throw new Error("no spendable VTXO at the swap address");
786
- if (offer.offerAsset) {
787
- const offered = offer.offerAsset.toString();
788
- const deposited = amountOfAsset(vtxo.assets, offered);
789
- if (deposited <= BigInt(0)) {
790
- throw new Error(
791
- `the deposit at the swap address carries no ${offered}, which this offer sells \u2014 filling it would pay wantAmount for nothing. The indexer may be behind, or the offer may not be backed by what it advertises`
792
- );
793
- }
794
- }
795
- const payout = payoutScript ?? ArkAddress.decode(takerAddress).pkScript;
796
- const makerSats = wantedAssetId === void 0 ? offer.wantAmount : assetCarrierSats;
797
- const fill = contract.functions.fulfill().from({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value }).fund(fund).to(offer.makerPkScript, makerSats).change(payout);
798
- const held = /* @__PURE__ */ new Map();
799
- const hold = (vin, assets) => {
800
- for (const a of assets ?? []) {
801
- const amount = BigInt(a.amount);
802
- if (amount <= BigInt(0)) continue;
803
- held.set(a.assetId, [...held.get(a.assetId) ?? [], { vin, amount }]);
804
- }
805
- };
806
- hold(0, vtxo.assets);
807
- fund.forEach((coin, i) => hold(i + 1, coin.assets));
808
- const outputsSum = makerSats;
809
- const inputsSum = fund.reduce((s, c) => s + BigInt(c.value), BigInt(vtxo.value));
810
- const hasPayoutOutput = inputsSum > outputsSum;
811
- const emitWanted = () => {
812
- if (wantedAssetId === void 0) return;
813
- const supplying = held.get(wantedAssetId) ?? [];
814
- const supplied = supplying.reduce((s, i) => s + i.amount, BigInt(0));
815
- const outputs = [{ vout: 0, amount: offer.wantAmount }];
816
- const surplus = supplied - offer.wantAmount;
817
- if (surplus > BigInt(0)) {
818
- if (!hasPayoutOutput) {
819
- throw new Error(
820
- `fillOffer has ${surplus} of ${wantedAssetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
821
- );
822
- }
823
- outputs.push({ vout: 1, amount: surplus });
824
- }
825
- fill.withAsset({ assetId: wantedAssetId, inputs: supplying, outputs });
826
- held.delete(wantedAssetId);
827
- };
828
- emitWanted();
829
- for (const [assetId, inputs] of held) {
830
- const amount = inputs.reduce((s, i) => s + i.amount, BigInt(0));
831
- if (!hasPayoutOutput) {
832
- throw new Error(
833
- `fillOffer has ${amount} of ${assetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
834
- );
835
- }
836
- fill.withAsset({ assetId, inputs, outputs: [{ vout: 1, amount }] });
837
- }
838
- const { txid } = await fill.send();
839
- return txid;
840
- }
841
- var amountOfAsset = (assets, assetId) => {
842
- let total = BigInt(0);
843
- for (const a of assets ?? []) {
844
- if (a.assetId === assetId) total += BigInt(a.amount);
845
- }
846
- return total;
847
- };
848
-
849
109
  // src/markets.ts
850
110
  import {
851
111
  bestMarket,
852
112
  discover,
853
113
  isNetwork,
854
114
  marketLegKey,
115
+ registryIndexUrl,
855
116
  sideLimits
856
117
  } from "@arkade-os/solver-discovery";
857
118
  import { isSubdust } from "@arkade-os/sdk";
@@ -866,9 +127,9 @@ var LEGACY_CORRIDORS = {
866
127
  };
867
128
  var assetOf = (market, side) => side === "base" ? market.base_asset : market.quote_asset;
868
129
  var marketAssetId = (market, side) => {
869
- const asset2 = assetOf(market, side);
870
- if (typeof asset2?.caip19_id === "string") return asset2.caip19_id;
871
- return typeof asset2?.id === "string" ? asset2.id : void 0;
130
+ const asset = assetOf(market, side);
131
+ if (typeof asset?.caip19_id === "string") return asset.caip19_id;
132
+ return typeof asset?.id === "string" ? asset.id : void 0;
872
133
  };
873
134
  var chainNamespaceOf = (id) => {
874
135
  if (id === void 0) return void 0;
@@ -967,12 +228,14 @@ var discoverMarkets = async (options) => {
967
228
  fetchImpl,
968
229
  useCache = true
969
230
  } = options;
970
- if (!registry || !isNetwork(network)) return [];
971
- const cached = repository && await readMarketsCache(repository, network, registry);
231
+ if (!isNetwork(network)) return [];
232
+ const registries = registry ? [registry] : void 0;
233
+ const registryKey = registries?.[0] ?? registryIndexUrl(network);
234
+ const cached = repository && await readMarketsCache(repository, network, registryKey);
972
235
  if (useCache && cached && Date.now() - cached.fetchedAt < MARKETS_CACHE_TTL_MS)
973
236
  return cached.markets;
974
237
  const { markets, sources, warnings } = await discover({
975
- registries: [registry],
238
+ registries,
976
239
  localCards,
977
240
  network,
978
241
  fetchImpl
@@ -982,7 +245,7 @@ var discoverMarkets = async (options) => {
982
245
  if (!reachable && cached) return cached.markets;
983
246
  if (reachable && repository) {
984
247
  try {
985
- await repository.saveCachedMarkets(network, registry, {
248
+ await repository.saveCachedMarkets(network, registryKey, {
986
249
  markets,
987
250
  fetchedAt: Date.now()
988
251
  });
@@ -996,8 +259,8 @@ var findMarket = (markets, fromId, toId) => {
996
259
  const resolveMarketId = (id) => {
997
260
  for (const market of markets) {
998
261
  for (const side of ["base", "quote"]) {
999
- const asset2 = side === "base" ? market.base_asset : market.quote_asset;
1000
- if (asset2.id === id) return marketLegKey(market, side);
262
+ const asset = side === "base" ? market.base_asset : market.quote_asset;
263
+ if (asset.id === id) return marketLegKey(market, side);
1001
264
  const canonical = marketAssetId(market, side) ?? "";
1002
265
  const matchesBtc = id === BTC_ASSET_ID && /^arkade:[^/]+\/slip44:(?:0|1)$/.test(canonical);
1003
266
  const matchesAsset = /^[0-9a-f]{68}$/.test(id) && canonical.endsWith(`/asset:${id}`);
@@ -1147,7 +410,7 @@ var IndexedDbAssetSwapRepository = class {
1147
410
  };
1148
411
 
1149
412
  // src/restore.ts
1150
- import { base64, hex as hex3 } from "@scure/base";
413
+ import { base64, hex } from "@scure/base";
1151
414
  import {
1152
415
  Extension,
1153
416
  Transaction,
@@ -1183,7 +446,7 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
1183
446
  let leaves;
1184
447
  try {
1185
448
  const script = offerVtxoScript(offer, serverPubkey);
1186
- if (hex3.encode(script.pkScript) !== hex3.encode(offer.swapPkScript)) return "indeterminate";
449
+ if (hex.encode(script.pkScript) !== hex.encode(offer.swapPkScript)) return "indeterminate";
1187
450
  leaves = {
1188
451
  // both routes that hand the deposit back; `exit` is absent on an
1189
452
  // offer that carries no exit closure, and drops out here
@@ -1196,11 +459,11 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
1196
459
  for (let i = 0; i < spendTx.inputsLength; i++) {
1197
460
  const input = spendTx.getInput(i);
1198
461
  if (!input.txid || input.index !== deposit.vout) continue;
1199
- if (hex3.encode(input.txid) !== deposit.txid) continue;
462
+ if (hex.encode(input.txid) !== deposit.txid) continue;
1200
463
  for (const leaf of input.tapLeafScript ?? []) {
1201
- const spent = hex3.encode(scriptFromTapLeafScript(leaf));
1202
- if (leaves.returned.some((back) => spent === hex3.encode(back))) return "cancelled";
1203
- if (leaves.fulfill && spent === hex3.encode(leaves.fulfill)) return "fulfilled";
464
+ const spent = hex.encode(scriptFromTapLeafScript(leaf));
465
+ if (leaves.returned.some((back) => spent === hex.encode(back))) return "cancelled";
466
+ if (leaves.fulfill && spent === hex.encode(leaves.fulfill)) return "fulfilled";
1204
467
  }
1205
468
  }
1206
469
  return "indeterminate";
@@ -1220,7 +483,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1220
483
  try {
1221
484
  reopened.push({
1222
485
  fundingTx: { type: "sent", redeemTxid: swap.fundingTxid },
1223
- offer: decodeOffer(hex3.decode(swap.offerHex)),
486
+ offer: decodeOffer(hex.decode(swap.offerHex)),
1224
487
  offerHex: swap.offerHex,
1225
488
  existing: swap
1226
489
  });
@@ -1252,13 +515,13 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1252
515
  found.push({
1253
516
  fundingTx,
1254
517
  offer: decodeOffer(payload),
1255
- offerHex: hex3.encode(payload)
518
+ offerHex: hex.encode(payload)
1256
519
  });
1257
520
  } catch {
1258
521
  }
1259
522
  }
1260
523
  if (found.length === 0) return { restored: [], scannedTxids: fetchedTxids };
1261
- const scripts = [...new Set(found.map((f) => hex3.encode(f.offer.swapPkScript)))];
524
+ const scripts = [...new Set(found.map((f) => hex.encode(f.offer.swapPkScript)))];
1262
525
  const { vtxos } = await indexer.getVtxos({ scripts });
1263
526
  const vtxoByScriptAndTxid = new Map(vtxos.map((v) => [`${v.script}:${v.txid}`, v]));
1264
527
  const txByAnyId = /* @__PURE__ */ new Map();
@@ -1270,7 +533,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1270
533
  const spendTxids = /* @__PURE__ */ new Set();
1271
534
  for (const { fundingTx, offer } of found) {
1272
535
  const vtxo = vtxoByScriptAndTxid.get(
1273
- `${hex3.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
536
+ `${hex.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
1274
537
  );
1275
538
  if (vtxo?.virtualStatus.state !== "spent") continue;
1276
539
  for (const txid of spendTxidsOf(vtxo)) spendTxids.add(txid);
@@ -1279,7 +542,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1279
542
  const restored = [];
1280
543
  const unresolved = /* @__PURE__ */ new Set();
1281
544
  for (const { fundingTx, offer, offerHex, existing } of found) {
1282
- const swapPkScript = hex3.encode(offer.swapPkScript);
545
+ const swapPkScript = hex.encode(offer.swapPkScript);
1283
546
  const vtxo = vtxoByScriptAndTxid.get(`${swapPkScript}:${fundingTx.redeemTxid}`);
1284
547
  if (!vtxo) {
1285
548
  unresolved.add(fundingTx.redeemTxid);
@@ -1459,7 +722,7 @@ function registerAssetSwapRestore(wallet, options) {
1459
722
  }
1460
723
 
1461
724
  // src/rfqCorridors.ts
1462
- import { hex as hex4 } from "@scure/base";
725
+ import { hex as hex2 } from "@scure/base";
1463
726
 
1464
727
  // src/rfqCorridor.ts
1465
728
  var RfqCorridorRegistry = class {
@@ -1615,13 +878,14 @@ var LightningReceiveCorridor = {
1615
878
  };
1616
879
  function onchainSendProfile(result) {
1617
880
  return {
1618
- claimKey: hex4.encode(result.htlcParams.claimKey),
1619
- refundKey: hex4.encode(result.htlcParams.refundKey),
881
+ claimKey: hex2.encode(result.htlcParams.claimKey),
882
+ refundKey: hex2.encode(result.htlcParams.refundKey),
1620
883
  htlcLocktime: result.htlcParams.refundLocktime,
1621
884
  network: result.l1Network,
1622
885
  htlcAddress: result.htlc.address,
1623
886
  minConfirmations: result.minConfirmations,
1624
- payoutPkScript: hex4.encode(result.payoutPkScript)
887
+ expectedAmount: result.expectedAmount,
888
+ payoutPkScript: hex2.encode(result.payoutPkScript)
1625
889
  };
1626
890
  }
1627
891
  var OnchainSendCorridor = {
@@ -1632,6 +896,7 @@ var OnchainSendCorridor = {
1632
896
  project: (swap) => {
1633
897
  const send = swap;
1634
898
  return {
899
+ expectedAmount: send.expectedAmount,
1635
900
  ...send.funding ? { funding: send.funding } : {},
1636
901
  ...send.claimTxid ? { claimTxid: send.claimTxid } : {}
1637
902
  };
@@ -1648,6 +913,11 @@ var OnchainSendCorridor = {
1648
913
  `onchain_send record carries no usable minConfirmations (${String(profile.minConfirmations)}); the confirmation gate cannot be checked \u2014 refusing to restore a swap that would claim an unconfirmed fill`
1649
914
  );
1650
915
  }
916
+ if (!Number.isSafeInteger(profile.expectedAmount) || profile.expectedAmount <= 0) {
917
+ throw new Error(
918
+ `onchain_send record carries no usable expectedAmount (${String(profile.expectedAmount)}); the funded value cannot be checked \u2014 refusing to restore a swap that would claim an underfunded fill`
919
+ );
920
+ }
1651
921
  const htlc = onchainHtlcScript(
1652
922
  {
1653
923
  // From the profile, not the covenant: the lockup commits to
@@ -1655,8 +925,8 @@ var OnchainSendCorridor = {
1655
925
  // unlocks both legs, but only one of the two hashes of it
1656
926
  // is recoverable here.
1657
927
  paymentHash,
1658
- claimKey: hex4.decode(profile.claimKey),
1659
- refundKey: hex4.decode(profile.refundKey),
928
+ claimKey: hex2.decode(profile.claimKey),
929
+ refundKey: hex2.decode(profile.refundKey),
1660
930
  refundLocktime: profile.htlcLocktime
1661
931
  },
1662
932
  profile.network
@@ -1670,9 +940,10 @@ var OnchainSendCorridor = {
1670
940
  paymentHash,
1671
941
  htlc,
1672
942
  minConfirmations: profile.minConfirmations,
943
+ expectedAmount: profile.expectedAmount,
1673
944
  // Optional here, required at the write: throwing on an older
1674
945
  // record would strand the refund it is still owed.
1675
- ...profile.payoutPkScript ? { payoutPkScript: hex4.decode(profile.payoutPkScript) } : {},
946
+ ...profile.payoutPkScript ? { payoutPkScript: hex2.decode(profile.payoutPkScript) } : {},
1676
947
  ...profile.funding ? { funding: profile.funding } : {},
1677
948
  ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1678
949
  };
@@ -1690,8 +961,8 @@ rfqCorridorHandlers.register(LightningReceiveCorridor);
1690
961
  rfqCorridorHandlers.register(OnchainSendCorridor);
1691
962
 
1692
963
  // src/rfqRecord.ts
1693
- import { ArkAddress as ArkAddress2, VHTLCV2ContractHandler } from "@arkade-os/sdk";
1694
- import { hex as hex5 } from "@scure/base";
964
+ import { ArkAddress, VHTLCV2ContractHandler } from "@arkade-os/sdk";
965
+ import { hex as hex3 } from "@scure/base";
1695
966
 
1696
967
  // src/rfqSwapState.ts
1697
968
  var RFQ_SWAP_TERMINAL_STATES = ["settled", "refunded", "failed"];
@@ -1715,8 +986,8 @@ function assertSameSwap(origin, swap) {
1715
986
  `rfq swap record is a ${origin.kind} origin paired with a ${swap.kind} swap`
1716
987
  );
1717
988
  }
1718
- const funded = hex5.encode(ArkAddress2.decode(origin.lockupAddress).pkScript);
1719
- const watched = hex5.encode(swap.lockupPkScript);
989
+ const funded = hex3.encode(ArkAddress.decode(origin.lockupAddress).pkScript);
990
+ const watched = hex3.encode(swap.lockupPkScript);
1720
991
  if (funded !== watched) {
1721
992
  throw new Error(
1722
993
  `rfq swap record's lockup address holds ${funded}, but the swap watches ${watched} \u2014 these are not the same swap`
@@ -1761,10 +1032,10 @@ function rfqSwapOriginOf(record) {
1761
1032
  }
1762
1033
  function lockupScript(params, lockupAddress) {
1763
1034
  const script = VHTLCV2ContractHandler.createScript(params);
1764
- const funded = ArkAddress2.decode(lockupAddress).pkScript;
1765
- if (hex5.encode(funded) !== hex5.encode(script.pkScript)) {
1035
+ const funded = ArkAddress.decode(lockupAddress).pkScript;
1036
+ if (hex3.encode(funded) !== hex3.encode(script.pkScript)) {
1766
1037
  throw new Error(
1767
- `rfq swap covenant params derive ${hex5.encode(script.pkScript)}, but the record's lockup address holds ${hex5.encode(funded)} \u2014 these params are not this swap's`
1038
+ `rfq swap covenant params derive ${hex3.encode(script.pkScript)}, but the record's lockup address holds ${hex3.encode(funded)} \u2014 these params are not this swap's`
1768
1039
  );
1769
1040
  }
1770
1041
  return script;
@@ -1799,11 +1070,11 @@ function shouldRetainRfqSwap(record, now) {
1799
1070
  }
1800
1071
 
1801
1072
  // src/watch.ts
1802
- import { base64 as base642, hex as hex6 } from "@scure/base";
1073
+ import { base64 as base642, hex as hex4 } from "@scure/base";
1803
1074
  import {
1804
- ArkAddress as ArkAddress3,
1075
+ ArkAddress as ArkAddress2,
1805
1076
  isContractVtxoEvent,
1806
- RestIndexerProvider as RestIndexerProvider3,
1077
+ RestIndexerProvider as RestIndexerProvider2,
1807
1078
  Transaction as Transaction2
1808
1079
  } from "@arkade-os/sdk";
1809
1080
  var TERMINAL = ["fulfilled", "cancelled", "recoverable"];
@@ -1825,8 +1096,8 @@ async function watchOfferSwaps({
1825
1096
  onUpdate
1826
1097
  }) {
1827
1098
  const manager = await wallet.getContractManager();
1828
- const serverPubkey = ArkAddress3.decode(await wallet.getAddress()).serverPubKey;
1829
- const indexer = new RestIndexerProvider3(arkServerUrl);
1099
+ const serverPubkey = ArkAddress2.decode(await wallet.getAddress()).serverPubKey;
1100
+ const indexer = new RestIndexerProvider2(arkServerUrl);
1830
1101
  let queue = Promise.resolve();
1831
1102
  const enqueue = (task) => {
1832
1103
  queue = queue.then(task).catch(() => {
@@ -1839,7 +1110,7 @@ async function watchOfferSwaps({
1839
1110
  if (candidates.length === 0) return "indeterminate";
1840
1111
  const { txs } = await indexer.getVirtualTxs(candidates);
1841
1112
  return classifyDepositSpend(
1842
- decodeOffer(hex6.decode(swap.offerHex)),
1113
+ decodeOffer(hex4.decode(swap.offerHex)),
1843
1114
  serverPubkey,
1844
1115
  txs.map((psbt) => Transaction2.fromPSBT(base642.decode(psbt))),
1845
1116
  { txid: vtxo.txid, vout: vtxo.vout }
@@ -1904,7 +1175,7 @@ async function watchOfferSwaps({
1904
1175
 
1905
1176
  // src/chainSource.ts
1906
1177
  import * as btc from "@scure/btc-signer";
1907
- import { hex as hex7 } from "@scure/base";
1178
+ import { hex as hex5 } from "@scure/base";
1908
1179
  var addressOf = (pkScript, network) => btc.Address(L1_NETWORKS[network]).encode(btc.OutScript.decode(pkScript));
1909
1180
  var chainSourceFrom = (provider, network) => {
1910
1181
  const spenderFromHistory = async (txid, vout, pkScript) => {
@@ -1934,7 +1205,7 @@ var chainSourceFrom = (provider, network) => {
1934
1205
  const spender = outspend.txid || await spenderFromHistory(txid, vout, pkScript);
1935
1206
  if (!spender) return null;
1936
1207
  const raw = await provider.getRawTransaction(spender);
1937
- return { txHex: hex7.encode(raw) };
1208
+ return { txHex: hex5.encode(raw) };
1938
1209
  },
1939
1210
  broadcast(txHex) {
1940
1211
  return provider.broadcastTransaction(txHex);
@@ -1947,7 +1218,7 @@ var chainSourceFrom = (provider, network) => {
1947
1218
 
1948
1219
  // src/payment/rendezvous.ts
1949
1220
  import { sideLimits as sideLimits2 } from "@arkade-os/solver-discovery";
1950
- import { hex as hex8 } from "@scure/base";
1221
+ import { hex as hex6 } from "@scure/base";
1951
1222
  var XONLY_HEX = /^[0-9a-f]{64}$/;
1952
1223
  var rendezvousOf = (market, pinned) => {
1953
1224
  const transports = { nostr: { relays: market.transports?.nostr?.relays ?? [] } };
@@ -1966,7 +1237,7 @@ var rendezvousOf = (market, pinned) => {
1966
1237
  };
1967
1238
  };
1968
1239
  var solverRendezvous = (markets, payoutCorridor, amountSats, fallbackEmulatorPubkey) => {
1969
- const encoded = fallbackEmulatorPubkey ? hex8.encode(fallbackEmulatorPubkey) : void 0;
1240
+ const encoded = fallbackEmulatorPubkey ? hex6.encode(fallbackEmulatorPubkey) : void 0;
1970
1241
  if (encoded !== void 0 && !XONLY_HEX.test(encoded)) return void 0;
1971
1242
  const pinned = encoded;
1972
1243
  const quoteCorridor = payoutCorridor === "lightning" ? "bolt11" : "bitcoin";
@@ -2221,12 +1492,12 @@ var parseAssetId = (assetId) => {
2221
1492
  };
2222
1493
  var marketLabel = (market) => `${market.base_asset.ticker || market.base_asset.id}/${market.quote_asset.ticker || market.quote_asset.id}`;
2223
1494
  function crossAssetRail(deps) {
2224
- const planFor = async (asset2) => {
1495
+ const planFor = async (asset) => {
2225
1496
  const markets = await deps.discover();
2226
- const found = findMarket(markets, BTC_ASSET_ID, asset2.assetId);
1497
+ const found = findMarket(markets, BTC_ASSET_ID, asset.assetId);
2227
1498
  const market = found?.market;
2228
1499
  if (!market) return void 0;
2229
- const plan = await deps.quote(market, found.give, asset2.amount);
1500
+ const plan = await deps.quote(market, found.give, asset.amount);
2230
1501
  const [balance, dust] = await Promise.all([deps.btcBalance(), deps.dust()]);
2231
1502
  return { plan, error: validatePlan(plan, balance, dust) };
2232
1503
  };
@@ -2236,29 +1507,29 @@ function crossAssetRail(deps) {
2236
1507
  available: async (req) => {
2237
1508
  const assets = assetsOf3(req);
2238
1509
  if (assets.length !== 1) return false;
2239
- const [asset2] = assets;
2240
- if (asset2.assetId === BTC_ASSET_ID) return false;
2241
- if (typeof asset2.amount !== "bigint" || asset2.amount <= 0n) return false;
2242
- if (!parseAssetId(asset2.assetId)) return false;
2243
- const planned = await planFor(asset2);
1510
+ const [asset] = assets;
1511
+ if (asset.assetId === BTC_ASSET_ID) return false;
1512
+ if (typeof asset.amount !== "bigint" || asset.amount <= 0n) return false;
1513
+ if (!parseAssetId(asset.assetId)) return false;
1514
+ const planned = await planFor(asset);
2244
1515
  return planned !== void 0 && planned.error === void 0;
2245
1516
  },
2246
1517
  quote: async (req, ctx) => {
2247
1518
  const payTo = arkTarget(req.raw);
2248
- const asset2 = resolveAssetAmount(CROSS_ASSET_RAIL, req);
2249
- const wantAsset = parseAssetId(asset2.assetId);
1519
+ const asset = resolveAssetAmount(CROSS_ASSET_RAIL, req);
1520
+ const wantAsset = parseAssetId(asset.assetId);
2250
1521
  if (!wantAsset) {
2251
1522
  throw new Error(
2252
- `${CROSS_ASSET_RAIL}: ${asset2.assetId} is not an asset id (expected 34 bytes of hex)`
1523
+ `${CROSS_ASSET_RAIL}: ${asset.assetId} is not an asset id (expected 34 bytes of hex)`
2253
1524
  );
2254
1525
  }
2255
- const planned = await planFor(asset2);
1526
+ const planned = await planFor(asset);
2256
1527
  if (!planned) {
2257
- throw new Error(`${CROSS_ASSET_RAIL}: no market swaps BTC into ${asset2.assetId}`);
1528
+ throw new Error(`${CROSS_ASSET_RAIL}: no market swaps BTC into ${asset.assetId}`);
2258
1529
  }
2259
1530
  if (planned.error) {
2260
1531
  throw new Error(
2261
- `${CROSS_ASSET_RAIL}: cannot swap into ${asset2.assetId} \u2014 ${planned.error}`
1532
+ `${CROSS_ASSET_RAIL}: cannot swap into ${asset.assetId} \u2014 ${planned.error}`
2262
1533
  );
2263
1534
  }
2264
1535
  const { plan } = planned;
@@ -2278,7 +1549,7 @@ function crossAssetRail(deps) {
2278
1549
  fee: depositSats,
2279
1550
  total: depositSats + carrier,
2280
1551
  assets: {
2281
- delivered: asset2,
1552
+ delivered: asset,
2282
1553
  spent: { assetId: BTC_ASSET_ID, amount: plan.deposit.atomic }
2283
1554
  },
2284
1555
  meta: {
@@ -2288,7 +1559,7 @@ function crossAssetRail(deps) {
2288
1559
  },
2289
1560
  send: async () => makeHandle3(CROSS_ASSET_RAIL, async (emit) => {
2290
1561
  const offer = await createOffer(ctx.wallet, deps.arkServerUrl, {
2291
- wantAmount: asset2.amount,
1562
+ wantAmount: asset.amount,
2292
1563
  wantAsset,
2293
1564
  ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
2294
1565
  });
@@ -2298,7 +1569,7 @@ function crossAssetRail(deps) {
2298
1569
  address: offer.address,
2299
1570
  swapPkScript: offer.swapPkScript,
2300
1571
  depositSats,
2301
- asset: asset2,
1572
+ asset,
2302
1573
  payTo,
2303
1574
  plan
2304
1575
  };
@@ -2315,7 +1586,7 @@ function crossAssetRail(deps) {
2315
1586
  const txid = await ctx.wallet.send({
2316
1587
  address: payTo,
2317
1588
  amount: carrier,
2318
- assets: [asset2]
1589
+ assets: [asset]
2319
1590
  });
2320
1591
  await deps.persist({ ...swap, phase: "settled", txid });
2321
1592
  const result = {
@@ -2332,10 +1603,10 @@ function crossAssetRail(deps) {
2332
1603
  }
2333
1604
 
2334
1605
  // src/reveal.ts
2335
- import { base64 as base643, hex as hex9 } from "@scure/base";
1606
+ import { base64 as base643, hex as hex7 } from "@scure/base";
2336
1607
  import { ripemd160 } from "@noble/hashes/legacy.js";
2337
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
2338
- import { ArkAddress as ArkAddress4 } from "@arkade-os/sdk";
1608
+ import { sha256 } from "@noble/hashes/sha2.js";
1609
+ import { ArkAddress as ArkAddress3 } from "@arkade-os/sdk";
2339
1610
  var CovclaimdRevealError = class extends Error {
2340
1611
  name = "CovclaimdRevealError";
2341
1612
  status;
@@ -2352,7 +1623,7 @@ var decodeKey = (value, field) => {
2352
1623
  if (typeof value !== "string") throw new Error(`covclaimd info: ${field} is not a string`);
2353
1624
  let bytes;
2354
1625
  try {
2355
- bytes = hex9.decode(value);
1626
+ bytes = hex7.decode(value);
2356
1627
  } catch {
2357
1628
  throw new Error(`covclaimd info: ${field} is not hex`);
2358
1629
  }
@@ -2412,7 +1683,7 @@ var covclaimdClient = (baseUrl, options = {}) => {
2412
1683
  const body = await readJson("info", response);
2413
1684
  return {
2414
1685
  covclaimdPubkey: decodeKey(body?.covclaimd_pub_key, "covclaimd_pub_key"),
2415
- emulatorPubkey: hex9.encode(decodeKey(body?.emulator_pub_key, "emulator_pub_key"))
1686
+ emulatorPubkey: hex7.encode(decodeKey(body?.emulator_pub_key, "emulator_pub_key"))
2416
1687
  };
2417
1688
  },
2418
1689
  async reveal(params) {
@@ -2434,15 +1705,15 @@ function revealFieldsFromScript(script) {
2434
1705
  }
2435
1706
  return {
2436
1707
  arkadeScript: base643.encode(script.nonInteractiveClaimArkadeScript),
2437
- taptree: hex9.encode(script.encode())
1708
+ taptree: hex7.encode(script.encode())
2438
1709
  };
2439
1710
  }
2440
1711
  async function revealClaimPacket(client, input) {
2441
1712
  const fields = revealFieldsFromScript(input.script);
2442
- if (hex9.encode(ArkAddress4.decode(input.address).pkScript) !== hex9.encode(input.script.pkScript)) {
1713
+ if (hex7.encode(ArkAddress3.decode(input.address).pkScript) !== hex7.encode(input.script.pkScript)) {
2443
1714
  throw new Error("address does not belong to this covenant");
2444
1715
  }
2445
- if (hex9.encode(ripemd160(sha2562(input.preimage))) !== hex9.encode(input.script.options.preimageHash)) {
1716
+ if (hex7.encode(ripemd160(sha256(input.preimage))) !== hex7.encode(input.script.options.preimageHash)) {
2446
1717
  throw new Error("preimage does not match the covenant's payment hash");
2447
1718
  }
2448
1719
  const { ciphertext } = await sealClaimPacket({
@@ -2454,9 +1725,9 @@ async function revealClaimPacket(client, input) {
2454
1725
  }
2455
1726
 
2456
1727
  // src/claim.ts
2457
- import { hex as hex11 } from "@scure/base";
1728
+ import { hex as hex9 } from "@scure/base";
2458
1729
  import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
2459
- import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
1730
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
2460
1731
  import {
2461
1732
  CSVMultisigTapscript as CSVMultisigTapscript2,
2462
1733
  claimWithPreimageIdentity,
@@ -2464,8 +1735,8 @@ import {
2464
1735
  } from "@arkade-os/sdk";
2465
1736
 
2466
1737
  // src/refund.ts
2467
- import { base64 as base644, hex as hex10 } from "@scure/base";
2468
- import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1738
+ import { base64 as base644, hex as hex8 } from "@scure/base";
1739
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
2469
1740
  import {
2470
1741
  CSVMultisigTapscript,
2471
1742
  ConditionWitness,
@@ -2522,7 +1793,7 @@ var LockupNeedsRecoveryError = class extends Error {
2522
1793
  }
2523
1794
  };
2524
1795
  async function findLockupVtxos(indexer, swapPkScript) {
2525
- const scripts = [hex10.encode(swapPkScript)];
1796
+ const scripts = [hex8.encode(swapPkScript)];
2526
1797
  const [spendable, recoverable] = await Promise.all([
2527
1798
  indexer.getVtxos({ scripts, spendableOnly: true }),
2528
1799
  indexer.getVtxos({ scripts, recoverableOnly: true })
@@ -2548,13 +1819,13 @@ async function findLockupVtxos(indexer, swapPkScript) {
2548
1819
  }
2549
1820
  return out;
2550
1821
  }
2551
- var hashesTo = (candidate, paymentHash) => hex10.encode(sha2563(candidate)) === paymentHash;
1822
+ var hashesTo = (candidate, paymentHash) => hex8.encode(sha2562(candidate)) === paymentHash;
2552
1823
  var candidateWitnessItems = (tx, inputIndex) => [
2553
1824
  ...getArkPsbtFields(tx, inputIndex, ConditionWitness).flat(),
2554
1825
  ...tx.getInput(inputIndex).finalScriptWitness ?? []
2555
1826
  ];
2556
1827
  async function readLockupFate(indexer, input) {
2557
- const { vtxos } = await indexer.getVtxos({ scripts: [hex10.encode(input.swapPkScript)] });
1828
+ const { vtxos } = await indexer.getVtxos({ scripts: [hex8.encode(input.swapPkScript)] });
2558
1829
  const all = vtxos ?? [];
2559
1830
  if (all.length === 0) return { fate: "unknown" };
2560
1831
  const exited = all.filter((vtxo) => vtxo.isUnrolled && !hasTerminalSpend(vtxo));
@@ -2589,7 +1860,7 @@ async function readLockupFate(indexer, input) {
2589
1860
  for (let i = 0; i < tx.inputsLength; i++) {
2590
1861
  const spent = tx.getInput(i);
2591
1862
  if (!spent.txid) continue;
2592
- const txid = hex10.encode(spent.txid);
1863
+ const txid = hex8.encode(spent.txid);
2593
1864
  if (!all.some((vtxo) => vtxo.txid === txid && vtxo.vout === spent.index)) continue;
2594
1865
  for (const candidate of candidateWitnessItems(tx, i)) {
2595
1866
  if (hashesTo(candidate, input.paymentHash)) {
@@ -2618,7 +1889,7 @@ async function pushRefundWithoutReceiver(ark, input) {
2618
1889
  const info = await ark.getInfo();
2619
1890
  let serverUnrollScript;
2620
1891
  try {
2621
- serverUnrollScript = CSVMultisigTapscript.decode(hex10.decode(info.checkpointTapscript));
1892
+ serverUnrollScript = CSVMultisigTapscript.decode(hex8.decode(info.checkpointTapscript));
2622
1893
  } catch {
2623
1894
  throw new Error("invalid checkpointTapscript from the Arkade server");
2624
1895
  }
@@ -2744,13 +2015,13 @@ async function pushClaim(ark, input) {
2744
2015
  }
2745
2016
  }
2746
2017
  const committed = input.script.options.preimageHash;
2747
- if (hex11.encode(ripemd1602(sha2564(input.preimage))) !== hex11.encode(committed)) {
2018
+ if (hex9.encode(ripemd1602(sha2563(input.preimage))) !== hex9.encode(committed)) {
2748
2019
  throw new Error("preimage does not match the covenant's payment hash");
2749
2020
  }
2750
2021
  const info = await ark.getInfo();
2751
2022
  let serverUnrollScript;
2752
2023
  try {
2753
- serverUnrollScript = CSVMultisigTapscript2.decode(hex11.decode(info.checkpointTapscript));
2024
+ serverUnrollScript = CSVMultisigTapscript2.decode(hex9.decode(info.checkpointTapscript));
2754
2025
  } catch {
2755
2026
  throw new Error("invalid checkpointTapscript from the Arkade server");
2756
2027
  }
@@ -2869,7 +2140,7 @@ function arkadeRefunder(deps) {
2869
2140
  }
2870
2141
 
2871
2142
  // src/swapManager.ts
2872
- import { hex as hex12 } from "@scure/base";
2143
+ import { hex as hex10 } from "@scure/base";
2873
2144
  import {
2874
2145
  isContractVtxoEvent as isContractVtxoEvent2
2875
2146
  } from "@arkade-os/sdk";
@@ -3320,7 +2591,7 @@ var RfqSwapManager = class {
3320
2591
  // ── internals ────────────────────────────────────────────────────────────
3321
2592
  track(swap) {
3322
2593
  this.monitored.set(swap.rfqId, swap);
3323
- this.byLockupScript.set(hex12.encode(swap.lockupPkScript), swap);
2594
+ this.byLockupScript.set(hex10.encode(swap.lockupPkScript), swap);
3324
2595
  }
3325
2596
  /** Drops the swap from BOTH indexes. The event index is the one that stops
3326
2597
  * a late event finding a swap that is gone; `pollSwap`'s own
@@ -3329,7 +2600,7 @@ var RfqSwapManager = class {
3329
2600
  * them from silently re-driving a cancelled swap. */
3330
2601
  untrack(rfqId) {
3331
2602
  const swap = this.monitored.get(rfqId);
3332
- if (swap) this.byLockupScript.delete(hex12.encode(swap.lockupPkScript));
2603
+ if (swap) this.byLockupScript.delete(hex10.encode(swap.lockupPkScript));
3333
2604
  this.monitored.delete(rfqId);
3334
2605
  this.refundRefused.delete(rfqId);
3335
2606
  this.lastClaimError.delete(rfqId);
@@ -3392,7 +2663,7 @@ var RfqSwapManager = class {
3392
2663
  if (!lockup) {
3393
2664
  try {
3394
2665
  const [existing] = await contracts.getContracts({
3395
- script: hex12.encode(swap.lockupPkScript)
2666
+ script: hex10.encode(swap.lockupPkScript)
3396
2667
  });
3397
2668
  if (existing) {
3398
2669
  this.registered.set(swap.rfqId, true);
@@ -3411,13 +2682,13 @@ var RfqSwapManager = class {
3411
2682
  );
3412
2683
  return;
3413
2684
  }
3414
- const script = hex12.encode(lockup.script.pkScript);
3415
- if (script !== hex12.encode(swap.lockupPkScript)) {
2685
+ const script = hex10.encode(lockup.script.pkScript);
2686
+ if (script !== hex10.encode(swap.lockupPkScript)) {
3416
2687
  this.registered.set(swap.rfqId, false);
3417
2688
  this.emitFailed(
3418
2689
  swap,
3419
2690
  new Error(
3420
- `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex12.encode(swap.lockupPkScript)}`
2691
+ `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex10.encode(swap.lockupPkScript)}`
3421
2692
  )
3422
2693
  );
3423
2694
  return;
@@ -3436,7 +2707,7 @@ var RfqSwapManager = class {
3436
2707
  * script for its whole life. Best-effort — the swap is over either way. */
3437
2708
  retireContract(swap) {
3438
2709
  if (!this.deps.contracts || !this.registered.get(swap.rfqId)) return;
3439
- void this.deps.contracts.setContractWatchState(hex12.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
2710
+ void this.deps.contracts.setContractWatchState(hex10.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
3440
2711
  }
3441
2712
  arm() {
3442
2713
  if (!this.running) return;
@@ -3636,6 +2907,21 @@ var RfqSwapManager = class {
3636
2907
  });
3637
2908
  if (action === "claim" && phase.phase === "claimable") {
3638
2909
  if (swap.claimTxid) return "continue";
2910
+ if (!Number.isSafeInteger(swap.expectedAmount) || swap.expectedAmount <= 0) {
2911
+ this.block(
2912
+ swap,
2913
+ `expectedAmount is not a positive number of sats (${String(swap.expectedAmount)}), so the funded value cannot be checked \u2014 refusing to publish the preimage`
2914
+ );
2915
+ return "handled";
2916
+ }
2917
+ const filled = Number(phase.utxo.amount);
2918
+ if (!Number.isFinite(filled) || filled < swap.expectedAmount) {
2919
+ this.block(
2920
+ swap,
2921
+ `fill holds ${filled} sats, below the agreed ${swap.expectedAmount} \u2014 refusing to publish the preimage`
2922
+ );
2923
+ return "handled";
2924
+ }
3639
2925
  if (this.callbacks && !this.callbacks.claimOnchain) {
3640
2926
  this.block(
3641
2927
  swap,
@@ -3689,6 +2975,8 @@ var RfqSwapManager = class {
3689
2975
  if (pushed) {
3690
2976
  swap.refundArkTxid = pushed.arkTxid;
3691
2977
  this.touch(swap);
2978
+ } else if (now < swap.refundLocktime + REFUND_MTP_LAG_SECONDS) {
2979
+ return;
3692
2980
  }
3693
2981
  this.setState(swap, "refunded");
3694
2982
  this.emitAction(swap, "refundArkade");
@@ -3910,9 +3198,9 @@ var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
3910
3198
 
3911
3199
  // src/activity.ts
3912
3200
  import {
3913
- ArkAddress as ArkAddress5
3201
+ ArkAddress as ArkAddress4
3914
3202
  } from "@arkade-os/sdk";
3915
- import { hex as hex13 } from "@scure/base";
3203
+ import { hex as hex11 } from "@scure/base";
3916
3204
  var LABELS = {
3917
3205
  lightning_send: "Lightning send",
3918
3206
  lightning_receive: "Lightning receive",
@@ -3987,7 +3275,7 @@ async function activityInputOf(record, indexer) {
3987
3275
  async function lockupTxids(indexer, record, wantFunding) {
3988
3276
  let script;
3989
3277
  try {
3990
- script = hex13.encode(ArkAddress5.decode(record.lockupAddress).pkScript);
3278
+ script = hex11.encode(ArkAddress4.decode(record.lockupAddress).pkScript);
3991
3279
  } catch {
3992
3280
  return [];
3993
3281
  }
@@ -4058,6 +3346,7 @@ export {
4058
3346
  arkadeAssetLeg,
4059
3347
  arkadeRefunder,
4060
3348
  arkadeSwapRequest,
3349
+ assertArkadeFundable,
4061
3350
  assertFundable,
4062
3351
  assertReceivable,
4063
3352
  awaitLockupFunding,
@@ -4066,6 +3355,7 @@ export {
4066
3355
  buildHtlcClaim,
4067
3356
  buildHtlcRefund,
4068
3357
  cancelOffer,
3358
+ canonicalAssetAmount,
4069
3359
  chainSourceFrom,
4070
3360
  claimOnchainFill,
4071
3361
  claimPacketShape,
@@ -4106,6 +3396,8 @@ export {
4106
3396
  newPreimage,
4107
3397
  newRfqId,
4108
3398
  nextOnchainAction,
3399
+ normalizeMakerPkScript,
3400
+ normalizeMakerPublicKey,
4109
3401
  offerTermsFromQuote,
4110
3402
  offerVtxoScript,
4111
3403
  onchainHtlcScript,
@@ -4123,6 +3415,7 @@ export {
4123
3415
  registerAssetSwapRestore,
4124
3416
  registerLockupContract,
4125
3417
  relayTransport,
3418
+ requestArkadeSwap,
4126
3419
  requestLightningReceive,
4127
3420
  requestLightningSend,
4128
3421
  requestOnchainReceive,
@@ -4160,6 +3453,7 @@ export {
4160
3453
  updateRfqSwapRecord,
4161
3454
  validatePlan,
4162
3455
  verifyLockupAddress,
3456
+ verifyOfferAddress,
4163
3457
  verifyReceiveInvoice,
4164
3458
  watchOfferSwaps
4165
3459
  };