@arkade-os/swap 0.0.10 → 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/dist/index.cjs CHANGED
@@ -89,6 +89,7 @@ __export(index_exports, {
89
89
  classifySpend: () => classifySpend,
90
90
  createOffer: () => createOffer,
91
91
  createRfqSwapRecord: () => createRfqSwapRecord,
92
+ createSwapClient: () => createSwapClient,
92
93
  decodeOffer: () => decodeOffer,
93
94
  deriveLightningReceive: () => deriveLightningReceive,
94
95
  deriveOnchainReceive: () => deriveOnchainReceive,
@@ -103,16 +104,18 @@ __export(index_exports, {
103
104
  httpTransport: () => httpTransport,
104
105
  isRfqSwapTerminal: () => isRfqSwapTerminal,
105
106
  isRfqTerminal: () => isRfqTerminal,
107
+ lightningReceiveContract: () => lightningReceiveContract,
106
108
  lightningReceiveRequest: () => lightningReceiveRequest,
109
+ lightningSendContract: () => lightningSendContract,
107
110
  lightningSendRequest: () => lightningSendRequest,
108
- lightningSendVtxoScript: () => lightningSendVtxoScript,
109
111
  lockupContractParams: () => lockupContractParams,
110
112
  makeCachedFeedFetch: () => makeCachedFeedFetch,
111
113
  newPreimage: () => newPreimage,
112
114
  newRfqId: () => newRfqId,
113
115
  nextOnchainAction: () => nextOnchainAction,
116
+ normalizeRfqSwapRecord: () => normalizeRfqSwapRecord,
117
+ offerContract: () => offerContract,
114
118
  offerTermsFromQuote: () => offerTermsFromQuote,
115
- offerVtxoScript: () => offerVtxoScript,
116
119
  onchainHtlcScript: () => onchainHtlcScript,
117
120
  onchainReceiveRequest: () => onchainReceiveRequest,
118
121
  onchainSendProfile: () => onchainSendProfile,
@@ -123,7 +126,6 @@ __export(index_exports, {
123
126
  pushRefundWithoutReceiver: () => pushRefundWithoutReceiver,
124
127
  readLockupFate: () => readLockupFate,
125
128
  rebuildRfqSwap: () => rebuildRfqSwap,
126
- receiveVtxoScript: () => receiveVtxoScript,
127
129
  refundIfUnresolved: () => refundIfUnresolved,
128
130
  registerLockupContract: () => registerLockupContract,
129
131
  relayTransport: () => relayTransport,
@@ -411,32 +413,51 @@ var swapPrograms = {
411
413
  wantAsset: import_sdk2.arkade.parseArtifact(swap_want_asset_program_default),
412
414
  wantBtc: import_sdk2.arkade.parseArtifact(swap_want_btc_program_default)
413
415
  };
414
- function swapProgramBinding(offer, serverPubkey) {
416
+ function withExitClosure(program, exit) {
417
+ if (!exit) return program;
418
+ return {
419
+ ...program,
420
+ // typed params are authoritative: an undeclared `$exitDelay` fails
421
+ // validateProgram instead of compiling against an unbound value
422
+ params: [...program.params ?? [], { name: "exitDelay", type: "int" }],
423
+ functions: {
424
+ ...program.functions,
425
+ exit: {
426
+ tapscript: { signers: ["$user"], csv: { type: exit.type, value: "$exitDelay" } }
427
+ }
428
+ }
429
+ };
430
+ }
431
+ function swapProgramBinding(offer, operatorPubkey) {
415
432
  if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
416
433
  throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
417
434
  }
418
435
  return {
419
- program: offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
436
+ program: withExitClosure(
437
+ offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
438
+ offer.exitDelay
439
+ ),
420
440
  args: {
421
441
  makerWP: offer.makerPkScript.subarray(2),
422
442
  wantAmount: offer.wantAmount,
423
- server: serverPubkey,
443
+ server: operatorPubkey,
424
444
  user: offer.makerPublicKey,
425
445
  // internal byte order
426
446
  ...offer.wantAsset && {
427
447
  wantAssetTxid: offer.wantAsset.txid.slice().reverse(),
428
448
  wantAssetGroupIndex: offer.wantAsset.groupIndex
429
- }
449
+ },
450
+ ...offer.exitDelay && { exitDelay: offer.exitDelay.value }
430
451
  },
431
452
  keys: {
432
- serverKey: serverPubkey,
453
+ serverKey: operatorPubkey,
433
454
  userKey: offer.makerPublicKey,
434
455
  emulatorKey: offer.emulatorPubkey
435
456
  }
436
457
  };
437
458
  }
438
- function offerVtxoScript(offer, serverPubkey) {
439
- const { program, args, keys } = swapProgramBinding(offer, serverPubkey);
459
+ function offerContract(offer, operatorPubkey) {
460
+ const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
440
461
  return new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
441
462
  }
442
463
  var OFFER_PACKET_TYPE = 3;
@@ -447,9 +468,27 @@ var FIELDS = {
447
468
  makerPkScript: { tag: 5, width: 34 },
448
469
  makerPublicKey: { tag: 7, width: 32 },
449
470
  emulatorPubkey: { tag: 8, width: 32 },
450
- offerAsset: { tag: 11, width: void 0 }
471
+ ratioNum: { tag: 9, width: 8 },
472
+ ratioDen: { tag: 10, width: 8 },
473
+ offerAsset: { tag: 11, width: void 0 },
474
+ exitTimelock: { tag: 12, width: 9 }
451
475
  };
476
+ var EXIT_TYPES = ["blocks", "seconds"];
452
477
  var NAMES = Object.fromEntries(Object.entries(FIELDS).map(([k, f]) => [f.tag, k]));
478
+ function u64(name, value) {
479
+ if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
480
+ throw new Error(`${name} does not fit the offer wire format (u64)`);
481
+ }
482
+ const out = new Uint8Array(FIELDS.wantAmount.width);
483
+ new DataView(out.buffer).setBigUint64(0, value, false);
484
+ return out;
485
+ }
486
+ var readU64 = (value) => new DataView(value.buffer, value.byteOffset).getBigUint64(0, false);
487
+ var setRatio = (name, value) => {
488
+ if (value === void 0 || value === BigInt(0)) return void 0;
489
+ if (value < BigInt(0)) throw new Error(`${name} does not fit the offer wire format (u64)`);
490
+ return value;
491
+ };
453
492
  function tlv(type, value) {
454
493
  if (value.length > 65535) throw new Error("TLV value exceeds the u16 length field");
455
494
  return (0, import_utils.concatBytes)(Uint8Array.of(type, value.length >> 8 & 255, value.length & 255), value);
@@ -468,24 +507,45 @@ function encodeOffer(offer) {
468
507
  throw new Error(`${name} must be ${FIELDS[name].width} bytes`);
469
508
  }
470
509
  }
471
- if (offer.wantAmount < BigInt(0) || offer.wantAmount >> BigInt(64) > BigInt(0)) {
472
- throw new Error("wantAmount does not fit the offer wire format (u64)");
510
+ const ratioNum = setRatio("ratioNum", offer.ratioNum);
511
+ const ratioDen = setRatio("ratioDen", offer.ratioDen);
512
+ if (ratioNum === void 0 !== (ratioDen === void 0)) {
513
+ throw new Error("offer must carry both ratioNum and ratioDen, or neither");
473
514
  }
474
- const amount = new Uint8Array(FIELDS.wantAmount.width);
475
- new DataView(amount.buffer).setBigUint64(0, offer.wantAmount, false);
476
515
  const recs = [
477
516
  tlv(FIELDS.swapPkScript.tag, offer.swapPkScript),
478
- tlv(FIELDS.wantAmount.tag, amount)
517
+ tlv(FIELDS.wantAmount.tag, u64("wantAmount", offer.wantAmount))
479
518
  ];
480
519
  if (offer.wantAsset) recs.push(tlv(FIELDS.wantAsset.tag, offer.wantAsset.serialize()));
520
+ if (ratioNum !== void 0) recs.push(tlv(FIELDS.ratioNum.tag, u64("ratioNum", ratioNum)));
521
+ if (ratioDen !== void 0) recs.push(tlv(FIELDS.ratioDen.tag, u64("ratioDen", ratioDen)));
481
522
  if (offer.offerAsset) recs.push(tlv(FIELDS.offerAsset.tag, offer.offerAsset.serialize()));
482
523
  recs.push(
483
524
  tlv(FIELDS.makerPkScript.tag, offer.makerPkScript),
484
525
  tlv(FIELDS.makerPublicKey.tag, offer.makerPublicKey),
485
526
  tlv(FIELDS.emulatorPubkey.tag, offer.emulatorPubkey)
486
527
  );
528
+ if (offer.exitDelay) recs.push(tlv(FIELDS.exitTimelock.tag, encodeExitDelay(offer.exitDelay)));
487
529
  return (0, import_utils.concatBytes)(...recs);
488
530
  }
531
+ function encodeExitDelay(exit) {
532
+ return (0, import_utils.concatBytes)(
533
+ Uint8Array.of(EXIT_TYPES.indexOf(assertExitDelay(exit).type)),
534
+ u64("exitDelay", exit.value)
535
+ );
536
+ }
537
+ function assertExitDelay(exit) {
538
+ if (EXIT_TYPES.indexOf(exit.type) < 0) {
539
+ throw new Error(`unknown exitDelay locktime type: ${exit.type}`);
540
+ }
541
+ if (exit.value <= BigInt(0)) {
542
+ throw new Error("exitDelay must be a positive relative locktime");
543
+ }
544
+ if (exit.value >> BigInt(32) > BigInt(0)) {
545
+ throw new Error("exitDelay does not fit the locktime field (u32)");
546
+ }
547
+ return exit;
548
+ }
489
549
  function decodeOffer(data) {
490
550
  const fields = {};
491
551
  let off = 0;
@@ -505,40 +565,70 @@ function decodeOffer(data) {
505
565
  for (const name of ["wantAsset", "offerAsset"]) {
506
566
  if (fields[name]?.length === 0) throw new Error(`missing/invalid ${name}`);
507
567
  }
508
- const need = (name) => {
568
+ for (const [name, value] of Object.entries(fields)) {
569
+ const width = FIELDS[name].width;
570
+ if (width !== void 0 && value.length !== width) {
571
+ throw new Error(`missing/invalid ${name}`);
572
+ }
573
+ }
574
+ const need2 = (name) => {
509
575
  const v = fields[name];
510
576
  const len = FIELDS[name].width;
511
577
  if (!v || len !== void 0 && v.length !== len)
512
578
  throw new Error(`missing/invalid ${name}`);
513
579
  return v;
514
580
  };
515
- const amount = need("wantAmount");
581
+ const amount = need2("wantAmount");
516
582
  if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
517
583
  throw new Error("offer must carry exactly one of wantAsset or offerAsset");
518
584
  }
585
+ const readRatio = (name) => {
586
+ const raw = fields[name];
587
+ if (!raw) return void 0;
588
+ const value = readU64(raw);
589
+ if (value === BigInt(0)) throw new Error(`missing/invalid ${name}`);
590
+ return value;
591
+ };
592
+ const ratioNum = readRatio("ratioNum");
593
+ const ratioDen = readRatio("ratioDen");
594
+ if (ratioNum === void 0 !== (ratioDen === void 0)) {
595
+ throw new Error("offer must carry both ratioNum and ratioDen, or neither");
596
+ }
519
597
  return {
520
- swapPkScript: need("swapPkScript"),
521
- wantAmount: new DataView(amount.buffer, amount.byteOffset).getBigUint64(0, false),
598
+ swapPkScript: need2("swapPkScript"),
599
+ wantAmount: readU64(amount),
522
600
  ...fields.wantAsset && { wantAsset: import_sdk2.asset.AssetId.fromBytes(fields.wantAsset) },
523
601
  ...fields.offerAsset && { offerAsset: import_sdk2.asset.AssetId.fromBytes(fields.offerAsset) },
524
- makerPkScript: need("makerPkScript"),
525
- makerPublicKey: need("makerPublicKey"),
526
- emulatorPubkey: need("emulatorPubkey")
602
+ makerPkScript: need2("makerPkScript"),
603
+ makerPublicKey: need2("makerPublicKey"),
604
+ emulatorPubkey: need2("emulatorPubkey"),
605
+ ...ratioNum !== void 0 && { ratioNum },
606
+ ...ratioDen !== void 0 && { ratioDen },
607
+ ...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
527
608
  };
528
609
  }
610
+ function decodeExitDelay(value) {
611
+ const type = EXIT_TYPES[value[0]];
612
+ if (!type) throw new Error(`unknown exitDelay locktime type: 0x${value[0].toString(16)}`);
613
+ return { type, value: readU64(value.subarray(1)) };
614
+ }
529
615
  var OFFER_CONTRACT_LABEL = "Arkade swap offer";
530
616
  var OFFER_CONTRACT_KIND = "asset-swap-offer";
531
- async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript) {
532
- const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
617
+ async function registerOfferContract(wallet, info, binding, operatorPubkey, expectedPkScript) {
618
+ const { program, args, keys } = swapProgramBinding(binding, operatorPubkey);
533
619
  const contractManager = await wallet.getContractManager();
534
620
  const client = await import_sdk2.arkade.Arkade.connect({
535
- arkade: new import_sdk2.RestArkProvider(arkServerUrl),
536
- indexer: new import_sdk2.RestIndexerProvider(arkServerUrl),
621
+ // Registration derives and persists; it never broadcasts and never
622
+ // reads UTXOs, so the only thing the client needs off the server is the
623
+ // info the caller already resolved. Handing that back — rather than a
624
+ // provider built from a URL — is what lets `createOffer` take just a
625
+ // wallet, and spares a second `/v1/info` round-trip.
626
+ arkade: { getInfo: async () => info },
537
627
  identity: wallet.identity,
538
628
  // without this the row's `address` would be derived against the SDK's
539
629
  // default network while its script is right — a row that disagrees with
540
630
  // the address the user is about to fund
541
- network: (0, import_sdk2.getNetwork)(network),
631
+ network: (0, import_sdk2.networkFromArkadeInfo)(info),
542
632
  contractManager
543
633
  });
544
634
  const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
@@ -551,17 +641,29 @@ async function registerOfferContract(wallet, arkServerUrl, network, binding, ser
551
641
  });
552
642
  await promoteOfferContract(contractManager, import_base2.hex.encode(expectedPkScript));
553
643
  }
554
- async function createOffer(wallet, arkServerUrl, params) {
644
+ function serverExitDelay(delay) {
645
+ if (typeof delay !== "bigint" || delay <= BigInt(0)) {
646
+ throw new Error(
647
+ "the server reports no usable unilateralExitDelay; pass `exitDelay` to set the offer's exit closure explicitly, or `noExit: true` to publish without one"
648
+ );
649
+ }
650
+ return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
651
+ }
652
+ async function createOffer(wallet, params) {
555
653
  if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
556
654
  throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
557
655
  }
558
656
  const [info, makerAddress, makerPublicKey] = await Promise.all([
559
- new import_sdk2.RestArkProvider(arkServerUrl).getInfo(),
657
+ // requireLive: this info binds signerPubkey into the covenant — a
658
+ // snapshot could derive an address the operator no longer co-signs
659
+ // for, so an unreachable operator fails the call instead (fail closed,
660
+ // the same behaviour the pre-#734 caller-held provider had)
661
+ wallet.getArkadeInfo({ requireLive: true }),
560
662
  wallet.getAddress(),
561
663
  wallet.identity.xOnlyPublicKey()
562
664
  ]);
563
- const serverPubKey = import_base2.hex.decode((0, import_sdk2.toXOnlySignerHex)(info.signerPubkey));
564
- const network = (0, import_sdk2.getNetwork)(info.network);
665
+ const operatorPubKey = import_base2.hex.decode((0, import_sdk2.toXOnlySignerHex)(info.signerPubkey));
666
+ const network = (0, import_sdk2.networkFromArkadeInfo)(info);
565
667
  const emuKey = import_base2.hex.decode(
566
668
  (0, import_sdk2.toXOnlySignerHex)((0, import_sdk2.resolveEmulatorPubkey)(network, params.emulatorPubkey))
567
669
  );
@@ -571,35 +673,39 @@ async function createOffer(wallet, arkServerUrl, params) {
571
673
  offerAsset: params.offerAsset,
572
674
  makerPkScript: import_sdk2.ArkAddress.decode(makerAddress).pkScript,
573
675
  makerPublicKey,
574
- emulatorPubkey: emuKey
676
+ emulatorPubkey: emuKey,
677
+ // checked HERE, before the covenant is derived and registered below:
678
+ // deferring it to `encodeOffer` leaves a registered contract behind for
679
+ // an offer that then fails to encode. @see assertExitDelay
680
+ exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
575
681
  };
576
- const script = offerVtxoScript(binding, serverPubKey);
682
+ const script = offerContract(binding, operatorPubKey);
577
683
  const offer = { ...binding, swapPkScript: script.pkScript };
578
- await registerOfferContract(
579
- wallet,
580
- arkServerUrl,
581
- info.network,
582
- binding,
583
- serverPubKey,
584
- script.pkScript
585
- );
684
+ await registerOfferContract(wallet, info, binding, operatorPubKey, script.pkScript);
586
685
  const payload = encodeOffer(offer);
587
686
  return {
588
687
  offerHex: import_base2.hex.encode(payload),
589
688
  extension: { type: OFFER_PACKET_TYPE, payload },
590
- // VtxoScript.address owns address construction; assembling an ArkAddress
689
+ // the contract's .address() builds the address; assembling an ArkAddress
591
690
  // from tweakedPublicKey here would silently miss any future step it gains
592
- address: script.address(network.hrp, serverPubKey).encode(),
691
+ address: script.address(network.hrp, operatorPubKey).encode(),
593
692
  swapPkScript: script.pkScript
594
693
  };
595
694
  }
596
- async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
695
+ async function cancelOffer(wallet, offerHex, opts) {
597
696
  const { repository, fundingTxid, swapAddress } = opts;
598
697
  const offer = decodeOffer(import_base2.hex.decode(offerHex));
599
- const contractManager = await wallet.getContractManager();
698
+ const [contractManager, info, reader, broadcaster] = await Promise.all([
699
+ wallet.getContractManager(),
700
+ wallet.getArkadeInfo({ requireLive: true }),
701
+ wallet.getArkadeReader(),
702
+ wallet.getArkadeBroadcaster()
703
+ ]);
600
704
  const client = await import_sdk2.arkade.Arkade.connect({
601
- arkade: new import_sdk2.RestArkProvider(arkServerUrl),
602
- indexer: new import_sdk2.RestIndexerProvider(arkServerUrl),
705
+ // info the wallet already holds, plus its broadcast pair — the shape
706
+ // `ArkadeServerProvider` describes, without a second `/v1/info`
707
+ arkade: { getInfo: async () => info, ...broadcaster },
708
+ indexer: reader,
603
709
  identity: wallet.identity,
604
710
  // registered offers resolve their VTXOs from the contract repository
605
711
  // instead of a direct indexer query; the indexer above stays as the
@@ -614,7 +720,7 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
614
720
  const rebuilt = new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
615
721
  if (import_base2.hex.encode(rebuilt.pkScript) !== import_base2.hex.encode(offer.swapPkScript)) {
616
722
  throw new Error(
617
- "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"
723
+ "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"
618
724
  );
619
725
  }
620
726
  const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
@@ -1278,7 +1384,7 @@ var LightningReceiveCorridor = {
1278
1384
  const receive = swap;
1279
1385
  return {
1280
1386
  expectedAmount: receive.expectedAmount,
1281
- ...receive.claimArkTxid ? { claimArkTxid: receive.claimArkTxid } : {}
1387
+ ...receive.claimTxid ? { claimTxid: receive.claimTxid } : {}
1282
1388
  };
1283
1389
  },
1284
1390
  hydrate(profile) {
@@ -1288,13 +1394,13 @@ var LightningReceiveCorridor = {
1288
1394
  return {
1289
1395
  ...hydrateHashlock(profile),
1290
1396
  expectedAmount: profile.expectedAmount,
1291
- ...profile.claimArkTxid ? { claimArkTxid: profile.claimArkTxid } : {}
1397
+ ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1292
1398
  };
1293
1399
  },
1294
1400
  // We are the claimant here, so the preimage material on the hashlock is
1295
1401
  // ours to use.
1296
1402
  claimSecret: (profile) => ({ ...profile.signer, ...profile.hashlock }),
1297
- activityTxids: (profile) => profile.claimArkTxid ? [profile.claimArkTxid] : []
1403
+ activityTxids: (profile) => profile.claimTxid ? [profile.claimTxid] : []
1298
1404
  };
1299
1405
  function onchainSendProfile(result) {
1300
1406
  return {
@@ -1378,13 +1484,32 @@ var isRfqSwapTerminal = (state) => RFQ_SWAP_TERMINAL_STATES.includes(state);
1378
1484
 
1379
1485
  // src/rfqRecord.ts
1380
1486
  var RFQ_SWAP_RETENTION_SECONDS = 30 * 24 * 60 * 60;
1487
+ function renameLegacyClaimTxid(profile) {
1488
+ const { claimArkTxid, ...rest } = profile;
1489
+ return { ...rest, claimTxid: rest.claimTxid ?? claimArkTxid };
1490
+ }
1491
+ function normalizeRfqSwapRecord(record) {
1492
+ const { fundingArkTxid, refundArkTxid, lockupSpendArkTxids, ...rest } = record;
1493
+ const legacyClaim = record.kind === "lightning_receive" && record.profile.claimArkTxid !== void 0;
1494
+ if (!fundingArkTxid && !refundArkTxid && !lockupSpendArkTxids && !legacyClaim) return record;
1495
+ const fundingTxid = rest.fundingTxid ?? fundingArkTxid;
1496
+ const refundTxid = rest.refundTxid ?? refundArkTxid;
1497
+ const lockupSpendTxids = rest.lockupSpendTxids ?? lockupSpendArkTxids;
1498
+ return {
1499
+ ...rest,
1500
+ ...fundingTxid ? { fundingTxid } : {},
1501
+ ...refundTxid ? { refundTxid } : {},
1502
+ ...lockupSpendTxids?.length ? { lockupSpendTxids } : {},
1503
+ ...legacyClaim ? { profile: renameLegacyClaimTxid(record.profile) } : {}
1504
+ };
1505
+ }
1381
1506
  var managerState = (swap) => ({
1382
1507
  rfqId: swap.rfqId,
1383
1508
  state: swap.state,
1384
1509
  createdAt: swap.createdAt,
1385
1510
  updatedAt: swap.updatedAt,
1386
- ...swap.refundArkTxid ? { refundArkTxid: swap.refundArkTxid } : {},
1387
- ...swap.lockupSpendArkTxids?.length ? { lockupSpendArkTxids: [...swap.lockupSpendArkTxids] } : {},
1511
+ ...swap.refundTxid ? { refundTxid: swap.refundTxid } : {},
1512
+ ...swap.lockupSpendTxids?.length ? { lockupSpendTxids: [...swap.lockupSpendTxids] } : {},
1388
1513
  ...swap.failure ? { failure: swap.failure } : {},
1389
1514
  ...swap.blockedReason ? { blockedReason: swap.blockedReason } : {}
1390
1515
  });
@@ -1415,27 +1540,29 @@ function createRfqSwapRecord(origin, swap) {
1415
1540
  }
1416
1541
  function updateRfqSwapRecord(record, swap) {
1417
1542
  assertSameSwap(record, swap);
1543
+ const stored = normalizeRfqSwapRecord(record);
1418
1544
  const {
1419
- refundArkTxid: _refundArkTxid,
1420
- lockupSpendArkTxids: _lockupSpendArkTxids,
1545
+ refundTxid: _refundTxid,
1546
+ lockupSpendTxids: _lockupSpendTxids,
1421
1547
  failure: _failure,
1422
1548
  blockedReason: _blockedReason,
1423
1549
  ...origin
1424
- } = record;
1425
- const handler = rfqCorridorHandlers.getOrThrow(record.kind);
1550
+ } = stored;
1551
+ const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
1426
1552
  return {
1427
1553
  ...origin,
1428
1554
  ...managerState(swap),
1429
- profile: { ...record.profile, ...handler.project(swap) }
1555
+ profile: { ...stored.profile, ...handler.project(swap) }
1430
1556
  };
1431
1557
  }
1432
1558
  function rfqSwapOriginOf(record) {
1559
+ const stored = normalizeRfqSwapRecord(record);
1433
1560
  return {
1434
- kind: record.kind,
1435
- lockupAddress: record.lockupAddress,
1436
- profile: { ...record.profile },
1437
- ...record.amount !== void 0 ? { amount: record.amount } : {},
1438
- ...record.fundingArkTxid ? { fundingArkTxid: record.fundingArkTxid } : {}
1561
+ kind: stored.kind,
1562
+ lockupAddress: stored.lockupAddress,
1563
+ profile: { ...stored.profile },
1564
+ ...stored.amount !== void 0 ? { amount: stored.amount } : {},
1565
+ ...stored.fundingTxid ? { fundingTxid: stored.fundingTxid } : {}
1439
1566
  };
1440
1567
  }
1441
1568
  function lockupScript(params, lockupAddress) {
@@ -1449,27 +1576,28 @@ function lockupScript(params, lockupAddress) {
1449
1576
  return script;
1450
1577
  }
1451
1578
  function rebuildRfqSwap(record, params) {
1452
- const script = lockupScript(params, record.lockupAddress);
1579
+ const stored = normalizeRfqSwapRecord(record);
1580
+ const script = lockupScript(params, stored.lockupAddress);
1453
1581
  const common = {
1454
- rfqId: record.rfqId,
1455
- state: record.state,
1582
+ rfqId: stored.rfqId,
1583
+ state: stored.state,
1456
1584
  lockupPkScript: script.pkScript,
1457
- lockup: { script, address: record.lockupAddress },
1585
+ lockup: { script, address: stored.lockupAddress },
1458
1586
  // From the covenant, which binds it: the record's own copy would be a
1459
1587
  // second source for the deadline the refund is gated on.
1460
1588
  refundLocktime: Number(script.options.refundLocktime),
1461
- createdAt: record.createdAt,
1462
- updatedAt: record.updatedAt,
1463
- ...record.refundArkTxid ? { refundArkTxid: record.refundArkTxid } : {},
1464
- ...record.lockupSpendArkTxids?.length ? { lockupSpendArkTxids: [...record.lockupSpendArkTxids] } : {},
1465
- ...record.failure ? { failure: record.failure } : {},
1466
- ...record.blockedReason ? { blockedReason: record.blockedReason } : {}
1589
+ createdAt: stored.createdAt,
1590
+ updatedAt: stored.updatedAt,
1591
+ ...stored.refundTxid ? { refundTxid: stored.refundTxid } : {},
1592
+ ...stored.lockupSpendTxids?.length ? { lockupSpendTxids: [...stored.lockupSpendTxids] } : {},
1593
+ ...stored.failure ? { failure: stored.failure } : {},
1594
+ ...stored.blockedReason ? { blockedReason: stored.blockedReason } : {}
1467
1595
  };
1468
- const handler = rfqCorridorHandlers.getOrThrow(record.kind);
1596
+ const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
1469
1597
  return {
1470
1598
  ...common,
1471
- kind: record.kind,
1472
- ...handler.hydrate(record.profile, { lockup: script })
1599
+ kind: stored.kind,
1600
+ ...handler.hydrate(stored.profile, { lockup: script })
1473
1601
  };
1474
1602
  }
1475
1603
  function shouldRetainRfqSwap(record, now) {
@@ -1506,13 +1634,15 @@ async function fetchParsedTxs(indexer, txids) {
1506
1634
  var unscannedSwapCandidates = (txs, existingIds, scanned) => txs.filter(
1507
1635
  (tx) => tx.type === "sent" && tx.redeemTxid && !existingIds.has(tx.redeemTxid) && !scanned.has(tx.redeemTxid)
1508
1636
  );
1509
- function classifySpend(offer, serverPubkey, spendTx, deposit) {
1637
+ function classifySpend(offer, operatorPubkey, spendTx, deposit) {
1510
1638
  let leaves;
1511
1639
  try {
1512
- const script = offerVtxoScript(offer, serverPubkey);
1640
+ const script = offerContract(offer, operatorPubkey);
1513
1641
  if (import_base6.hex.encode(script.pkScript) !== import_base6.hex.encode(offer.swapPkScript)) return "indeterminate";
1514
1642
  leaves = {
1515
- cancel: script.functionByName("cancel")?.leafScript,
1643
+ // both routes that hand the deposit back; `exit` is absent on an
1644
+ // offer that carries no exit closure, and drops out here
1645
+ returned: ["cancel", "exit"].map((name) => script.functionByName(name)?.leafScript).filter((leaf) => leaf !== void 0),
1516
1646
  fulfill: script.functionByName("fulfill")?.leafScript
1517
1647
  };
1518
1648
  } catch {
@@ -1524,22 +1654,22 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
1524
1654
  if (import_base6.hex.encode(input.txid) !== deposit.txid) continue;
1525
1655
  for (const leaf of input.tapLeafScript ?? []) {
1526
1656
  const spent = import_base6.hex.encode((0, import_sdk6.scriptFromTapLeafScript)(leaf));
1527
- if (leaves.cancel && spent === import_base6.hex.encode(leaves.cancel)) return "cancelled";
1657
+ if (leaves.returned.some((back) => spent === import_base6.hex.encode(back))) return "cancelled";
1528
1658
  if (leaves.fulfill && spent === import_base6.hex.encode(leaves.fulfill)) return "fulfilled";
1529
1659
  }
1530
1660
  }
1531
1661
  return "indeterminate";
1532
1662
  }
1533
1663
  var spendTxidsOf = (vtxo) => [vtxo.spentBy, vtxo.arkTxId].filter((id) => Boolean(id));
1534
- function classifyDepositSpend(offer, serverPubkey, spendTxs, deposit) {
1664
+ function classifyDepositSpend(offer, operatorPubkey, spendTxs, deposit) {
1535
1665
  for (const tx of spendTxs) {
1536
- const kind = classifySpend(offer, serverPubkey, tx, deposit);
1666
+ const kind = classifySpend(offer, operatorPubkey, tx, deposit);
1537
1667
  if (kind !== "indeterminate") return kind;
1538
1668
  }
1539
1669
  return "indeterminate";
1540
1670
  }
1541
1671
  async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1542
- const { serverPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
1672
+ const { operatorPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
1543
1673
  const candidates = unscannedSwapCandidates(txs, existingIds, scanned);
1544
1674
  if (candidates.length === 0) return { restored: [], scannedTxids: [] };
1545
1675
  const byTxid = new Map(candidates.map((tx) => [tx.redeemTxid, tx]));
@@ -1608,7 +1738,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1608
1738
  if (state === "swept") status = "recoverable";
1609
1739
  else if (state === "spent") {
1610
1740
  const spendTxs = spendTxidsOf(vtxo).map((id) => spendTxByTxid.get(id)).filter((tx) => tx !== void 0);
1611
- const kind = classifyDepositSpend(offer, serverPubkey, spendTxs, {
1741
+ const kind = classifyDepositSpend(offer, operatorPubkey, spendTxs, {
1612
1742
  txid: vtxo.txid,
1613
1743
  vout: vtxo.vout
1614
1744
  });
@@ -1625,7 +1755,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1625
1755
  fromAmount,
1626
1756
  toAmount: offer.wantAmount.toString(),
1627
1757
  // ponytail(arkade-os/ts-sdk#680): empty address makes cancel fall back
1628
- // to the current server key; store the funded address if server-key
1758
+ // to the current operator key; store the funded address if operator-key
1629
1759
  // rotations become real (cancelOffer now at least diagnoses the
1630
1760
  // mismatch instead of reporting a missing VTXO)
1631
1761
  swapAddress: "",
@@ -1660,13 +1790,15 @@ function spendUpdate(swap, spend) {
1660
1790
  }
1661
1791
  async function watchOfferSwaps({
1662
1792
  wallet,
1663
- arkServerUrl,
1664
1793
  repository,
1665
1794
  onUpdate
1666
1795
  }) {
1667
- const manager = await wallet.getContractManager();
1668
- const serverPubkey = import_sdk7.ArkAddress.decode(await wallet.getAddress()).serverPubKey;
1669
- const indexer = new import_sdk7.RestIndexerProvider(arkServerUrl);
1796
+ const [manager, address, indexer] = await Promise.all([
1797
+ wallet.getContractManager(),
1798
+ wallet.getAddress(),
1799
+ wallet.getArkadeReader()
1800
+ ]);
1801
+ const operatorPubkey = import_sdk7.ArkAddress.decode(address).serverPubKey;
1670
1802
  let queue = Promise.resolve();
1671
1803
  const enqueue = (task) => {
1672
1804
  queue = queue.then(task).catch(() => {
@@ -1680,7 +1812,7 @@ async function watchOfferSwaps({
1680
1812
  const { txs } = await indexer.getVirtualTxs(candidates);
1681
1813
  return classifyDepositSpend(
1682
1814
  decodeOffer(import_base7.hex.decode(swap.offerHex)),
1683
- serverPubkey,
1815
+ operatorPubkey,
1684
1816
  txs.map((psbt) => import_sdk7.Transaction.fromPSBT(import_base7.base64.decode(psbt))),
1685
1817
  { txid: vtxo.txid, vout: vtxo.vout }
1686
1818
  );
@@ -1834,11 +1966,14 @@ var ONCHAIN_SEND_PAIR = rfqPair(ARKADE_BTC, ONCHAIN_BTC);
1834
1966
  var ONCHAIN_RECEIVE_PAIR = rfqPair(ONCHAIN_BTC, ARKADE_BTC);
1835
1967
  var RFQ_TERMINAL_STATES = ["settled", "refused", "expired", "refunded", "stuck"];
1836
1968
  var SwapRefusal = class extends Error {
1969
+ /** Literal-typed so the v2 error taxonomy's union discriminates on `name`
1970
+ * — a `string` here collapses the discriminant for every member. Same value
1971
+ * the constructor has always set, moved to a field initializer. */
1972
+ name = "SwapRefusal";
1837
1973
  reason;
1838
1974
  rfqId;
1839
1975
  constructor(reason, rfqId) {
1840
1976
  super(`solver refused: ${reason}`);
1841
- this.name = "SwapRefusal";
1842
1977
  this.reason = reason;
1843
1978
  this.rfqId = rfqId;
1844
1979
  }
@@ -1918,8 +2053,22 @@ var assertPairLength = (pair) => {
1918
2053
  };
1919
2054
  var verifyLockupAddress = (quote, derivedAddress) => {
1920
2055
  const quoted = quote.profile?.lockup_address;
1921
- if (derivedAddress !== quoted) throw new AddressMismatch(derivedAddress, quoted);
1922
- return derivedAddress;
2056
+ const candidates = Array.isArray(derivedAddress) ? derivedAddress : [derivedAddress];
2057
+ const matched = candidates.find((address) => address === quoted);
2058
+ if (matched === void 0) throw new AddressMismatch(candidates, quoted);
2059
+ return matched;
2060
+ };
2061
+ var LOCKUP_SHAPE_VARIANTS = [void 0, "preTimelockedRefund"];
2062
+ var matchQuotedLockup = (quote, hrp, serverPubkey, build) => {
2063
+ const candidates = LOCKUP_SHAPE_VARIANTS.map((legacy) => {
2064
+ const script = build(legacy);
2065
+ return { script, address: script.address(hrp, serverPubkey).encode(), legacy };
2066
+ });
2067
+ const matchedAddress = verifyLockupAddress(
2068
+ quote,
2069
+ candidates.map((candidate) => candidate.address)
2070
+ );
2071
+ return candidates.find((candidate) => candidate.address === matchedAddress);
1923
2072
  };
1924
2073
  var assertFundable = (input) => {
1925
2074
  const fail = (reason, message) => {
@@ -2090,22 +2239,22 @@ var relayTransport = (relayUrl, options) => {
2090
2239
  };
2091
2240
  var SEQUENCE_GRANULARITY_SECONDS = 512;
2092
2241
  var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
2093
- var unilateralClaimDelay = (serverExitDelaySeconds) => {
2094
- if (!Number.isFinite(serverExitDelaySeconds) || serverExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
2242
+ var unilateralClaimDelay = (operatorExitDelaySeconds) => {
2243
+ if (!Number.isFinite(operatorExitDelaySeconds) || operatorExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
2095
2244
  throw new Error(
2096
- `server exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${serverExitDelaySeconds}`
2245
+ `operator exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${operatorExitDelaySeconds}`
2097
2246
  );
2098
2247
  }
2099
- if (serverExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
2248
+ if (operatorExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
2100
2249
  throw new Error(
2101
- `server exit delay ${serverExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
2250
+ `operator exit delay ${operatorExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
2102
2251
  );
2103
2252
  }
2104
- return Math.ceil(serverExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
2253
+ return Math.ceil(operatorExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
2105
2254
  };
2106
2255
  var unilateralRefundDelay = (claimDelay) => claimDelay;
2107
2256
  var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay + SOLO_REFUND_HEADROOM_SECONDS;
2108
- function lightningSendVtxoScript(params) {
2257
+ function lightningSendContract(params) {
2109
2258
  const seconds = (value) => ({
2110
2259
  type: "seconds",
2111
2260
  value: BigInt(value)
@@ -2113,7 +2262,7 @@ function lightningSendVtxoScript(params) {
2113
2262
  return new import_sdk9.VHTLC.ScriptV2({
2114
2263
  sender: params.senderPubkey,
2115
2264
  receiver: params.solverPubkey,
2116
- server: params.serverPubkey,
2265
+ server: params.operatorPubkey,
2117
2266
  preimageHash: (0, import_legacy2.ripemd160)(import_base10.hex.decode(params.paymentHash)),
2118
2267
  refundLocktime: BigInt(params.refundLocktime),
2119
2268
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -2121,22 +2270,20 @@ function lightningSendVtxoScript(params) {
2121
2270
  unilateralRefundWithoutReceiverDelay: seconds(
2122
2271
  unilateralRefundWithoutReceiverDelay(params.claimDelay)
2123
2272
  ),
2124
- nonInteractiveClaim: {
2273
+ nonInteractiveParameters: {
2125
2274
  receiverPkScript: params.receiverPkScript,
2126
- emulatorPubkey: params.emulatorPubkey
2127
- },
2128
- nonInteractiveRefund: {
2129
2275
  senderPkScript: params.refundPkScript,
2130
- emulatorPubkey: params.emulatorPubkey
2276
+ emulatorPubkey: params.emulatorPubkey,
2277
+ ...params.legacy !== void 0 && { legacy: params.legacy }
2131
2278
  }
2132
2279
  });
2133
2280
  }
2134
- async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2281
+ async function requestLightningSend(wallet, transport, params) {
2135
2282
  const rfqId = params.rfqId ?? newRfqId();
2136
2283
  const secrets = await (0, import_sdk10.provisionRefundKey)(wallet);
2137
2284
  const senderPubkey = secrets.pubkey;
2138
2285
  const refundAddress = secrets.address;
2139
- const info = await new import_sdk9.RestArkProvider(arkServerUrl).getInfo();
2286
+ const info = await wallet.getArkadeInfo({ requireLive: true });
2140
2287
  const quote = await transport.requestQuote(
2141
2288
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
2142
2289
  );
@@ -2157,12 +2304,12 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2157
2304
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
2158
2305
  );
2159
2306
  }
2160
- const serverPubkey = (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2161
- const network = (0, import_sdk9.getNetwork)(info.network);
2162
- const treeParams = {
2307
+ const operatorPubkey = (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2308
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2309
+ const contractParams = {
2163
2310
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2164
2311
  refundLocktime: quote.refund_locktime,
2165
- serverPubkey,
2312
+ operatorPubkey,
2166
2313
  paymentHash: params.invoice.paymentHash,
2167
2314
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
2168
2315
  emulatorPubkey: (0, import_sdk9.toXOnly)(
@@ -2173,9 +2320,18 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2173
2320
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
2174
2321
  refundPkScript: secrets.pkScript
2175
2322
  };
2176
- const script = lightningSendVtxoScript(treeParams);
2177
- const address = script.address(network.hrp, serverPubkey).encode();
2178
- verifyLockupAddress(quote, address);
2323
+ const matched = matchQuotedLockup(
2324
+ quote,
2325
+ network.hrp,
2326
+ operatorPubkey,
2327
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2328
+ );
2329
+ const script = matched.script;
2330
+ const address = matched.address;
2331
+ const matchedContractParams = {
2332
+ ...contractParams,
2333
+ ...matched.legacy !== void 0 && { legacy: matched.legacy }
2334
+ };
2179
2335
  assertFundable({
2180
2336
  quote,
2181
2337
  invoiceExpiresAt: params.invoice.expiresAt,
@@ -2194,7 +2350,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2194
2350
  refundAddress,
2195
2351
  senderPubkey,
2196
2352
  secrets,
2197
- treeParams
2353
+ contractParams: matchedContractParams
2198
2354
  };
2199
2355
  }
2200
2356
  var offerTermsFromQuote = (quote, assets) => {
@@ -2259,19 +2415,23 @@ function deriveOnchainSend(input) {
2259
2415
  if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
2260
2416
  throw new Error("onchain-send quote is missing a binding field");
2261
2417
  }
2262
- const script = lightningSendVtxoScript({
2418
+ const contractParams = {
2263
2419
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2264
2420
  refundLocktime,
2265
- serverPubkey: input.serverPubkey,
2421
+ operatorPubkey: input.operatorPubkey,
2266
2422
  paymentHash: input.paymentHash,
2267
2423
  claimDelay: input.claimDelay,
2268
2424
  emulatorPubkey: input.emulatorPubkey,
2269
2425
  senderPubkey: input.senderPubkey,
2270
2426
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
2271
2427
  refundPkScript: import_sdk9.ArkAddress.decode(input.refundAddress).pkScript
2272
- });
2273
- const address = script.address(input.hrp, input.serverPubkey).encode();
2274
- verifyLockupAddress(quote, address);
2428
+ };
2429
+ const { script, address } = matchQuotedLockup(
2430
+ quote,
2431
+ input.hrp,
2432
+ input.operatorPubkey,
2433
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2434
+ );
2275
2435
  const htlcParams = {
2276
2436
  paymentHash: input.paymentHash,
2277
2437
  claimKey: input.payoutPubkey,
@@ -2292,7 +2452,7 @@ function deriveOnchainSend(input) {
2292
2452
  minConfirmations
2293
2453
  };
2294
2454
  }
2295
- async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2455
+ async function requestOnchainSend(wallet, transport, params) {
2296
2456
  const rfqId = params.rfqId ?? newRfqId();
2297
2457
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet, { preimage: params.preimage });
2298
2458
  if (secrets.mustPersistPreimage) {
@@ -2303,7 +2463,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2303
2463
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2304
2464
  const senderPubkey = secrets.pubkey;
2305
2465
  const [info, refundAddress] = await Promise.all([
2306
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2466
+ wallet.getArkadeInfo({ requireLive: true }),
2307
2467
  wallet.getAddress()
2308
2468
  ]);
2309
2469
  const quote = await transport.requestQuote(
@@ -2317,12 +2477,12 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2317
2477
  amountSide: params.amountSide
2318
2478
  })
2319
2479
  );
2320
- const network = (0, import_sdk9.getNetwork)(info.network);
2480
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2321
2481
  const derived = deriveOnchainSend({
2322
2482
  quote,
2323
2483
  paymentHash,
2324
2484
  payoutPubkey: params.payoutPubkey,
2325
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2485
+ operatorPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2326
2486
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2327
2487
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2328
2488
  "emulator signer key"
@@ -2359,6 +2519,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2359
2519
  htlcParams: derived.htlcParams,
2360
2520
  l1Network: derived.l1Network,
2361
2521
  minConfirmations: derived.minConfirmations,
2522
+ refundLocktime: derived.refundLocktime,
2362
2523
  senderPubkey,
2363
2524
  secrets
2364
2525
  };
@@ -2430,7 +2591,7 @@ var assertReceivable = (input) => {
2430
2591
  );
2431
2592
  }
2432
2593
  };
2433
- function receiveVtxoScript(params) {
2594
+ function lightningReceiveContract(params) {
2434
2595
  const seconds = (value) => ({
2435
2596
  type: "seconds",
2436
2597
  value: BigInt(value)
@@ -2438,7 +2599,7 @@ function receiveVtxoScript(params) {
2438
2599
  return new import_sdk9.VHTLC.ScriptV2({
2439
2600
  sender: params.solverPubkey,
2440
2601
  receiver: params.payoutPubkey,
2441
- server: params.serverPubkey,
2602
+ server: params.operatorPubkey,
2442
2603
  preimageHash: (0, import_legacy2.ripemd160)(import_base10.hex.decode(params.paymentHash)),
2443
2604
  refundLocktime: BigInt(params.refundLocktime),
2444
2605
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -2446,13 +2607,11 @@ function receiveVtxoScript(params) {
2446
2607
  unilateralRefundWithoutReceiverDelay: seconds(
2447
2608
  unilateralRefundWithoutReceiverDelay(params.claimDelay)
2448
2609
  ),
2449
- nonInteractiveClaim: {
2610
+ nonInteractiveParameters: {
2450
2611
  receiverPkScript: params.payoutPkScript,
2451
- emulatorPubkey: params.emulatorPubkey
2452
- },
2453
- nonInteractiveRefund: {
2454
2612
  senderPkScript: params.solverRefundPkScript,
2455
- emulatorPubkey: params.emulatorPubkey
2613
+ emulatorPubkey: params.emulatorPubkey,
2614
+ ...params.legacy !== void 0 && { legacy: params.legacy }
2456
2615
  }
2457
2616
  });
2458
2617
  }
@@ -2465,10 +2624,10 @@ function deriveLightningReceive(input) {
2465
2624
  if (refundLocktime === void 0 || invoice === void 0 || solverRefundPkScriptHex === void 0) {
2466
2625
  throw new Error("lightning-receive quote is missing a binding field");
2467
2626
  }
2468
- const treeParams = {
2627
+ const contractParams = {
2469
2628
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2470
2629
  refundLocktime,
2471
- serverPubkey: input.serverPubkey,
2630
+ operatorPubkey: input.operatorPubkey,
2472
2631
  paymentHash: input.paymentHash,
2473
2632
  claimDelay: input.claimDelay,
2474
2633
  emulatorPubkey: input.emulatorPubkey,
@@ -2476,12 +2635,25 @@ function deriveLightningReceive(input) {
2476
2635
  payoutPubkey: input.payoutPubkey,
2477
2636
  payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
2478
2637
  };
2479
- const script = receiveVtxoScript(treeParams);
2480
- const address = script.address(input.hrp, input.serverPubkey).encode();
2481
- verifyLockupAddress(quote, address);
2482
- return { address, swapPkScript: script.pkScript, script, invoice, refundLocktime, treeParams };
2638
+ const matched = matchQuotedLockup(
2639
+ quote,
2640
+ input.hrp,
2641
+ input.operatorPubkey,
2642
+ (legacy) => lightningReceiveContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2643
+ );
2644
+ return {
2645
+ address: matched.address,
2646
+ swapPkScript: matched.script.pkScript,
2647
+ script: matched.script,
2648
+ invoice,
2649
+ refundLocktime,
2650
+ contractParams: {
2651
+ ...contractParams,
2652
+ ...matched.legacy !== void 0 && { legacy: matched.legacy }
2653
+ }
2654
+ };
2483
2655
  }
2484
- async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
2656
+ async function requestLightningReceive(wallet, transport, params) {
2485
2657
  const rfqId = params.rfqId ?? newRfqId();
2486
2658
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
2487
2659
  if (secrets.mustPersistPreimage) {
@@ -2493,7 +2665,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2493
2665
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2494
2666
  const payoutPubkey = secrets.pubkey;
2495
2667
  const [info, payoutAddress] = await Promise.all([
2496
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2668
+ wallet.getArkadeInfo({ requireLive: true }),
2497
2669
  wallet.getAddress()
2498
2670
  ]);
2499
2671
  const claimPacket = await sealClaimPacket({
@@ -2512,13 +2684,13 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2512
2684
  })
2513
2685
  );
2514
2686
  assertQuotedAmount(quote, params.amountSide, params.amount);
2515
- const network = (0, import_sdk9.getNetwork)(info.network);
2687
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2516
2688
  const derived = deriveLightningReceive({
2517
2689
  quote,
2518
2690
  paymentHash,
2519
2691
  payoutPubkey,
2520
2692
  payoutAddress,
2521
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2693
+ operatorPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2522
2694
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2523
2695
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2524
2696
  "emulator signer key"
@@ -2552,7 +2724,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2552
2724
  payoutAddress,
2553
2725
  payoutPubkey,
2554
2726
  secrets,
2555
- treeParams: derived.treeParams
2727
+ contractParams: derived.contractParams
2556
2728
  };
2557
2729
  }
2558
2730
  function deriveOnchainReceive(input) {
@@ -2567,19 +2739,26 @@ function deriveOnchainReceive(input) {
2567
2739
  if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
2568
2740
  throw new Error("onchain-receive quote is missing a binding field");
2569
2741
  }
2570
- const script = receiveVtxoScript({
2742
+ const contractParams = {
2571
2743
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2572
2744
  refundLocktime,
2573
- serverPubkey: input.serverPubkey,
2745
+ operatorPubkey: input.operatorPubkey,
2574
2746
  paymentHash: input.paymentHash,
2575
2747
  claimDelay: input.claimDelay,
2576
2748
  emulatorPubkey: input.emulatorPubkey,
2577
2749
  solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
2578
2750
  payoutPubkey: input.payoutPubkey,
2579
2751
  payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
2580
- });
2581
- const address = script.address(input.hrp, input.serverPubkey).encode();
2582
- verifyLockupAddress(quote, address);
2752
+ };
2753
+ const { script, address } = matchQuotedLockup(
2754
+ quote,
2755
+ input.hrp,
2756
+ input.operatorPubkey,
2757
+ (legacy) => lightningReceiveContract({
2758
+ ...contractParams,
2759
+ ...legacy !== void 0 && { legacy }
2760
+ })
2761
+ );
2583
2762
  const htlc = onchainHtlcScript(
2584
2763
  {
2585
2764
  paymentHash: input.paymentHash,
@@ -2600,7 +2779,7 @@ function deriveOnchainReceive(input) {
2600
2779
  minConfirmations
2601
2780
  };
2602
2781
  }
2603
- async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2782
+ async function requestOnchainReceive(wallet, transport, params) {
2604
2783
  const rfqId = params.rfqId ?? newRfqId();
2605
2784
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
2606
2785
  if (secrets.mustPersistPreimage) {
@@ -2612,7 +2791,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2612
2791
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2613
2792
  const payoutPubkey = secrets.pubkey;
2614
2793
  const [info, payoutAddress] = await Promise.all([
2615
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2794
+ wallet.getArkadeInfo({ requireLive: true }),
2616
2795
  wallet.getAddress()
2617
2796
  ]);
2618
2797
  const claimPacket = await sealClaimPacket({
@@ -2632,14 +2811,14 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2632
2811
  })
2633
2812
  );
2634
2813
  assertQuotedAmount(quote, params.amountSide, params.amount);
2635
- const network = (0, import_sdk9.getNetwork)(info.network);
2814
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2636
2815
  const derived = deriveOnchainReceive({
2637
2816
  quote,
2638
2817
  paymentHash,
2639
2818
  payoutPubkey,
2640
2819
  payoutAddress,
2641
2820
  refundPubkey: params.refundPubkey,
2642
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2821
+ operatorPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2643
2822
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2644
2823
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2645
2824
  "emulator signer key"
@@ -2717,11 +2896,10 @@ var LockupNeedsRecoveryError = class extends Error {
2717
2896
  * into one settlement with no CLTV awareness, so an early attempt can fail
2718
2897
  * the whole batch — including unrelated outputs that were otherwise fine.
2719
2898
  *
2720
- * Exposed as a value, not only inside the message, so a caller can encode
2721
- * `packages/boltz-swap`'s "pre-CLTV recoverable skipped" rule without
2722
- * parsing prose. Seconds-based locktimes mature against the chain tip's
2723
- * timestamp rather than wall clock, so treat this as a floor to wait past,
2724
- * not an exact alarm.
2899
+ * Exposed as a value, not only inside the message, so a caller can skip
2900
+ * pre-CLTV recoverable outputs without parsing prose. Seconds-based
2901
+ * locktimes mature against the chain tip's timestamp rather than wall
2902
+ * clock, so treat this as a floor to wait past, not an exact alarm.
2725
2903
  */
2726
2904
  recoverableAfter;
2727
2905
  constructor(outpoints, recoverableAfter) {
@@ -2782,7 +2960,7 @@ async function readLockupFate(indexer, input) {
2782
2960
  if (vtxo.spentBy)
2783
2961
  spentBy.set(vtxo.spentBy, {
2784
2962
  checkpointTxid: vtxo.spentBy,
2785
- arkTxid: vtxo.arkTxId
2963
+ txid: vtxo.arkTxId
2786
2964
  });
2787
2965
  else everySpendNamed = false;
2788
2966
  }
@@ -2811,32 +2989,32 @@ async function readLockupFate(indexer, input) {
2811
2989
  }
2812
2990
  return everySpendNamed && observed.size === spentBy.size ? { fate: "returned", spends } : { fate: "unknown" };
2813
2991
  }
2814
- async function pushRefundWithoutReceiver(ark, input) {
2992
+ async function pushRefundWithoutReceiver(operator, input) {
2815
2993
  if (input.vtxos.length === 0) throw new Error("nothing to refund: no funded outputs");
2816
2994
  const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
2817
2995
  if (swept.length > 0) {
2818
2996
  throw new LockupNeedsRecoveryError(
2819
2997
  swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
2820
- input.script.options.refundLocktime
2998
+ input.contract.options.refundLocktime
2821
2999
  );
2822
3000
  }
2823
- const refundPkScript = input.refundPkScript ?? input.script.options.nonInteractiveRefund?.senderPkScript;
3001
+ const refundPkScript = input.refundPkScript ?? input.contract.options.nonInteractiveParameters?.senderPkScript;
2824
3002
  if (!refundPkScript) {
2825
3003
  throw new Error(
2826
- "no refund destination: the contract carries no nonInteractiveRefund leaf, so pass refundPkScript explicitly"
3004
+ "no refund destination: the contract carries no emulator covenant suite, so pass refundPkScript explicitly"
2827
3005
  );
2828
3006
  }
2829
- const info = await ark.getInfo();
2830
- let serverUnrollScript;
3007
+ const info = await operator.getInfo();
3008
+ let operatorUnrollScript;
2831
3009
  try {
2832
- serverUnrollScript = import_sdk11.CSVMultisigTapscript.decode(import_base11.hex.decode(info.checkpointTapscript));
3010
+ operatorUnrollScript = import_sdk11.CSVMultisigTapscript.decode(import_base11.hex.decode(info.checkpointTapscript));
2833
3011
  } catch {
2834
- throw new Error("invalid checkpointTapscript from the Arkade server");
3012
+ throw new Error("invalid checkpointTapscript from the operator");
2835
3013
  }
2836
- const leaf = input.script.refundWithoutReceiver();
2837
- const tapTree = input.script.encode();
3014
+ const leaf = input.contract.refundWithoutReceiver();
3015
+ const tapTree = input.contract.encode();
2838
3016
  const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
2839
- const { arkTx, checkpoints } = (0, import_sdk11.buildOffchainTx)(
3017
+ const { arkTx: tx, checkpoints } = (0, import_sdk11.buildOffchainTx)(
2840
3018
  input.vtxos.map((vtxo) => ({
2841
3019
  txid: vtxo.txid,
2842
3020
  vout: vtxo.vout,
@@ -2845,14 +3023,14 @@ async function pushRefundWithoutReceiver(ark, input) {
2845
3023
  tapTree
2846
3024
  })),
2847
3025
  [{ script: refundPkScript, amount: BigInt(amount) }],
2848
- serverUnrollScript
3026
+ operatorUnrollScript
2849
3027
  );
2850
- const signedArkTx = await input.sender.sign(arkTx);
2851
- const submitted = await ark.submitTx(
2852
- import_base11.base64.encode(signedArkTx.toPSBT()),
3028
+ const signedTx = await input.sender.sign(tx);
3029
+ const submitted = await operator.submitTx(
3030
+ import_base11.base64.encode(signedTx.toPSBT()),
2853
3031
  checkpoints.map((c) => import_base11.base64.encode(c.toPSBT()))
2854
3032
  );
2855
- (0, import_sdk11.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
3033
+ (0, import_sdk11.assertSubmittedArkTxid)(submitted, signedTx, "refundWithoutReceiver");
2856
3034
  const matched = (0, import_sdk11.matchServerCheckpoints)(
2857
3035
  submitted.signedCheckpointTxs,
2858
3036
  checkpoints,
@@ -2863,11 +3041,11 @@ async function pushRefundWithoutReceiver(ark, input) {
2863
3041
  async ({ server }) => import_base11.base64.encode((await input.sender.sign(server, [0])).toPSBT())
2864
3042
  )
2865
3043
  );
2866
- await ark.finalizeTx(submitted.arkTxid, finalCheckpoints);
2867
- return { arkTxid: submitted.arkTxid, amount };
3044
+ await operator.finalizeTx(submitted.arkTxid, finalCheckpoints);
3045
+ return { txid: submitted.arkTxid, amount };
2868
3046
  }
2869
3047
  var REFUND_MTP_LAG_SECONDS = 2 * 60 * 60;
2870
- async function refundIfUnresolved(transport, ark, indexer, input) {
3048
+ async function refundIfUnresolved(transport, operator, indexer, input) {
2871
3049
  const pollMs = input.pollMs ?? 5e3;
2872
3050
  const now = input.now ?? (() => Math.floor(Date.now() / 1e3));
2873
3051
  const attemptDeadline = input.attemptDeadline ?? input.refundLocktime + REFUND_MTP_LAG_SECONDS;
@@ -2875,11 +3053,26 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
2875
3053
  const status = await transport.status(input.rfqId);
2876
3054
  if (status && isResolved(status.state)) return { outcome: "resolved", status };
2877
3055
  if (now() >= input.refundLocktime) {
2878
- const vtxos = await findLockupVtxos(indexer, input.script.pkScript);
3056
+ let fate = { fate: "unknown" };
3057
+ try {
3058
+ fate = await readLockupFate(indexer, {
3059
+ swapPkScript: input.contract.pkScript,
3060
+ paymentHash: input.paymentHash
3061
+ });
3062
+ } catch {
3063
+ }
3064
+ if (fate.fate === "exited") {
3065
+ return {
3066
+ outcome: "exited",
3067
+ outpoints: fate.outpoints.map((o) => `${o.txid}:${o.vout}`),
3068
+ status
3069
+ };
3070
+ }
3071
+ const vtxos = await findLockupVtxos(indexer, input.contract.pkScript);
2879
3072
  if (vtxos.length === 0) return { outcome: "nothing_to_refund", status };
2880
3073
  try {
2881
- const pushed = await pushRefundWithoutReceiver(ark, {
2882
- script: input.script,
3074
+ const pushed = await pushRefundWithoutReceiver(operator, {
3075
+ contract: input.contract,
2883
3076
  sender: input.sender,
2884
3077
  vtxos,
2885
3078
  refundPkScript: input.refundPkScript
@@ -2922,13 +3115,13 @@ var assertFiniteAmount = (value, reason, label) => {
2922
3115
  error.reason = reason;
2923
3116
  throw error;
2924
3117
  };
2925
- async function pushClaim(ark, input) {
3118
+ async function pushClaim(operator, input) {
2926
3119
  if (input.vtxos.length === 0) throw new Error("nothing to claim: no funded outputs");
2927
3120
  const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
2928
3121
  if (swept.length > 0) {
2929
3122
  throw new LockupNeedsRecoveryError(
2930
3123
  swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
2931
- input.script.options.refundLocktime
3124
+ input.contract.options.refundLocktime
2932
3125
  );
2933
3126
  }
2934
3127
  const locked = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
@@ -2939,22 +3132,22 @@ async function pushClaim(ark, input) {
2939
3132
  throw new LockupAmountMismatchError(input.expectedAmount, locked);
2940
3133
  }
2941
3134
  }
2942
- const committed = input.script.options.preimageHash;
3135
+ const committed = input.contract.options.preimageHash;
2943
3136
  if (import_base12.hex.encode((0, import_legacy3.ripemd160)((0, import_sha25.sha256)(input.preimage))) !== import_base12.hex.encode(committed)) {
2944
3137
  throw new Error("preimage does not match the covenant's payment hash");
2945
3138
  }
2946
- const info = await ark.getInfo();
2947
- let serverUnrollScript;
3139
+ const info = await operator.getInfo();
3140
+ let operatorUnrollScript;
2948
3141
  try {
2949
- serverUnrollScript = import_sdk12.CSVMultisigTapscript.decode(import_base12.hex.decode(info.checkpointTapscript));
3142
+ operatorUnrollScript = import_sdk12.CSVMultisigTapscript.decode(import_base12.hex.decode(info.checkpointTapscript));
2950
3143
  } catch {
2951
- throw new Error("invalid checkpointTapscript from the Arkade server");
3144
+ throw new Error("invalid checkpointTapscript from the operator");
2952
3145
  }
2953
- const leaf = input.script.claim();
2954
- const tapTree = input.script.encode();
2955
- const arkTxid = await (0, import_sdk12.signAndSubmitOffchainTx)({
3146
+ const leaf = input.contract.claim();
3147
+ const tapTree = input.contract.encode();
3148
+ const txid = await (0, import_sdk12.signAndSubmitOffchainTx)({
2956
3149
  identity: (0, import_sdk12.claimWithPreimageIdentity)(input.receiver, input.preimage),
2957
- provider: ark,
3150
+ provider: operator,
2958
3151
  inputs: input.vtxos.map((vtxo) => ({
2959
3152
  txid: vtxo.txid,
2960
3153
  vout: vtxo.vout,
@@ -2965,10 +3158,10 @@ async function pushClaim(ark, input) {
2965
3158
  // One aggregate output: unlike the covenant refund, this leaf inspects
2966
3159
  // nothing about the output set.
2967
3160
  outputs: [{ script: input.destinationPkScript, amount: BigInt(locked) }],
2968
- serverUnrollScript,
2969
- verifyServerSignatures: { serverPubkey: input.script.options.server }
3161
+ serverUnrollScript: operatorUnrollScript,
3162
+ verifyServerSignatures: { serverPubkey: input.contract.options.server }
2970
3163
  });
2971
- return { arkTxid, amount: locked };
3164
+ return { txid, amount: locked };
2972
3165
  }
2973
3166
  async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
2974
3167
  const pollMs = options.pollMs ?? 5e3;
@@ -2983,13 +3176,13 @@ async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
2983
3176
  await sleep3(pollMs);
2984
3177
  }
2985
3178
  }
2986
- async function claimReceiveLockup(indexer, ark, input) {
3179
+ async function claimReceiveLockup(indexer, operator, input) {
2987
3180
  const vtxos = await awaitLockupFunding(indexer, input.swapPkScript, {
2988
3181
  pollMs: input.pollMs,
2989
3182
  deadline: input.deadline
2990
3183
  });
2991
- return pushClaim(ark, {
2992
- script: input.script,
3184
+ return pushClaim(operator, {
3185
+ contract: input.contract,
2993
3186
  receiver: input.receiver,
2994
3187
  preimage: input.preimage,
2995
3188
  vtxos,
@@ -3040,8 +3233,8 @@ async function senderIdentityForSwapRecord(wallet, record) {
3040
3233
  // src/arkadeRefunder.ts
3041
3234
  function arkadeRefunder(deps) {
3042
3235
  return async (swap) => {
3043
- const script = swap.lockup?.script;
3044
- if (!script) {
3236
+ const contract = swap.lockup?.script;
3237
+ if (!contract) {
3045
3238
  throw new Error(
3046
3239
  `swap ${swap.rfqId} carries no lockup covenant, so its refund cannot be built`
3047
3240
  );
@@ -3056,7 +3249,7 @@ function arkadeRefunder(deps) {
3056
3249
  );
3057
3250
  }
3058
3251
  const sender = await senderIdentityForSwapRecord(deps.wallet, rfqSignerOf(record) ?? {});
3059
- return pushRefundWithoutReceiver(deps.ark, { script, sender, vtxos });
3252
+ return pushRefundWithoutReceiver(deps.operator, { contract, sender, vtxos });
3060
3253
  };
3061
3254
  }
3062
3255
 
@@ -3452,6 +3645,19 @@ var RfqSwapManager = class {
3452
3645
  async getPendingSwaps() {
3453
3646
  return [...this.monitored.values()];
3454
3647
  }
3648
+ /**
3649
+ * Every swap this manager holds — {@link getPendingSwaps} plus the ones
3650
+ * that already ended. The two sets are disjoint: a swap leaves `monitored`
3651
+ * as it enters `finished`.
3652
+ *
3653
+ * The finished half is what this process has seen, which after
3654
+ * {@link restoreFromRepository} is the stored history minus what retention
3655
+ * pruned. A manager that has restored nothing answers with the live swaps
3656
+ * alone.
3657
+ */
3658
+ async getAllSwaps() {
3659
+ return [...this.monitored.values(), ...this.finished.values()];
3660
+ }
3455
3661
  async hasSwap(rfqId) {
3456
3662
  return this.monitored.has(rfqId);
3457
3663
  }
@@ -3730,14 +3936,14 @@ var RfqSwapManager = class {
3730
3936
  return this.claimIfFunded(swap, vtxos);
3731
3937
  }
3732
3938
  if (now < swap.refundLocktime + REFUND_MTP_LAG_SECONDS) {
3733
- if (swap.claimArkTxid) return;
3939
+ if (swap.claimTxid) return;
3734
3940
  return this.block(
3735
3941
  swap,
3736
3942
  "the claim window closed with the lockup unclaimed \u2014 only the solver can act now"
3737
3943
  );
3738
3944
  }
3739
3945
  const failure = this.lastClaimError.get(swap.rfqId);
3740
- if (failure && !swap.claimArkTxid) {
3946
+ if (failure && !swap.claimTxid) {
3741
3947
  return this.fail(swap, new Error(failure));
3742
3948
  }
3743
3949
  this.setState(swap, "refunded");
@@ -3752,7 +3958,7 @@ var RfqSwapManager = class {
3752
3958
  */
3753
3959
  async claimIfFunded(swap, vtxos) {
3754
3960
  if (vtxos.length === 0) return this.unblock(swap);
3755
- const partiallyClaimed = swap.claimArkTxid !== void 0;
3961
+ const partiallyClaimed = swap.claimTxid !== void 0;
3756
3962
  if (partiallyClaimed && !this.hasUnclaimedOutpoint(swap.rfqId, vtxos)) {
3757
3963
  return;
3758
3964
  }
@@ -3780,10 +3986,10 @@ var RfqSwapManager = class {
3780
3986
  this.setState(swap, "claimable");
3781
3987
  if (!this.config.enableAutoActions) return;
3782
3988
  try {
3783
- const { arkTxid } = await this.callbacks.claimLockup(swap, vtxos, { partiallyClaimed });
3989
+ const { txid } = await this.callbacks.claimLockup(swap, vtxos, { partiallyClaimed });
3784
3990
  this.lastClaimError.delete(swap.rfqId);
3785
3991
  this.rememberClaimed(swap.rfqId, vtxos);
3786
- swap.claimArkTxid = arkTxid;
3992
+ swap.claimTxid = txid;
3787
3993
  this.touch(swap);
3788
3994
  this.setState(swap, "claimed");
3789
3995
  this.emitAction(swap, "claimLockup");
@@ -3875,7 +4081,7 @@ var RfqSwapManager = class {
3875
4081
  try {
3876
4082
  const pushed = await this.callbacks.refundArkade(swap);
3877
4083
  if (pushed) {
3878
- swap.refundArkTxid = pushed.arkTxid;
4084
+ swap.refundTxid = pushed.txid;
3879
4085
  this.touch(swap);
3880
4086
  }
3881
4087
  this.setState(swap, "refunded");
@@ -3948,7 +4154,7 @@ var RfqSwapManager = class {
3948
4154
  /**
3949
4155
  * Record which ark transactions ended the lockup.
3950
4156
  *
3951
- * Only the ones the indexer actually named: `LockupSpend.arkTxid` is
4157
+ * Only the ones the indexer actually named: `LockupSpend.txid` is
3952
4158
  * optional, and a checkpoint txid is not what history correlates on — a
3953
4159
  * record carrying one would name a transaction the wallet's own activity
3954
4160
  * never shows. Fewer txids is the right failure here.
@@ -3958,9 +4164,9 @@ var RfqSwapManager = class {
3958
4164
  * verdict once.
3959
4165
  */
3960
4166
  stampLockupSpends(swap, spends) {
3961
- const arkTxids = spends.map((spend) => spend.arkTxid).filter((txid) => txid !== void 0);
3962
- if (arkTxids.length === 0) return;
3963
- swap.lockupSpendArkTxids = arkTxids;
4167
+ const txids = spends.map((spend) => spend.txid).filter((txid) => txid !== void 0);
4168
+ if (txids.length === 0) return;
4169
+ swap.lockupSpendTxids = txids;
3964
4170
  this.touch(swap);
3965
4171
  }
3966
4172
  touch(swap) {
@@ -4041,10 +4247,9 @@ var RfqSwapManager = class {
4041
4247
  /**
4042
4248
  * Drop a terminal swap from monitoring and report it exactly once.
4043
4249
  *
4044
- * `onSwapCompleted` and `onSwapFailed` are mutually exclusive here, unlike
4045
- * Boltz's manager, which fires completion for every swap that leaves
4046
- * monitoring including the failed ones a listener named "completed" that
4047
- * also fires on failure is a trap worth not inheriting.
4250
+ * `onSwapCompleted` and `onSwapFailed` are mutually exclusive here: a
4251
+ * listener named "completed" that also fires on failure is a trap, so a
4252
+ * swap that leaves monitoring reports through exactly one of them.
4048
4253
  */
4049
4254
  finalize(swap) {
4050
4255
  if (!this.monitored.has(swap.rfqId)) return;
@@ -4080,7 +4285,7 @@ var traderClaimTxid = (swap) => {
4080
4285
  case "onchain_send":
4081
4286
  return swap.claimTxid;
4082
4287
  case "lightning_receive":
4083
- return swap.claimArkTxid;
4288
+ return swap.claimTxid;
4084
4289
  default:
4085
4290
  return void 0;
4086
4291
  }
@@ -4090,7 +4295,7 @@ var outcomeOf = (swap) => {
4090
4295
  const lostReceive = swap.kind === "lightning_receive" && swap.state === "refunded";
4091
4296
  return {
4092
4297
  state: swap.state,
4093
- txid: lostReceive ? swap.refundArkTxid : traderClaimTxid(swap) ?? swap.refundArkTxid
4298
+ txid: lostReceive ? swap.refundTxid : traderClaimTxid(swap) ?? swap.refundTxid
4094
4299
  };
4095
4300
  };
4096
4301
  var errorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -4155,16 +4360,17 @@ async function rfqSwapActivityInputs(deps) {
4155
4360
  const records = await deps.repository.getAllRfqSwaps();
4156
4361
  return Promise.all(records.map((record) => activityInputOf(record, deps.indexer)));
4157
4362
  }
4158
- async function activityInputOf(record, indexer) {
4363
+ async function activityInputOf(stored, indexer) {
4364
+ const record = normalizeRfqSwapRecord(stored);
4159
4365
  const txids = /* @__PURE__ */ new Set();
4160
- if (record.fundingArkTxid) txids.add(record.fundingArkTxid);
4161
- if (record.refundArkTxid) txids.add(record.refundArkTxid);
4366
+ if (record.fundingTxid) txids.add(record.fundingTxid);
4367
+ if (record.refundTxid) txids.add(record.refundTxid);
4162
4368
  const handler = rfqCorridorHandlers.getOrThrow(record.kind);
4163
4369
  for (const txid of handler.activityTxids?.(record.profile) ?? []) txids.add(txid);
4164
- for (const txid of record.lockupSpendArkTxids ?? []) txids.add(txid);
4165
- const spendUnknown = isRfqSwapTerminal(record.state) && !record.refundArkTxid && !record.lockupSpendArkTxids?.length;
4166
- if (indexer && (!record.fundingArkTxid || spendUnknown)) {
4167
- for (const txid of await lockupTxids(indexer, record, !record.fundingArkTxid)) {
4370
+ for (const txid of record.lockupSpendTxids ?? []) txids.add(txid);
4371
+ const spendUnknown = isRfqSwapTerminal(record.state) && !record.refundTxid && !record.lockupSpendTxids?.length;
4372
+ if (indexer && (!record.fundingTxid || spendUnknown)) {
4373
+ for (const txid of await lockupTxids(indexer, record, !record.fundingTxid)) {
4168
4374
  txids.add(txid);
4169
4375
  }
4170
4376
  }
@@ -4189,6 +4395,334 @@ async function lockupTxids(indexer, record, wantFunding) {
4189
4395
  return [];
4190
4396
  }
4191
4397
  }
4398
+
4399
+ // src/swapClient.ts
4400
+ var import_base15 = require("@scure/base");
4401
+ var import_sdk15 = require("@arkade-os/sdk");
4402
+ var import_solver_discovery2 = require("@arkade-os/solver-discovery");
4403
+ var sideCorridorsOf = (market) => {
4404
+ const read = (value) => value === "lightning" || value === "onchain" ? value : "arkade";
4405
+ const m = market;
4406
+ return { base: read(m.base_corridor), quote: read(m.quote_corridor) };
4407
+ };
4408
+ var resolveKind = (market, give) => {
4409
+ const corridors = sideCorridorsOf(market);
4410
+ const giveCorridor = corridors[give];
4411
+ const receiveCorridor = corridors[give === "base" ? "quote" : "base"];
4412
+ if (giveCorridor === "arkade" && receiveCorridor === "arkade") return "spot";
4413
+ if (receiveCorridor === "lightning") return "ln_send";
4414
+ if (giveCorridor === "lightning") return "ln_receive";
4415
+ if (receiveCorridor === "onchain") return "onchain_send";
4416
+ return "onchain_receive";
4417
+ };
4418
+ var need = (value, what, leg) => {
4419
+ if (value === void 0) throw new Error(`a ${leg} quote needs ${what}`);
4420
+ return value;
4421
+ };
4422
+ var corridorAmount = (input, leg) => {
4423
+ const raw = need(input.amount, "an amount", leg);
4424
+ const amountOn = need(input.amountOn, "amountOn ('give' or 'receive')", leg);
4425
+ const amount = Number(raw);
4426
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
4427
+ throw new Error(`a ${leg} amount must be a positive integer of sats, got ${String(raw)}`);
4428
+ }
4429
+ return { amount, amountSide: amountOn === "give" ? "from" : "to" };
4430
+ };
4431
+ function createSwapClient(deps) {
4432
+ const { wallet, repository, transportFor } = deps;
4433
+ let broadcaster;
4434
+ const broadcasting = () => broadcaster ??= wallet.getArkadeBroadcaster();
4435
+ const ark = deps.ark ?? {
4436
+ getInfo: () => wallet.getArkadeInfo({ requireLive: true }),
4437
+ submitTx: async (...args) => (await broadcasting()).submitTx(...args),
4438
+ finalizeTx: async (...args) => (await broadcasting()).finalizeTx(...args)
4439
+ };
4440
+ let reader;
4441
+ const reading = () => reader ??= wallet.getArkadeReader();
4442
+ const indexer = deps.indexer ?? {
4443
+ getVtxos: async (opts) => {
4444
+ if (!opts)
4445
+ throw new Error("getVtxos on the swap indexer requires scripts or outpoints");
4446
+ return (await reading()).getVtxos(opts);
4447
+ },
4448
+ getVirtualTxs: async (...args) => (await reading()).getVirtualTxs(...args)
4449
+ };
4450
+ const managerDeps = { indexer, chain: deps.chain, repository };
4451
+ const manager = new RfqSwapManager(managerDeps);
4452
+ const listeners = /* @__PURE__ */ new Set();
4453
+ const notify2 = (swap) => {
4454
+ for (const listener of listeners) {
4455
+ try {
4456
+ listener(swap);
4457
+ } catch {
4458
+ }
4459
+ }
4460
+ };
4461
+ manager.onSwapUpdate((swap) => notify2({ family: "rfq", swap }));
4462
+ const claimLockup = async (swap, vtxos, { partiallyClaimed }) => {
4463
+ const record = await repository.getRfqSwap(swap.rfqId);
4464
+ if (!record) throw new Error(`rfq swap ${swap.rfqId} has no stored record to claim from`);
4465
+ const secret = rfqClaimSecretOf(record);
4466
+ if (!secret) throw new Error(`rfq swap ${swap.rfqId} carries no claim secret`);
4467
+ const script = swap.lockup?.script;
4468
+ if (!script) throw new Error(`rfq swap ${swap.rfqId} carries no lockup covenant`);
4469
+ const payoutAddress = record.profile.payoutAddress;
4470
+ if (!payoutAddress) throw new Error(`rfq swap ${swap.rfqId} carries no payoutAddress`);
4471
+ return pushClaim(ark, {
4472
+ contract: script,
4473
+ receiver: await (0, import_sdk15.contractSigner)(wallet, secret.signingDescriptor),
4474
+ preimage: await preimageForSwapRecord(wallet, secret),
4475
+ vtxos,
4476
+ destinationPkScript: import_sdk15.ArkAddress.decode(payoutAddress).pkScript,
4477
+ expectedAmount: swap.expectedAmount,
4478
+ partiallyClaimed
4479
+ });
4480
+ };
4481
+ manager.setCallbacks({
4482
+ refundArkade: arkadeRefunder({ operator: ark, indexer, wallet, repository }),
4483
+ claimLockup,
4484
+ ...deps.claimOnchain ? { claimOnchain: deps.claimOnchain } : {}
4485
+ });
4486
+ let watcher;
4487
+ const quote = async (market, input) => {
4488
+ const kind = resolveKind(market, input.give);
4489
+ switch (kind) {
4490
+ case "spot": {
4491
+ const raw = need(input.amount, "an amount", kind);
4492
+ const amountOn = need(input.amountOn, "amountOn ('give' or 'receive')", kind);
4493
+ const plan = await (0, import_solver_discovery2.quoteOffer)(market, {
4494
+ give: input.give,
4495
+ ...amountOn === "give" ? { giveAmount: raw } : { wantAmount: raw },
4496
+ fetchImpl: deps.discovery.fetchImpl
4497
+ });
4498
+ return { kind, market, plan };
4499
+ }
4500
+ case "ln_send": {
4501
+ const invoice = need(input.invoice, "the invoice to pay", kind);
4502
+ const request = await requestLightningSend(wallet, transportFor(market), {
4503
+ invoice,
4504
+ emulatorPubkey: deps.emulatorPubkey
4505
+ });
4506
+ return { kind, market, request };
4507
+ }
4508
+ case "ln_receive": {
4509
+ const params = {
4510
+ ...corridorAmount(input, kind),
4511
+ covclaimdPubkey: need(deps.covclaimdPubkey, "deps.covclaimdPubkey", kind),
4512
+ decodeInvoice: need(deps.decodeInvoice, "deps.decodeInvoice", kind),
4513
+ maxPayAmount: input.maxPayAmount,
4514
+ emulatorPubkey: deps.emulatorPubkey
4515
+ };
4516
+ const request = await requestLightningReceive(wallet, transportFor(market), params);
4517
+ return { kind, market, request, invoice: request.invoice };
4518
+ }
4519
+ case "onchain_send": {
4520
+ need(deps.chain, "deps.chain (L1 access)", kind);
4521
+ const params = {
4522
+ ...corridorAmount(input, kind),
4523
+ payoutPubkey: need(input.payoutPubkey, "a payoutPubkey", kind),
4524
+ preimage: input.preimage,
4525
+ emulatorPubkey: deps.emulatorPubkey
4526
+ };
4527
+ const request = await requestOnchainSend(wallet, transportFor(market), params);
4528
+ return { kind, market, request };
4529
+ }
4530
+ case "onchain_receive":
4531
+ throw new Error(
4532
+ "onchain->arkade is not driven by RfqSwapManager yet; quote it directly with requestOnchainReceive"
4533
+ );
4534
+ }
4535
+ };
4536
+ const admit = async (swap, origin) => {
4537
+ let announced = false;
4538
+ const heard = manager.onSwapUpdate((updated) => {
4539
+ if (updated.rfqId === swap.rfqId) announced = true;
4540
+ });
4541
+ try {
4542
+ await manager.addSwap(swap, origin);
4543
+ } finally {
4544
+ heard();
4545
+ }
4546
+ const unified = { family: "rfq", swap };
4547
+ if (!announced) notify2(unified);
4548
+ return unified;
4549
+ };
4550
+ const fundPersisted = async (swap, origin, funding) => {
4551
+ await repository.saveRfqSwap(createRfqSwapRecord(origin, swap));
4552
+ return wallet.send({ address: funding.address, amount: funding.amount });
4553
+ };
4554
+ const accept = async (accepted) => {
4555
+ const now = Math.floor(Date.now() / 1e3);
4556
+ switch (accepted.kind) {
4557
+ case "spot": {
4558
+ const { plan } = accepted;
4559
+ const depositIsBtc = plan.deposit.asset.id === BTC_ASSET_ID;
4560
+ const offer = await createOffer(wallet, {
4561
+ wantAmount: plan.receive.atomic,
4562
+ ...plan.receive.asset.id === BTC_ASSET_ID ? { offerAsset: import_sdk15.asset.AssetId.fromString(plan.deposit.asset.id) } : { wantAsset: import_sdk15.asset.AssetId.fromString(plan.receive.asset.id) },
4563
+ emulatorPubkey: deps.emulatorPubkey
4564
+ });
4565
+ const txid = await wallet.send({
4566
+ address: offer.address,
4567
+ // asset deposits ride the SDK's dust-sat carrier
4568
+ amount: depositIsBtc ? Number(plan.deposit.atomic) : void 0,
4569
+ assets: depositIsBtc ? void 0 : [{ assetId: plan.deposit.asset.id, amount: plan.deposit.atomic }],
4570
+ extensions: [offer.extension]
4571
+ });
4572
+ const swap = {
4573
+ id: txid,
4574
+ fromAsset: plan.deposit.asset.id,
4575
+ toAsset: plan.receive.asset.id,
4576
+ fromAmount: plan.deposit.atomic.toString(),
4577
+ toAmount: plan.receive.atomic.toString(),
4578
+ swapAddress: offer.address,
4579
+ swapPkScript: import_base15.hex.encode(offer.swapPkScript),
4580
+ offerHex: offer.offerHex,
4581
+ fundingTxid: txid,
4582
+ status: "pending",
4583
+ createdAt: Date.now()
4584
+ };
4585
+ await addAssetSwap(repository, swap);
4586
+ const unified = { family: "offer", swap };
4587
+ notify2(unified);
4588
+ return unified;
4589
+ }
4590
+ case "ln_send": {
4591
+ const { request } = accepted;
4592
+ const swap = {
4593
+ kind: "lightning_send",
4594
+ rfqId: request.rfqId,
4595
+ state: "pending",
4596
+ lockupPkScript: request.swapPkScript,
4597
+ lockup: { script: request.script, address: request.address },
4598
+ // the trader's own decode, never the solver's echo
4599
+ paymentHash: request.contractParams.paymentHash,
4600
+ refundLocktime: request.contractParams.refundLocktime,
4601
+ createdAt: now,
4602
+ updatedAt: now
4603
+ };
4604
+ const origin = {
4605
+ kind: "lightning_send",
4606
+ lockupAddress: request.address,
4607
+ profile: rfqSecretsProfile(request.secrets, swap.paymentHash),
4608
+ amount: request.fundAmount
4609
+ };
4610
+ const txid = await fundPersisted(swap, origin, {
4611
+ address: request.address,
4612
+ amount: request.fundAmount
4613
+ });
4614
+ return admit(swap, { ...origin, fundingTxid: txid });
4615
+ }
4616
+ case "ln_receive": {
4617
+ const { request } = accepted;
4618
+ const paymentHash = import_base15.hex.encode(request.secrets.paymentHash);
4619
+ const swap = {
4620
+ kind: "lightning_receive",
4621
+ rfqId: request.rfqId,
4622
+ state: "pending",
4623
+ lockupPkScript: request.swapPkScript,
4624
+ lockup: { script: request.script, address: request.address },
4625
+ paymentHash,
4626
+ refundLocktime: request.contractParams.refundLocktime,
4627
+ expectedAmount: request.expectedAmount,
4628
+ createdAt: now,
4629
+ updatedAt: now
4630
+ };
4631
+ return admit(swap, {
4632
+ kind: "lightning_receive",
4633
+ lockupAddress: request.address,
4634
+ profile: {
4635
+ ...rfqSecretsProfile(request.secrets, paymentHash),
4636
+ expectedAmount: request.expectedAmount,
4637
+ payoutAddress: request.payoutAddress
4638
+ },
4639
+ amount: request.expectedAmount
4640
+ });
4641
+ }
4642
+ case "onchain_send": {
4643
+ const { request } = accepted;
4644
+ const paymentHash = import_base15.hex.encode(request.secrets.paymentHash);
4645
+ const swap = {
4646
+ kind: "onchain_send",
4647
+ rfqId: request.rfqId,
4648
+ state: "pending",
4649
+ lockupPkScript: request.swapPkScript,
4650
+ lockup: { script: request.script, address: request.address },
4651
+ paymentHash,
4652
+ refundLocktime: request.refundLocktime,
4653
+ htlc: request.htlc,
4654
+ minConfirmations: request.minConfirmations,
4655
+ createdAt: now,
4656
+ updatedAt: now
4657
+ };
4658
+ const origin = {
4659
+ kind: "onchain_send",
4660
+ lockupAddress: request.address,
4661
+ profile: {
4662
+ ...rfqSecretsProfile(request.secrets, paymentHash),
4663
+ ...onchainSendProfile(request)
4664
+ },
4665
+ amount: request.fundAmount
4666
+ };
4667
+ const txid = await fundPersisted(swap, origin, {
4668
+ address: request.address,
4669
+ amount: request.fundAmount
4670
+ });
4671
+ return admit(swap, { ...origin, fundingTxid: txid });
4672
+ }
4673
+ }
4674
+ };
4675
+ return {
4676
+ manager,
4677
+ markets: (useCache) => discoverMarkets({ ...deps.discovery, repository, useCache }),
4678
+ quote,
4679
+ accept,
4680
+ cancel: async (fundingTxid) => {
4681
+ const swaps = await getAssetSwapsOrThrow(repository);
4682
+ const swap = swaps.find((s) => s.id === fundingTxid);
4683
+ if (!swap) throw new Error(`no offer swap with funding txid ${fundingTxid}`);
4684
+ if (swap.status !== "pending") {
4685
+ throw new Error(`offer swap ${fundingTxid} is ${swap.status}, not cancellable`);
4686
+ }
4687
+ await updateAssetSwap(repository, fundingTxid, { status: "cancelling" });
4688
+ await cancelOffer(wallet, swap.offerHex, {
4689
+ repository,
4690
+ fundingTxid
4691
+ });
4692
+ },
4693
+ // Both families, live AND terminal — the offer half reads the whole
4694
+ // stored history, and the RFQ half is every swap the manager holds.
4695
+ // Its history is what `start()` restored plus what this session
4696
+ // accepted; records past `RFQ_SWAP_RETENTION_SECONDS` are pruned.
4697
+ swaps: async () => {
4698
+ const offers = await getAssetSwaps(repository);
4699
+ const rfq = await manager.getAllSwaps();
4700
+ return [
4701
+ ...offers.map((swap) => ({ family: "offer", swap })),
4702
+ ...rfq.map((swap) => ({ family: "rfq", swap }))
4703
+ ];
4704
+ },
4705
+ onUpdate: (listener) => {
4706
+ listeners.add(listener);
4707
+ return () => listeners.delete(listener);
4708
+ },
4709
+ start: async () => {
4710
+ managerDeps.contracts ??= await wallet.getContractManager();
4711
+ await manager.restoreFromRepository();
4712
+ await manager.start();
4713
+ watcher ??= await watchOfferSwaps({
4714
+ wallet,
4715
+ repository,
4716
+ onUpdate: (swap) => notify2({ family: "offer", swap })
4717
+ });
4718
+ },
4719
+ stop: async () => {
4720
+ await manager.stop();
4721
+ watcher?.stop();
4722
+ watcher = void 0;
4723
+ }
4724
+ };
4725
+ }
4192
4726
  // Annotate the CommonJS export names for ESM import in node:
4193
4727
  0 && (module.exports = {
4194
4728
  ARKADE_ASSET,
@@ -4250,6 +4784,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4250
4784
  classifySpend,
4251
4785
  createOffer,
4252
4786
  createRfqSwapRecord,
4787
+ createSwapClient,
4253
4788
  decodeOffer,
4254
4789
  deriveLightningReceive,
4255
4790
  deriveOnchainReceive,
@@ -4264,16 +4799,18 @@ async function lockupTxids(indexer, record, wantFunding) {
4264
4799
  httpTransport,
4265
4800
  isRfqSwapTerminal,
4266
4801
  isRfqTerminal,
4802
+ lightningReceiveContract,
4267
4803
  lightningReceiveRequest,
4804
+ lightningSendContract,
4268
4805
  lightningSendRequest,
4269
- lightningSendVtxoScript,
4270
4806
  lockupContractParams,
4271
4807
  makeCachedFeedFetch,
4272
4808
  newPreimage,
4273
4809
  newRfqId,
4274
4810
  nextOnchainAction,
4811
+ normalizeRfqSwapRecord,
4812
+ offerContract,
4275
4813
  offerTermsFromQuote,
4276
- offerVtxoScript,
4277
4814
  onchainHtlcScript,
4278
4815
  onchainReceiveRequest,
4279
4816
  onchainSendProfile,
@@ -4284,7 +4821,6 @@ async function lockupTxids(indexer, record, wantFunding) {
4284
4821
  pushRefundWithoutReceiver,
4285
4822
  readLockupFate,
4286
4823
  rebuildRfqSwap,
4287
- receiveVtxoScript,
4288
4824
  refundIfUnresolved,
4289
4825
  registerLockupContract,
4290
4826
  relayTransport,