@arkade-os/swap 0.0.11 → 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,
@@ -426,7 +428,7 @@ function withExitClosure(program, exit) {
426
428
  }
427
429
  };
428
430
  }
429
- function swapProgramBinding(offer, serverPubkey) {
431
+ function swapProgramBinding(offer, operatorPubkey) {
430
432
  if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
431
433
  throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
432
434
  }
@@ -438,7 +440,7 @@ function swapProgramBinding(offer, serverPubkey) {
438
440
  args: {
439
441
  makerWP: offer.makerPkScript.subarray(2),
440
442
  wantAmount: offer.wantAmount,
441
- server: serverPubkey,
443
+ server: operatorPubkey,
442
444
  user: offer.makerPublicKey,
443
445
  // internal byte order
444
446
  ...offer.wantAsset && {
@@ -448,14 +450,14 @@ function swapProgramBinding(offer, serverPubkey) {
448
450
  ...offer.exitDelay && { exitDelay: offer.exitDelay.value }
449
451
  },
450
452
  keys: {
451
- serverKey: serverPubkey,
453
+ serverKey: operatorPubkey,
452
454
  userKey: offer.makerPublicKey,
453
455
  emulatorKey: offer.emulatorPubkey
454
456
  }
455
457
  };
456
458
  }
457
- function offerVtxoScript(offer, serverPubkey) {
458
- const { program, args, keys } = swapProgramBinding(offer, serverPubkey);
459
+ function offerContract(offer, operatorPubkey) {
460
+ const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
459
461
  return new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
460
462
  }
461
463
  var OFFER_PACKET_TYPE = 3;
@@ -569,14 +571,14 @@ function decodeOffer(data) {
569
571
  throw new Error(`missing/invalid ${name}`);
570
572
  }
571
573
  }
572
- const need = (name) => {
574
+ const need2 = (name) => {
573
575
  const v = fields[name];
574
576
  const len = FIELDS[name].width;
575
577
  if (!v || len !== void 0 && v.length !== len)
576
578
  throw new Error(`missing/invalid ${name}`);
577
579
  return v;
578
580
  };
579
- const amount = need("wantAmount");
581
+ const amount = need2("wantAmount");
580
582
  if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
581
583
  throw new Error("offer must carry exactly one of wantAsset or offerAsset");
582
584
  }
@@ -593,13 +595,13 @@ function decodeOffer(data) {
593
595
  throw new Error("offer must carry both ratioNum and ratioDen, or neither");
594
596
  }
595
597
  return {
596
- swapPkScript: need("swapPkScript"),
598
+ swapPkScript: need2("swapPkScript"),
597
599
  wantAmount: readU64(amount),
598
600
  ...fields.wantAsset && { wantAsset: import_sdk2.asset.AssetId.fromBytes(fields.wantAsset) },
599
601
  ...fields.offerAsset && { offerAsset: import_sdk2.asset.AssetId.fromBytes(fields.offerAsset) },
600
- makerPkScript: need("makerPkScript"),
601
- makerPublicKey: need("makerPublicKey"),
602
- emulatorPubkey: need("emulatorPubkey"),
602
+ makerPkScript: need2("makerPkScript"),
603
+ makerPublicKey: need2("makerPublicKey"),
604
+ emulatorPubkey: need2("emulatorPubkey"),
603
605
  ...ratioNum !== void 0 && { ratioNum },
604
606
  ...ratioDen !== void 0 && { ratioDen },
605
607
  ...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
@@ -612,17 +614,21 @@ function decodeExitDelay(value) {
612
614
  }
613
615
  var OFFER_CONTRACT_LABEL = "Arkade swap offer";
614
616
  var OFFER_CONTRACT_KIND = "asset-swap-offer";
615
- async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript) {
616
- const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
617
+ async function registerOfferContract(wallet, info, binding, operatorPubkey, expectedPkScript) {
618
+ const { program, args, keys } = swapProgramBinding(binding, operatorPubkey);
617
619
  const contractManager = await wallet.getContractManager();
618
620
  const client = await import_sdk2.arkade.Arkade.connect({
619
- arkade: new import_sdk2.RestArkProvider(arkServerUrl),
620
- 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 },
621
627
  identity: wallet.identity,
622
628
  // without this the row's `address` would be derived against the SDK's
623
629
  // default network while its script is right — a row that disagrees with
624
630
  // the address the user is about to fund
625
- network: (0, import_sdk2.getNetwork)(network),
631
+ network: (0, import_sdk2.networkFromArkadeInfo)(info),
626
632
  contractManager
627
633
  });
628
634
  const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
@@ -643,17 +649,21 @@ function serverExitDelay(delay) {
643
649
  }
644
650
  return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
645
651
  }
646
- async function createOffer(wallet, arkServerUrl, params) {
652
+ async function createOffer(wallet, params) {
647
653
  if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
648
654
  throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
649
655
  }
650
656
  const [info, makerAddress, makerPublicKey] = await Promise.all([
651
- 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 }),
652
662
  wallet.getAddress(),
653
663
  wallet.identity.xOnlyPublicKey()
654
664
  ]);
655
- const serverPubKey = import_base2.hex.decode((0, import_sdk2.toXOnlySignerHex)(info.signerPubkey));
656
- 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);
657
667
  const emuKey = import_base2.hex.decode(
658
668
  (0, import_sdk2.toXOnlySignerHex)((0, import_sdk2.resolveEmulatorPubkey)(network, params.emulatorPubkey))
659
669
  );
@@ -669,33 +679,33 @@ async function createOffer(wallet, arkServerUrl, params) {
669
679
  // an offer that then fails to encode. @see assertExitDelay
670
680
  exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
671
681
  };
672
- const script = offerVtxoScript(binding, serverPubKey);
682
+ const script = offerContract(binding, operatorPubKey);
673
683
  const offer = { ...binding, swapPkScript: script.pkScript };
674
- await registerOfferContract(
675
- wallet,
676
- arkServerUrl,
677
- info.network,
678
- binding,
679
- serverPubKey,
680
- script.pkScript
681
- );
684
+ await registerOfferContract(wallet, info, binding, operatorPubKey, script.pkScript);
682
685
  const payload = encodeOffer(offer);
683
686
  return {
684
687
  offerHex: import_base2.hex.encode(payload),
685
688
  extension: { type: OFFER_PACKET_TYPE, payload },
686
- // VtxoScript.address owns address construction; assembling an ArkAddress
689
+ // the contract's .address() builds the address; assembling an ArkAddress
687
690
  // from tweakedPublicKey here would silently miss any future step it gains
688
- address: script.address(network.hrp, serverPubKey).encode(),
691
+ address: script.address(network.hrp, operatorPubKey).encode(),
689
692
  swapPkScript: script.pkScript
690
693
  };
691
694
  }
692
- async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
695
+ async function cancelOffer(wallet, offerHex, opts) {
693
696
  const { repository, fundingTxid, swapAddress } = opts;
694
697
  const offer = decodeOffer(import_base2.hex.decode(offerHex));
695
- 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
+ ]);
696
704
  const client = await import_sdk2.arkade.Arkade.connect({
697
- arkade: new import_sdk2.RestArkProvider(arkServerUrl),
698
- 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,
699
709
  identity: wallet.identity,
700
710
  // registered offers resolve their VTXOs from the contract repository
701
711
  // instead of a direct indexer query; the indexer above stays as the
@@ -710,7 +720,7 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
710
720
  const rebuilt = new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
711
721
  if (import_base2.hex.encode(rebuilt.pkScript) !== import_base2.hex.encode(offer.swapPkScript)) {
712
722
  throw new Error(
713
- "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"
714
724
  );
715
725
  }
716
726
  const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
@@ -1374,7 +1384,7 @@ var LightningReceiveCorridor = {
1374
1384
  const receive = swap;
1375
1385
  return {
1376
1386
  expectedAmount: receive.expectedAmount,
1377
- ...receive.claimArkTxid ? { claimArkTxid: receive.claimArkTxid } : {}
1387
+ ...receive.claimTxid ? { claimTxid: receive.claimTxid } : {}
1378
1388
  };
1379
1389
  },
1380
1390
  hydrate(profile) {
@@ -1384,13 +1394,13 @@ var LightningReceiveCorridor = {
1384
1394
  return {
1385
1395
  ...hydrateHashlock(profile),
1386
1396
  expectedAmount: profile.expectedAmount,
1387
- ...profile.claimArkTxid ? { claimArkTxid: profile.claimArkTxid } : {}
1397
+ ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1388
1398
  };
1389
1399
  },
1390
1400
  // We are the claimant here, so the preimage material on the hashlock is
1391
1401
  // ours to use.
1392
1402
  claimSecret: (profile) => ({ ...profile.signer, ...profile.hashlock }),
1393
- activityTxids: (profile) => profile.claimArkTxid ? [profile.claimArkTxid] : []
1403
+ activityTxids: (profile) => profile.claimTxid ? [profile.claimTxid] : []
1394
1404
  };
1395
1405
  function onchainSendProfile(result) {
1396
1406
  return {
@@ -1474,13 +1484,32 @@ var isRfqSwapTerminal = (state) => RFQ_SWAP_TERMINAL_STATES.includes(state);
1474
1484
 
1475
1485
  // src/rfqRecord.ts
1476
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
+ }
1477
1506
  var managerState = (swap) => ({
1478
1507
  rfqId: swap.rfqId,
1479
1508
  state: swap.state,
1480
1509
  createdAt: swap.createdAt,
1481
1510
  updatedAt: swap.updatedAt,
1482
- ...swap.refundArkTxid ? { refundArkTxid: swap.refundArkTxid } : {},
1483
- ...swap.lockupSpendArkTxids?.length ? { lockupSpendArkTxids: [...swap.lockupSpendArkTxids] } : {},
1511
+ ...swap.refundTxid ? { refundTxid: swap.refundTxid } : {},
1512
+ ...swap.lockupSpendTxids?.length ? { lockupSpendTxids: [...swap.lockupSpendTxids] } : {},
1484
1513
  ...swap.failure ? { failure: swap.failure } : {},
1485
1514
  ...swap.blockedReason ? { blockedReason: swap.blockedReason } : {}
1486
1515
  });
@@ -1511,27 +1540,29 @@ function createRfqSwapRecord(origin, swap) {
1511
1540
  }
1512
1541
  function updateRfqSwapRecord(record, swap) {
1513
1542
  assertSameSwap(record, swap);
1543
+ const stored = normalizeRfqSwapRecord(record);
1514
1544
  const {
1515
- refundArkTxid: _refundArkTxid,
1516
- lockupSpendArkTxids: _lockupSpendArkTxids,
1545
+ refundTxid: _refundTxid,
1546
+ lockupSpendTxids: _lockupSpendTxids,
1517
1547
  failure: _failure,
1518
1548
  blockedReason: _blockedReason,
1519
1549
  ...origin
1520
- } = record;
1521
- const handler = rfqCorridorHandlers.getOrThrow(record.kind);
1550
+ } = stored;
1551
+ const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
1522
1552
  return {
1523
1553
  ...origin,
1524
1554
  ...managerState(swap),
1525
- profile: { ...record.profile, ...handler.project(swap) }
1555
+ profile: { ...stored.profile, ...handler.project(swap) }
1526
1556
  };
1527
1557
  }
1528
1558
  function rfqSwapOriginOf(record) {
1559
+ const stored = normalizeRfqSwapRecord(record);
1529
1560
  return {
1530
- kind: record.kind,
1531
- lockupAddress: record.lockupAddress,
1532
- profile: { ...record.profile },
1533
- ...record.amount !== void 0 ? { amount: record.amount } : {},
1534
- ...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 } : {}
1535
1566
  };
1536
1567
  }
1537
1568
  function lockupScript(params, lockupAddress) {
@@ -1545,27 +1576,28 @@ function lockupScript(params, lockupAddress) {
1545
1576
  return script;
1546
1577
  }
1547
1578
  function rebuildRfqSwap(record, params) {
1548
- const script = lockupScript(params, record.lockupAddress);
1579
+ const stored = normalizeRfqSwapRecord(record);
1580
+ const script = lockupScript(params, stored.lockupAddress);
1549
1581
  const common = {
1550
- rfqId: record.rfqId,
1551
- state: record.state,
1582
+ rfqId: stored.rfqId,
1583
+ state: stored.state,
1552
1584
  lockupPkScript: script.pkScript,
1553
- lockup: { script, address: record.lockupAddress },
1585
+ lockup: { script, address: stored.lockupAddress },
1554
1586
  // From the covenant, which binds it: the record's own copy would be a
1555
1587
  // second source for the deadline the refund is gated on.
1556
1588
  refundLocktime: Number(script.options.refundLocktime),
1557
- createdAt: record.createdAt,
1558
- updatedAt: record.updatedAt,
1559
- ...record.refundArkTxid ? { refundArkTxid: record.refundArkTxid } : {},
1560
- ...record.lockupSpendArkTxids?.length ? { lockupSpendArkTxids: [...record.lockupSpendArkTxids] } : {},
1561
- ...record.failure ? { failure: record.failure } : {},
1562
- ...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 } : {}
1563
1595
  };
1564
- const handler = rfqCorridorHandlers.getOrThrow(record.kind);
1596
+ const handler = rfqCorridorHandlers.getOrThrow(stored.kind);
1565
1597
  return {
1566
1598
  ...common,
1567
- kind: record.kind,
1568
- ...handler.hydrate(record.profile, { lockup: script })
1599
+ kind: stored.kind,
1600
+ ...handler.hydrate(stored.profile, { lockup: script })
1569
1601
  };
1570
1602
  }
1571
1603
  function shouldRetainRfqSwap(record, now) {
@@ -1602,10 +1634,10 @@ async function fetchParsedTxs(indexer, txids) {
1602
1634
  var unscannedSwapCandidates = (txs, existingIds, scanned) => txs.filter(
1603
1635
  (tx) => tx.type === "sent" && tx.redeemTxid && !existingIds.has(tx.redeemTxid) && !scanned.has(tx.redeemTxid)
1604
1636
  );
1605
- function classifySpend(offer, serverPubkey, spendTx, deposit) {
1637
+ function classifySpend(offer, operatorPubkey, spendTx, deposit) {
1606
1638
  let leaves;
1607
1639
  try {
1608
- const script = offerVtxoScript(offer, serverPubkey);
1640
+ const script = offerContract(offer, operatorPubkey);
1609
1641
  if (import_base6.hex.encode(script.pkScript) !== import_base6.hex.encode(offer.swapPkScript)) return "indeterminate";
1610
1642
  leaves = {
1611
1643
  // both routes that hand the deposit back; `exit` is absent on an
@@ -1629,15 +1661,15 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
1629
1661
  return "indeterminate";
1630
1662
  }
1631
1663
  var spendTxidsOf = (vtxo) => [vtxo.spentBy, vtxo.arkTxId].filter((id) => Boolean(id));
1632
- function classifyDepositSpend(offer, serverPubkey, spendTxs, deposit) {
1664
+ function classifyDepositSpend(offer, operatorPubkey, spendTxs, deposit) {
1633
1665
  for (const tx of spendTxs) {
1634
- const kind = classifySpend(offer, serverPubkey, tx, deposit);
1666
+ const kind = classifySpend(offer, operatorPubkey, tx, deposit);
1635
1667
  if (kind !== "indeterminate") return kind;
1636
1668
  }
1637
1669
  return "indeterminate";
1638
1670
  }
1639
1671
  async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1640
- const { serverPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
1672
+ const { operatorPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
1641
1673
  const candidates = unscannedSwapCandidates(txs, existingIds, scanned);
1642
1674
  if (candidates.length === 0) return { restored: [], scannedTxids: [] };
1643
1675
  const byTxid = new Map(candidates.map((tx) => [tx.redeemTxid, tx]));
@@ -1706,7 +1738,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1706
1738
  if (state === "swept") status = "recoverable";
1707
1739
  else if (state === "spent") {
1708
1740
  const spendTxs = spendTxidsOf(vtxo).map((id) => spendTxByTxid.get(id)).filter((tx) => tx !== void 0);
1709
- const kind = classifyDepositSpend(offer, serverPubkey, spendTxs, {
1741
+ const kind = classifyDepositSpend(offer, operatorPubkey, spendTxs, {
1710
1742
  txid: vtxo.txid,
1711
1743
  vout: vtxo.vout
1712
1744
  });
@@ -1723,7 +1755,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1723
1755
  fromAmount,
1724
1756
  toAmount: offer.wantAmount.toString(),
1725
1757
  // ponytail(arkade-os/ts-sdk#680): empty address makes cancel fall back
1726
- // 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
1727
1759
  // rotations become real (cancelOffer now at least diagnoses the
1728
1760
  // mismatch instead of reporting a missing VTXO)
1729
1761
  swapAddress: "",
@@ -1758,13 +1790,15 @@ function spendUpdate(swap, spend) {
1758
1790
  }
1759
1791
  async function watchOfferSwaps({
1760
1792
  wallet,
1761
- arkServerUrl,
1762
1793
  repository,
1763
1794
  onUpdate
1764
1795
  }) {
1765
- const manager = await wallet.getContractManager();
1766
- const serverPubkey = import_sdk7.ArkAddress.decode(await wallet.getAddress()).serverPubKey;
1767
- 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;
1768
1802
  let queue = Promise.resolve();
1769
1803
  const enqueue = (task) => {
1770
1804
  queue = queue.then(task).catch(() => {
@@ -1778,7 +1812,7 @@ async function watchOfferSwaps({
1778
1812
  const { txs } = await indexer.getVirtualTxs(candidates);
1779
1813
  return classifyDepositSpend(
1780
1814
  decodeOffer(import_base7.hex.decode(swap.offerHex)),
1781
- serverPubkey,
1815
+ operatorPubkey,
1782
1816
  txs.map((psbt) => import_sdk7.Transaction.fromPSBT(import_base7.base64.decode(psbt))),
1783
1817
  { txid: vtxo.txid, vout: vtxo.vout }
1784
1818
  );
@@ -1932,11 +1966,14 @@ var ONCHAIN_SEND_PAIR = rfqPair(ARKADE_BTC, ONCHAIN_BTC);
1932
1966
  var ONCHAIN_RECEIVE_PAIR = rfqPair(ONCHAIN_BTC, ARKADE_BTC);
1933
1967
  var RFQ_TERMINAL_STATES = ["settled", "refused", "expired", "refunded", "stuck"];
1934
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";
1935
1973
  reason;
1936
1974
  rfqId;
1937
1975
  constructor(reason, rfqId) {
1938
1976
  super(`solver refused: ${reason}`);
1939
- this.name = "SwapRefusal";
1940
1977
  this.reason = reason;
1941
1978
  this.rfqId = rfqId;
1942
1979
  }
@@ -2202,22 +2239,22 @@ var relayTransport = (relayUrl, options) => {
2202
2239
  };
2203
2240
  var SEQUENCE_GRANULARITY_SECONDS = 512;
2204
2241
  var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
2205
- var unilateralClaimDelay = (serverExitDelaySeconds) => {
2206
- if (!Number.isFinite(serverExitDelaySeconds) || serverExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
2242
+ var unilateralClaimDelay = (operatorExitDelaySeconds) => {
2243
+ if (!Number.isFinite(operatorExitDelaySeconds) || operatorExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
2207
2244
  throw new Error(
2208
- `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}`
2209
2246
  );
2210
2247
  }
2211
- if (serverExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
2248
+ if (operatorExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
2212
2249
  throw new Error(
2213
- `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`
2214
2251
  );
2215
2252
  }
2216
- return Math.ceil(serverExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
2253
+ return Math.ceil(operatorExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
2217
2254
  };
2218
2255
  var unilateralRefundDelay = (claimDelay) => claimDelay;
2219
2256
  var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay + SOLO_REFUND_HEADROOM_SECONDS;
2220
- function lightningSendVtxoScript(params) {
2257
+ function lightningSendContract(params) {
2221
2258
  const seconds = (value) => ({
2222
2259
  type: "seconds",
2223
2260
  value: BigInt(value)
@@ -2225,7 +2262,7 @@ function lightningSendVtxoScript(params) {
2225
2262
  return new import_sdk9.VHTLC.ScriptV2({
2226
2263
  sender: params.senderPubkey,
2227
2264
  receiver: params.solverPubkey,
2228
- server: params.serverPubkey,
2265
+ server: params.operatorPubkey,
2229
2266
  preimageHash: (0, import_legacy2.ripemd160)(import_base10.hex.decode(params.paymentHash)),
2230
2267
  refundLocktime: BigInt(params.refundLocktime),
2231
2268
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -2241,12 +2278,12 @@ function lightningSendVtxoScript(params) {
2241
2278
  }
2242
2279
  });
2243
2280
  }
2244
- async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2281
+ async function requestLightningSend(wallet, transport, params) {
2245
2282
  const rfqId = params.rfqId ?? newRfqId();
2246
2283
  const secrets = await (0, import_sdk10.provisionRefundKey)(wallet);
2247
2284
  const senderPubkey = secrets.pubkey;
2248
2285
  const refundAddress = secrets.address;
2249
- const info = await new import_sdk9.RestArkProvider(arkServerUrl).getInfo();
2286
+ const info = await wallet.getArkadeInfo({ requireLive: true });
2250
2287
  const quote = await transport.requestQuote(
2251
2288
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
2252
2289
  );
@@ -2267,12 +2304,12 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2267
2304
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
2268
2305
  );
2269
2306
  }
2270
- const serverPubkey = (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2271
- const network = (0, import_sdk9.getNetwork)(info.network);
2272
- 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 = {
2273
2310
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2274
2311
  refundLocktime: quote.refund_locktime,
2275
- serverPubkey,
2312
+ operatorPubkey,
2276
2313
  paymentHash: params.invoice.paymentHash,
2277
2314
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
2278
2315
  emulatorPubkey: (0, import_sdk9.toXOnly)(
@@ -2286,13 +2323,13 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2286
2323
  const matched = matchQuotedLockup(
2287
2324
  quote,
2288
2325
  network.hrp,
2289
- serverPubkey,
2290
- (legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
2326
+ operatorPubkey,
2327
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2291
2328
  );
2292
2329
  const script = matched.script;
2293
2330
  const address = matched.address;
2294
- const matchedTreeParams = {
2295
- ...treeParams,
2331
+ const matchedContractParams = {
2332
+ ...contractParams,
2296
2333
  ...matched.legacy !== void 0 && { legacy: matched.legacy }
2297
2334
  };
2298
2335
  assertFundable({
@@ -2313,7 +2350,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2313
2350
  refundAddress,
2314
2351
  senderPubkey,
2315
2352
  secrets,
2316
- treeParams: matchedTreeParams
2353
+ contractParams: matchedContractParams
2317
2354
  };
2318
2355
  }
2319
2356
  var offerTermsFromQuote = (quote, assets) => {
@@ -2378,10 +2415,10 @@ function deriveOnchainSend(input) {
2378
2415
  if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
2379
2416
  throw new Error("onchain-send quote is missing a binding field");
2380
2417
  }
2381
- const treeParams = {
2418
+ const contractParams = {
2382
2419
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2383
2420
  refundLocktime,
2384
- serverPubkey: input.serverPubkey,
2421
+ operatorPubkey: input.operatorPubkey,
2385
2422
  paymentHash: input.paymentHash,
2386
2423
  claimDelay: input.claimDelay,
2387
2424
  emulatorPubkey: input.emulatorPubkey,
@@ -2392,8 +2429,8 @@ function deriveOnchainSend(input) {
2392
2429
  const { script, address } = matchQuotedLockup(
2393
2430
  quote,
2394
2431
  input.hrp,
2395
- input.serverPubkey,
2396
- (legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
2432
+ input.operatorPubkey,
2433
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2397
2434
  );
2398
2435
  const htlcParams = {
2399
2436
  paymentHash: input.paymentHash,
@@ -2415,7 +2452,7 @@ function deriveOnchainSend(input) {
2415
2452
  minConfirmations
2416
2453
  };
2417
2454
  }
2418
- async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2455
+ async function requestOnchainSend(wallet, transport, params) {
2419
2456
  const rfqId = params.rfqId ?? newRfqId();
2420
2457
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet, { preimage: params.preimage });
2421
2458
  if (secrets.mustPersistPreimage) {
@@ -2426,7 +2463,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2426
2463
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2427
2464
  const senderPubkey = secrets.pubkey;
2428
2465
  const [info, refundAddress] = await Promise.all([
2429
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2466
+ wallet.getArkadeInfo({ requireLive: true }),
2430
2467
  wallet.getAddress()
2431
2468
  ]);
2432
2469
  const quote = await transport.requestQuote(
@@ -2440,12 +2477,12 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2440
2477
  amountSide: params.amountSide
2441
2478
  })
2442
2479
  );
2443
- const network = (0, import_sdk9.getNetwork)(info.network);
2480
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2444
2481
  const derived = deriveOnchainSend({
2445
2482
  quote,
2446
2483
  paymentHash,
2447
2484
  payoutPubkey: params.payoutPubkey,
2448
- 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"),
2449
2486
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2450
2487
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2451
2488
  "emulator signer key"
@@ -2482,6 +2519,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2482
2519
  htlcParams: derived.htlcParams,
2483
2520
  l1Network: derived.l1Network,
2484
2521
  minConfirmations: derived.minConfirmations,
2522
+ refundLocktime: derived.refundLocktime,
2485
2523
  senderPubkey,
2486
2524
  secrets
2487
2525
  };
@@ -2553,7 +2591,7 @@ var assertReceivable = (input) => {
2553
2591
  );
2554
2592
  }
2555
2593
  };
2556
- function receiveVtxoScript(params) {
2594
+ function lightningReceiveContract(params) {
2557
2595
  const seconds = (value) => ({
2558
2596
  type: "seconds",
2559
2597
  value: BigInt(value)
@@ -2561,7 +2599,7 @@ function receiveVtxoScript(params) {
2561
2599
  return new import_sdk9.VHTLC.ScriptV2({
2562
2600
  sender: params.solverPubkey,
2563
2601
  receiver: params.payoutPubkey,
2564
- server: params.serverPubkey,
2602
+ server: params.operatorPubkey,
2565
2603
  preimageHash: (0, import_legacy2.ripemd160)(import_base10.hex.decode(params.paymentHash)),
2566
2604
  refundLocktime: BigInt(params.refundLocktime),
2567
2605
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -2586,10 +2624,10 @@ function deriveLightningReceive(input) {
2586
2624
  if (refundLocktime === void 0 || invoice === void 0 || solverRefundPkScriptHex === void 0) {
2587
2625
  throw new Error("lightning-receive quote is missing a binding field");
2588
2626
  }
2589
- const treeParams = {
2627
+ const contractParams = {
2590
2628
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2591
2629
  refundLocktime,
2592
- serverPubkey: input.serverPubkey,
2630
+ operatorPubkey: input.operatorPubkey,
2593
2631
  paymentHash: input.paymentHash,
2594
2632
  claimDelay: input.claimDelay,
2595
2633
  emulatorPubkey: input.emulatorPubkey,
@@ -2600,8 +2638,8 @@ function deriveLightningReceive(input) {
2600
2638
  const matched = matchQuotedLockup(
2601
2639
  quote,
2602
2640
  input.hrp,
2603
- input.serverPubkey,
2604
- (legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
2641
+ input.operatorPubkey,
2642
+ (legacy) => lightningReceiveContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
2605
2643
  );
2606
2644
  return {
2607
2645
  address: matched.address,
@@ -2609,13 +2647,13 @@ function deriveLightningReceive(input) {
2609
2647
  script: matched.script,
2610
2648
  invoice,
2611
2649
  refundLocktime,
2612
- treeParams: {
2613
- ...treeParams,
2650
+ contractParams: {
2651
+ ...contractParams,
2614
2652
  ...matched.legacy !== void 0 && { legacy: matched.legacy }
2615
2653
  }
2616
2654
  };
2617
2655
  }
2618
- async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
2656
+ async function requestLightningReceive(wallet, transport, params) {
2619
2657
  const rfqId = params.rfqId ?? newRfqId();
2620
2658
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
2621
2659
  if (secrets.mustPersistPreimage) {
@@ -2627,7 +2665,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2627
2665
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2628
2666
  const payoutPubkey = secrets.pubkey;
2629
2667
  const [info, payoutAddress] = await Promise.all([
2630
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2668
+ wallet.getArkadeInfo({ requireLive: true }),
2631
2669
  wallet.getAddress()
2632
2670
  ]);
2633
2671
  const claimPacket = await sealClaimPacket({
@@ -2646,13 +2684,13 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2646
2684
  })
2647
2685
  );
2648
2686
  assertQuotedAmount(quote, params.amountSide, params.amount);
2649
- const network = (0, import_sdk9.getNetwork)(info.network);
2687
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2650
2688
  const derived = deriveLightningReceive({
2651
2689
  quote,
2652
2690
  paymentHash,
2653
2691
  payoutPubkey,
2654
2692
  payoutAddress,
2655
- 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"),
2656
2694
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2657
2695
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2658
2696
  "emulator signer key"
@@ -2686,7 +2724,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2686
2724
  payoutAddress,
2687
2725
  payoutPubkey,
2688
2726
  secrets,
2689
- treeParams: derived.treeParams
2727
+ contractParams: derived.contractParams
2690
2728
  };
2691
2729
  }
2692
2730
  function deriveOnchainReceive(input) {
@@ -2701,10 +2739,10 @@ function deriveOnchainReceive(input) {
2701
2739
  if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
2702
2740
  throw new Error("onchain-receive quote is missing a binding field");
2703
2741
  }
2704
- const treeParams = {
2742
+ const contractParams = {
2705
2743
  solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2706
2744
  refundLocktime,
2707
- serverPubkey: input.serverPubkey,
2745
+ operatorPubkey: input.operatorPubkey,
2708
2746
  paymentHash: input.paymentHash,
2709
2747
  claimDelay: input.claimDelay,
2710
2748
  emulatorPubkey: input.emulatorPubkey,
@@ -2715,8 +2753,11 @@ function deriveOnchainReceive(input) {
2715
2753
  const { script, address } = matchQuotedLockup(
2716
2754
  quote,
2717
2755
  input.hrp,
2718
- input.serverPubkey,
2719
- (legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
2756
+ input.operatorPubkey,
2757
+ (legacy) => lightningReceiveContract({
2758
+ ...contractParams,
2759
+ ...legacy !== void 0 && { legacy }
2760
+ })
2720
2761
  );
2721
2762
  const htlc = onchainHtlcScript(
2722
2763
  {
@@ -2738,7 +2779,7 @@ function deriveOnchainReceive(input) {
2738
2779
  minConfirmations
2739
2780
  };
2740
2781
  }
2741
- async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2782
+ async function requestOnchainReceive(wallet, transport, params) {
2742
2783
  const rfqId = params.rfqId ?? newRfqId();
2743
2784
  const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
2744
2785
  if (secrets.mustPersistPreimage) {
@@ -2750,7 +2791,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2750
2791
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2751
2792
  const payoutPubkey = secrets.pubkey;
2752
2793
  const [info, payoutAddress] = await Promise.all([
2753
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2794
+ wallet.getArkadeInfo({ requireLive: true }),
2754
2795
  wallet.getAddress()
2755
2796
  ]);
2756
2797
  const claimPacket = await sealClaimPacket({
@@ -2770,14 +2811,14 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2770
2811
  })
2771
2812
  );
2772
2813
  assertQuotedAmount(quote, params.amountSide, params.amount);
2773
- const network = (0, import_sdk9.getNetwork)(info.network);
2814
+ const network = (0, import_sdk9.networkFromArkadeInfo)(info);
2774
2815
  const derived = deriveOnchainReceive({
2775
2816
  quote,
2776
2817
  paymentHash,
2777
2818
  payoutPubkey,
2778
2819
  payoutAddress,
2779
2820
  refundPubkey: params.refundPubkey,
2780
- 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"),
2781
2822
  emulatorPubkey: (0, import_sdk9.toXOnly)(
2782
2823
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2783
2824
  "emulator signer key"
@@ -2855,11 +2896,10 @@ var LockupNeedsRecoveryError = class extends Error {
2855
2896
  * into one settlement with no CLTV awareness, so an early attempt can fail
2856
2897
  * the whole batch — including unrelated outputs that were otherwise fine.
2857
2898
  *
2858
- * Exposed as a value, not only inside the message, so a caller can encode
2859
- * `packages/boltz-swap`'s "pre-CLTV recoverable skipped" rule without
2860
- * parsing prose. Seconds-based locktimes mature against the chain tip's
2861
- * timestamp rather than wall clock, so treat this as a floor to wait past,
2862
- * 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.
2863
2903
  */
2864
2904
  recoverableAfter;
2865
2905
  constructor(outpoints, recoverableAfter) {
@@ -2920,7 +2960,7 @@ async function readLockupFate(indexer, input) {
2920
2960
  if (vtxo.spentBy)
2921
2961
  spentBy.set(vtxo.spentBy, {
2922
2962
  checkpointTxid: vtxo.spentBy,
2923
- arkTxid: vtxo.arkTxId
2963
+ txid: vtxo.arkTxId
2924
2964
  });
2925
2965
  else everySpendNamed = false;
2926
2966
  }
@@ -2949,32 +2989,32 @@ async function readLockupFate(indexer, input) {
2949
2989
  }
2950
2990
  return everySpendNamed && observed.size === spentBy.size ? { fate: "returned", spends } : { fate: "unknown" };
2951
2991
  }
2952
- async function pushRefundWithoutReceiver(ark, input) {
2992
+ async function pushRefundWithoutReceiver(operator, input) {
2953
2993
  if (input.vtxos.length === 0) throw new Error("nothing to refund: no funded outputs");
2954
2994
  const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
2955
2995
  if (swept.length > 0) {
2956
2996
  throw new LockupNeedsRecoveryError(
2957
2997
  swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
2958
- input.script.options.refundLocktime
2998
+ input.contract.options.refundLocktime
2959
2999
  );
2960
3000
  }
2961
- const refundPkScript = input.refundPkScript ?? input.script.options.nonInteractiveParameters?.senderPkScript;
3001
+ const refundPkScript = input.refundPkScript ?? input.contract.options.nonInteractiveParameters?.senderPkScript;
2962
3002
  if (!refundPkScript) {
2963
3003
  throw new Error(
2964
3004
  "no refund destination: the contract carries no emulator covenant suite, so pass refundPkScript explicitly"
2965
3005
  );
2966
3006
  }
2967
- const info = await ark.getInfo();
2968
- let serverUnrollScript;
3007
+ const info = await operator.getInfo();
3008
+ let operatorUnrollScript;
2969
3009
  try {
2970
- serverUnrollScript = import_sdk11.CSVMultisigTapscript.decode(import_base11.hex.decode(info.checkpointTapscript));
3010
+ operatorUnrollScript = import_sdk11.CSVMultisigTapscript.decode(import_base11.hex.decode(info.checkpointTapscript));
2971
3011
  } catch {
2972
- throw new Error("invalid checkpointTapscript from the Arkade server");
3012
+ throw new Error("invalid checkpointTapscript from the operator");
2973
3013
  }
2974
- const leaf = input.script.refundWithoutReceiver();
2975
- const tapTree = input.script.encode();
3014
+ const leaf = input.contract.refundWithoutReceiver();
3015
+ const tapTree = input.contract.encode();
2976
3016
  const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
2977
- const { arkTx, checkpoints } = (0, import_sdk11.buildOffchainTx)(
3017
+ const { arkTx: tx, checkpoints } = (0, import_sdk11.buildOffchainTx)(
2978
3018
  input.vtxos.map((vtxo) => ({
2979
3019
  txid: vtxo.txid,
2980
3020
  vout: vtxo.vout,
@@ -2983,14 +3023,14 @@ async function pushRefundWithoutReceiver(ark, input) {
2983
3023
  tapTree
2984
3024
  })),
2985
3025
  [{ script: refundPkScript, amount: BigInt(amount) }],
2986
- serverUnrollScript
3026
+ operatorUnrollScript
2987
3027
  );
2988
- const signedArkTx = await input.sender.sign(arkTx);
2989
- const submitted = await ark.submitTx(
2990
- 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()),
2991
3031
  checkpoints.map((c) => import_base11.base64.encode(c.toPSBT()))
2992
3032
  );
2993
- (0, import_sdk11.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
3033
+ (0, import_sdk11.assertSubmittedArkTxid)(submitted, signedTx, "refundWithoutReceiver");
2994
3034
  const matched = (0, import_sdk11.matchServerCheckpoints)(
2995
3035
  submitted.signedCheckpointTxs,
2996
3036
  checkpoints,
@@ -3001,11 +3041,11 @@ async function pushRefundWithoutReceiver(ark, input) {
3001
3041
  async ({ server }) => import_base11.base64.encode((await input.sender.sign(server, [0])).toPSBT())
3002
3042
  )
3003
3043
  );
3004
- await ark.finalizeTx(submitted.arkTxid, finalCheckpoints);
3005
- return { arkTxid: submitted.arkTxid, amount };
3044
+ await operator.finalizeTx(submitted.arkTxid, finalCheckpoints);
3045
+ return { txid: submitted.arkTxid, amount };
3006
3046
  }
3007
3047
  var REFUND_MTP_LAG_SECONDS = 2 * 60 * 60;
3008
- async function refundIfUnresolved(transport, ark, indexer, input) {
3048
+ async function refundIfUnresolved(transport, operator, indexer, input) {
3009
3049
  const pollMs = input.pollMs ?? 5e3;
3010
3050
  const now = input.now ?? (() => Math.floor(Date.now() / 1e3));
3011
3051
  const attemptDeadline = input.attemptDeadline ?? input.refundLocktime + REFUND_MTP_LAG_SECONDS;
@@ -3016,7 +3056,7 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
3016
3056
  let fate = { fate: "unknown" };
3017
3057
  try {
3018
3058
  fate = await readLockupFate(indexer, {
3019
- swapPkScript: input.script.pkScript,
3059
+ swapPkScript: input.contract.pkScript,
3020
3060
  paymentHash: input.paymentHash
3021
3061
  });
3022
3062
  } catch {
@@ -3028,11 +3068,11 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
3028
3068
  status
3029
3069
  };
3030
3070
  }
3031
- const vtxos = await findLockupVtxos(indexer, input.script.pkScript);
3071
+ const vtxos = await findLockupVtxos(indexer, input.contract.pkScript);
3032
3072
  if (vtxos.length === 0) return { outcome: "nothing_to_refund", status };
3033
3073
  try {
3034
- const pushed = await pushRefundWithoutReceiver(ark, {
3035
- script: input.script,
3074
+ const pushed = await pushRefundWithoutReceiver(operator, {
3075
+ contract: input.contract,
3036
3076
  sender: input.sender,
3037
3077
  vtxos,
3038
3078
  refundPkScript: input.refundPkScript
@@ -3075,13 +3115,13 @@ var assertFiniteAmount = (value, reason, label) => {
3075
3115
  error.reason = reason;
3076
3116
  throw error;
3077
3117
  };
3078
- async function pushClaim(ark, input) {
3118
+ async function pushClaim(operator, input) {
3079
3119
  if (input.vtxos.length === 0) throw new Error("nothing to claim: no funded outputs");
3080
3120
  const swept = input.vtxos.filter((vtxo) => vtxo.recoverable);
3081
3121
  if (swept.length > 0) {
3082
3122
  throw new LockupNeedsRecoveryError(
3083
3123
  swept.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`),
3084
- input.script.options.refundLocktime
3124
+ input.contract.options.refundLocktime
3085
3125
  );
3086
3126
  }
3087
3127
  const locked = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
@@ -3092,22 +3132,22 @@ async function pushClaim(ark, input) {
3092
3132
  throw new LockupAmountMismatchError(input.expectedAmount, locked);
3093
3133
  }
3094
3134
  }
3095
- const committed = input.script.options.preimageHash;
3135
+ const committed = input.contract.options.preimageHash;
3096
3136
  if (import_base12.hex.encode((0, import_legacy3.ripemd160)((0, import_sha25.sha256)(input.preimage))) !== import_base12.hex.encode(committed)) {
3097
3137
  throw new Error("preimage does not match the covenant's payment hash");
3098
3138
  }
3099
- const info = await ark.getInfo();
3100
- let serverUnrollScript;
3139
+ const info = await operator.getInfo();
3140
+ let operatorUnrollScript;
3101
3141
  try {
3102
- serverUnrollScript = import_sdk12.CSVMultisigTapscript.decode(import_base12.hex.decode(info.checkpointTapscript));
3142
+ operatorUnrollScript = import_sdk12.CSVMultisigTapscript.decode(import_base12.hex.decode(info.checkpointTapscript));
3103
3143
  } catch {
3104
- throw new Error("invalid checkpointTapscript from the Arkade server");
3144
+ throw new Error("invalid checkpointTapscript from the operator");
3105
3145
  }
3106
- const leaf = input.script.claim();
3107
- const tapTree = input.script.encode();
3108
- 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)({
3109
3149
  identity: (0, import_sdk12.claimWithPreimageIdentity)(input.receiver, input.preimage),
3110
- provider: ark,
3150
+ provider: operator,
3111
3151
  inputs: input.vtxos.map((vtxo) => ({
3112
3152
  txid: vtxo.txid,
3113
3153
  vout: vtxo.vout,
@@ -3118,10 +3158,10 @@ async function pushClaim(ark, input) {
3118
3158
  // One aggregate output: unlike the covenant refund, this leaf inspects
3119
3159
  // nothing about the output set.
3120
3160
  outputs: [{ script: input.destinationPkScript, amount: BigInt(locked) }],
3121
- serverUnrollScript,
3122
- verifyServerSignatures: { serverPubkey: input.script.options.server }
3161
+ serverUnrollScript: operatorUnrollScript,
3162
+ verifyServerSignatures: { serverPubkey: input.contract.options.server }
3123
3163
  });
3124
- return { arkTxid, amount: locked };
3164
+ return { txid, amount: locked };
3125
3165
  }
3126
3166
  async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
3127
3167
  const pollMs = options.pollMs ?? 5e3;
@@ -3136,13 +3176,13 @@ async function awaitLockupFunding(indexer, swapPkScript, options = {}) {
3136
3176
  await sleep3(pollMs);
3137
3177
  }
3138
3178
  }
3139
- async function claimReceiveLockup(indexer, ark, input) {
3179
+ async function claimReceiveLockup(indexer, operator, input) {
3140
3180
  const vtxos = await awaitLockupFunding(indexer, input.swapPkScript, {
3141
3181
  pollMs: input.pollMs,
3142
3182
  deadline: input.deadline
3143
3183
  });
3144
- return pushClaim(ark, {
3145
- script: input.script,
3184
+ return pushClaim(operator, {
3185
+ contract: input.contract,
3146
3186
  receiver: input.receiver,
3147
3187
  preimage: input.preimage,
3148
3188
  vtxos,
@@ -3193,8 +3233,8 @@ async function senderIdentityForSwapRecord(wallet, record) {
3193
3233
  // src/arkadeRefunder.ts
3194
3234
  function arkadeRefunder(deps) {
3195
3235
  return async (swap) => {
3196
- const script = swap.lockup?.script;
3197
- if (!script) {
3236
+ const contract = swap.lockup?.script;
3237
+ if (!contract) {
3198
3238
  throw new Error(
3199
3239
  `swap ${swap.rfqId} carries no lockup covenant, so its refund cannot be built`
3200
3240
  );
@@ -3209,7 +3249,7 @@ function arkadeRefunder(deps) {
3209
3249
  );
3210
3250
  }
3211
3251
  const sender = await senderIdentityForSwapRecord(deps.wallet, rfqSignerOf(record) ?? {});
3212
- return pushRefundWithoutReceiver(deps.ark, { script, sender, vtxos });
3252
+ return pushRefundWithoutReceiver(deps.operator, { contract, sender, vtxos });
3213
3253
  };
3214
3254
  }
3215
3255
 
@@ -3605,6 +3645,19 @@ var RfqSwapManager = class {
3605
3645
  async getPendingSwaps() {
3606
3646
  return [...this.monitored.values()];
3607
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
+ }
3608
3661
  async hasSwap(rfqId) {
3609
3662
  return this.monitored.has(rfqId);
3610
3663
  }
@@ -3883,14 +3936,14 @@ var RfqSwapManager = class {
3883
3936
  return this.claimIfFunded(swap, vtxos);
3884
3937
  }
3885
3938
  if (now < swap.refundLocktime + REFUND_MTP_LAG_SECONDS) {
3886
- if (swap.claimArkTxid) return;
3939
+ if (swap.claimTxid) return;
3887
3940
  return this.block(
3888
3941
  swap,
3889
3942
  "the claim window closed with the lockup unclaimed \u2014 only the solver can act now"
3890
3943
  );
3891
3944
  }
3892
3945
  const failure = this.lastClaimError.get(swap.rfqId);
3893
- if (failure && !swap.claimArkTxid) {
3946
+ if (failure && !swap.claimTxid) {
3894
3947
  return this.fail(swap, new Error(failure));
3895
3948
  }
3896
3949
  this.setState(swap, "refunded");
@@ -3905,7 +3958,7 @@ var RfqSwapManager = class {
3905
3958
  */
3906
3959
  async claimIfFunded(swap, vtxos) {
3907
3960
  if (vtxos.length === 0) return this.unblock(swap);
3908
- const partiallyClaimed = swap.claimArkTxid !== void 0;
3961
+ const partiallyClaimed = swap.claimTxid !== void 0;
3909
3962
  if (partiallyClaimed && !this.hasUnclaimedOutpoint(swap.rfqId, vtxos)) {
3910
3963
  return;
3911
3964
  }
@@ -3933,10 +3986,10 @@ var RfqSwapManager = class {
3933
3986
  this.setState(swap, "claimable");
3934
3987
  if (!this.config.enableAutoActions) return;
3935
3988
  try {
3936
- const { arkTxid } = await this.callbacks.claimLockup(swap, vtxos, { partiallyClaimed });
3989
+ const { txid } = await this.callbacks.claimLockup(swap, vtxos, { partiallyClaimed });
3937
3990
  this.lastClaimError.delete(swap.rfqId);
3938
3991
  this.rememberClaimed(swap.rfqId, vtxos);
3939
- swap.claimArkTxid = arkTxid;
3992
+ swap.claimTxid = txid;
3940
3993
  this.touch(swap);
3941
3994
  this.setState(swap, "claimed");
3942
3995
  this.emitAction(swap, "claimLockup");
@@ -4028,7 +4081,7 @@ var RfqSwapManager = class {
4028
4081
  try {
4029
4082
  const pushed = await this.callbacks.refundArkade(swap);
4030
4083
  if (pushed) {
4031
- swap.refundArkTxid = pushed.arkTxid;
4084
+ swap.refundTxid = pushed.txid;
4032
4085
  this.touch(swap);
4033
4086
  }
4034
4087
  this.setState(swap, "refunded");
@@ -4101,7 +4154,7 @@ var RfqSwapManager = class {
4101
4154
  /**
4102
4155
  * Record which ark transactions ended the lockup.
4103
4156
  *
4104
- * Only the ones the indexer actually named: `LockupSpend.arkTxid` is
4157
+ * Only the ones the indexer actually named: `LockupSpend.txid` is
4105
4158
  * optional, and a checkpoint txid is not what history correlates on — a
4106
4159
  * record carrying one would name a transaction the wallet's own activity
4107
4160
  * never shows. Fewer txids is the right failure here.
@@ -4111,9 +4164,9 @@ var RfqSwapManager = class {
4111
4164
  * verdict once.
4112
4165
  */
4113
4166
  stampLockupSpends(swap, spends) {
4114
- const arkTxids = spends.map((spend) => spend.arkTxid).filter((txid) => txid !== void 0);
4115
- if (arkTxids.length === 0) return;
4116
- 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;
4117
4170
  this.touch(swap);
4118
4171
  }
4119
4172
  touch(swap) {
@@ -4194,10 +4247,9 @@ var RfqSwapManager = class {
4194
4247
  /**
4195
4248
  * Drop a terminal swap from monitoring and report it exactly once.
4196
4249
  *
4197
- * `onSwapCompleted` and `onSwapFailed` are mutually exclusive here, unlike
4198
- * Boltz's manager, which fires completion for every swap that leaves
4199
- * monitoring including the failed ones a listener named "completed" that
4200
- * 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.
4201
4253
  */
4202
4254
  finalize(swap) {
4203
4255
  if (!this.monitored.has(swap.rfqId)) return;
@@ -4233,7 +4285,7 @@ var traderClaimTxid = (swap) => {
4233
4285
  case "onchain_send":
4234
4286
  return swap.claimTxid;
4235
4287
  case "lightning_receive":
4236
- return swap.claimArkTxid;
4288
+ return swap.claimTxid;
4237
4289
  default:
4238
4290
  return void 0;
4239
4291
  }
@@ -4243,7 +4295,7 @@ var outcomeOf = (swap) => {
4243
4295
  const lostReceive = swap.kind === "lightning_receive" && swap.state === "refunded";
4244
4296
  return {
4245
4297
  state: swap.state,
4246
- txid: lostReceive ? swap.refundArkTxid : traderClaimTxid(swap) ?? swap.refundArkTxid
4298
+ txid: lostReceive ? swap.refundTxid : traderClaimTxid(swap) ?? swap.refundTxid
4247
4299
  };
4248
4300
  };
4249
4301
  var errorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -4308,16 +4360,17 @@ async function rfqSwapActivityInputs(deps) {
4308
4360
  const records = await deps.repository.getAllRfqSwaps();
4309
4361
  return Promise.all(records.map((record) => activityInputOf(record, deps.indexer)));
4310
4362
  }
4311
- async function activityInputOf(record, indexer) {
4363
+ async function activityInputOf(stored, indexer) {
4364
+ const record = normalizeRfqSwapRecord(stored);
4312
4365
  const txids = /* @__PURE__ */ new Set();
4313
- if (record.fundingArkTxid) txids.add(record.fundingArkTxid);
4314
- if (record.refundArkTxid) txids.add(record.refundArkTxid);
4366
+ if (record.fundingTxid) txids.add(record.fundingTxid);
4367
+ if (record.refundTxid) txids.add(record.refundTxid);
4315
4368
  const handler = rfqCorridorHandlers.getOrThrow(record.kind);
4316
4369
  for (const txid of handler.activityTxids?.(record.profile) ?? []) txids.add(txid);
4317
- for (const txid of record.lockupSpendArkTxids ?? []) txids.add(txid);
4318
- const spendUnknown = isRfqSwapTerminal(record.state) && !record.refundArkTxid && !record.lockupSpendArkTxids?.length;
4319
- if (indexer && (!record.fundingArkTxid || spendUnknown)) {
4320
- 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)) {
4321
4374
  txids.add(txid);
4322
4375
  }
4323
4376
  }
@@ -4342,6 +4395,334 @@ async function lockupTxids(indexer, record, wantFunding) {
4342
4395
  return [];
4343
4396
  }
4344
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
+ }
4345
4726
  // Annotate the CommonJS export names for ESM import in node:
4346
4727
  0 && (module.exports = {
4347
4728
  ARKADE_ASSET,
@@ -4403,6 +4784,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4403
4784
  classifySpend,
4404
4785
  createOffer,
4405
4786
  createRfqSwapRecord,
4787
+ createSwapClient,
4406
4788
  decodeOffer,
4407
4789
  deriveLightningReceive,
4408
4790
  deriveOnchainReceive,
@@ -4417,16 +4799,18 @@ async function lockupTxids(indexer, record, wantFunding) {
4417
4799
  httpTransport,
4418
4800
  isRfqSwapTerminal,
4419
4801
  isRfqTerminal,
4802
+ lightningReceiveContract,
4420
4803
  lightningReceiveRequest,
4804
+ lightningSendContract,
4421
4805
  lightningSendRequest,
4422
- lightningSendVtxoScript,
4423
4806
  lockupContractParams,
4424
4807
  makeCachedFeedFetch,
4425
4808
  newPreimage,
4426
4809
  newRfqId,
4427
4810
  nextOnchainAction,
4811
+ normalizeRfqSwapRecord,
4812
+ offerContract,
4428
4813
  offerTermsFromQuote,
4429
- offerVtxoScript,
4430
4814
  onchainHtlcScript,
4431
4815
  onchainReceiveRequest,
4432
4816
  onchainSendProfile,
@@ -4437,7 +4821,6 @@ async function lockupTxids(indexer, record, wantFunding) {
4437
4821
  pushRefundWithoutReceiver,
4438
4822
  readLockupFate,
4439
4823
  rebuildRfqSwap,
4440
- receiveVtxoScript,
4441
4824
  refundIfUnresolved,
4442
4825
  registerLockupContract,
4443
4826
  relayTransport,