@arkade-os/swap 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,31 +16,25 @@ import {
16
16
  ONCHAIN_RECEIVE_PAIR,
17
17
  ONCHAIN_SECONDS_PER_BLOCK,
18
18
  ONCHAIN_SEND_PAIR,
19
- RFQ_PREIMAGE_TAG,
20
19
  RFQ_TERMINAL_STATES,
21
- RefundNotLocallyPossibleError,
20
+ SOLO_REFUND_HEADROOM_SECONDS,
22
21
  SWAP_LOCKUP_CONTRACT_KIND,
23
22
  SWAP_LOCKUP_CONTRACT_LABEL,
24
23
  SWAP_LOCKUP_CONTRACT_TYPE,
25
24
  SwapRefusal,
26
- adoptSwapDescriptor,
27
25
  arkadeSwapRequest,
28
26
  assertFundable,
29
27
  assertReceivable,
30
28
  awaitOnchainFill,
31
29
  buildHtlcClaim,
32
30
  buildHtlcRefund,
33
- buildPreimageMessage,
34
31
  claimOnchainFill,
35
32
  classifyOnchainHtlc,
36
33
  deriveLightningReceive,
37
34
  deriveOnchainReceive,
38
35
  deriveOnchainSend,
39
- derivePreimage,
40
- deriveSwapSecrets,
41
36
  extractPreimage,
42
37
  httpTransport,
43
- isDeterministicSigner,
44
38
  lightningReceiveRequest,
45
39
  lightningSendRequest,
46
40
  lightningSendVtxoScript,
@@ -51,8 +45,6 @@ import {
51
45
  onchainReceiveRequest,
52
46
  onchainSendRequest,
53
47
  paymentHashOf,
54
- preimageForRfqSecrets,
55
- randomSwapSecrets,
56
48
  receiveVtxoScript,
57
49
  registerLockupContract,
58
50
  relayTransport,
@@ -61,21 +53,16 @@ import {
61
53
  requestOnchainReceive,
62
54
  requestOnchainSend,
63
55
  rfqPair,
64
- rfqSecretsOfRecord,
65
- rfqSecretsToRecord,
66
56
  sealClaimPacket,
67
- senderIdentityForRfqSecrets,
68
- senderIdentityForSwapRecord,
69
- senderPubkeyForRfqSecrets,
70
57
  unilateralClaimDelay,
71
58
  unilateralRefundDelay,
72
59
  unilateralRefundWithoutReceiverDelay,
73
60
  verifyLockupAddress,
74
61
  verifyReceiveInvoice
75
- } from "./chunk-HLMMA3LJ.js";
62
+ } from "./chunk-Q4FAYBXS.js";
76
63
 
77
64
  // src/offer.ts
78
- import { hex } from "@scure/base";
65
+ import { hex as hex2 } from "@scure/base";
79
66
  import { concatBytes } from "@scure/btc-signer/utils.js";
80
67
  import {
81
68
  ArkAddress,
@@ -216,6 +203,9 @@ async function retireSettledOfferContracts(manager, swaps) {
216
203
  }
217
204
 
218
205
  // src/store.ts
206
+ import { hex } from "@scure/base";
207
+ import { sha256 } from "@noble/hashes/sha2.js";
208
+ import { contractPreimage } from "@arkade-os/sdk";
219
209
  var BTC_ASSET_ID = "btc";
220
210
  var byNewest = (a, b) => b.createdAt - a.createdAt;
221
211
  var getAssetSwapsOrThrow = async (repository) => {
@@ -268,6 +258,63 @@ var updateAssetSwapBestEffort = async (repository, id, changes) => {
268
258
  return { swaps, persisted: false };
269
259
  }
270
260
  };
261
+ var swapSecretsToRecord = (secrets) => ({
262
+ signingDescriptor: secrets.descriptor,
263
+ ..."mustPersistPreimage" in secrets && secrets.mustPersistPreimage ? { preimageHex: hex.encode(secrets.preimage) } : {},
264
+ ..."preimageSalt" in secrets && secrets.preimageSalt ? { preimageSaltHex: hex.encode(secrets.preimageSalt) } : {}
265
+ });
266
+ var decodeHex32 = (value, field) => {
267
+ const bytes = hex.decode(value);
268
+ if (bytes.length !== 32) {
269
+ throw new Error(`${field} must be 32 bytes, got ${bytes.length}`);
270
+ }
271
+ return bytes;
272
+ };
273
+ var PreimageNotRecoverableError = class extends Error {
274
+ constructor(reason, message, options) {
275
+ super(message, options);
276
+ this.reason = reason;
277
+ }
278
+ reason;
279
+ name = "PreimageNotRecoverableError";
280
+ };
281
+ var preimageForSwapRecord = async (wallet, record) => {
282
+ if (!record.signingDescriptor) {
283
+ throw new PreimageNotRecoverableError(
284
+ "no-secrets",
285
+ "this swap record carries no signing descriptor"
286
+ );
287
+ }
288
+ let stored;
289
+ let salt;
290
+ try {
291
+ stored = record.preimageHex ? decodeHex32(record.preimageHex, "preimageHex") : void 0;
292
+ salt = record.preimageSaltHex ? decodeHex32(record.preimageSaltHex, "preimageSaltHex") : void 0;
293
+ } catch (cause) {
294
+ throw new PreimageNotRecoverableError(
295
+ "malformed-record",
296
+ `this swap record's secrets projection is unreadable: ${String(cause)}`,
297
+ { cause }
298
+ );
299
+ }
300
+ let preimage;
301
+ try {
302
+ preimage = await contractPreimage(wallet, record.signingDescriptor, { stored, salt });
303
+ } catch (cause) {
304
+ throw new PreimageNotRecoverableError(
305
+ "not-derivable",
306
+ `this wallet cannot produce the preimage for ${record.signingDescriptor}`,
307
+ { cause }
308
+ );
309
+ }
310
+ if (record.paymentHash && hex.encode(sha256(preimage)) !== record.paymentHash.toLowerCase()) {
311
+ throw new PreimageNotRecoverableError(
312
+ "hash-mismatch",
313
+ "the derived preimage does not match this swap's payment hash: wrong wallet, or a tampered salt"
314
+ );
315
+ }
316
+ return preimage;
317
+ };
271
318
 
272
319
  // src/offer.ts
273
320
  var swapPrograms = {
@@ -405,14 +452,14 @@ async function registerOfferContract(wallet, arkServerUrl, network, binding, ser
405
452
  contractManager
406
453
  });
407
454
  const contract = new arkade.ArkadeContract(client, program, args, keys);
408
- if (hex.encode(contract.pkScript) !== hex.encode(expectedPkScript)) {
455
+ if (hex2.encode(contract.pkScript) !== hex2.encode(expectedPkScript)) {
409
456
  throw new Error("derived covenant does not match the offer's swapPkScript");
410
457
  }
411
458
  await contract.register({
412
459
  label: OFFER_CONTRACT_LABEL,
413
460
  metadata: { genericallySpendable: false, kind: OFFER_CONTRACT_KIND }
414
461
  });
415
- await promoteOfferContract(contractManager, hex.encode(expectedPkScript));
462
+ await promoteOfferContract(contractManager, hex2.encode(expectedPkScript));
416
463
  }
417
464
  async function createOffer(wallet, arkServerUrl, params) {
418
465
  if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
@@ -423,9 +470,9 @@ async function createOffer(wallet, arkServerUrl, params) {
423
470
  wallet.getAddress(),
424
471
  wallet.identity.xOnlyPublicKey()
425
472
  ]);
426
- const serverPubKey = hex.decode(toXOnlySignerHex(info.signerPubkey));
473
+ const serverPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
427
474
  const network = getNetwork(info.network);
428
- const emuKey = hex.decode(
475
+ const emuKey = hex2.decode(
429
476
  toXOnlySignerHex(resolveEmulatorPubkey(network, params.emulatorPubkey))
430
477
  );
431
478
  const binding = {
@@ -448,7 +495,7 @@ async function createOffer(wallet, arkServerUrl, params) {
448
495
  );
449
496
  const payload = encodeOffer(offer);
450
497
  return {
451
- offerHex: hex.encode(payload),
498
+ offerHex: hex2.encode(payload),
452
499
  extension: { type: OFFER_PACKET_TYPE, payload },
453
500
  // VtxoScript.address owns address construction; assembling an ArkAddress
454
501
  // from tweakedPublicKey here would silently miss any future step it gains
@@ -458,7 +505,7 @@ async function createOffer(wallet, arkServerUrl, params) {
458
505
  }
459
506
  async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
460
507
  const { repository, fundingTxid, swapAddress } = opts;
461
- const offer = decodeOffer(hex.decode(offerHex));
508
+ const offer = decodeOffer(hex2.decode(offerHex));
462
509
  const contractManager = await wallet.getContractManager();
463
510
  const client = await arkade.Arkade.connect({
464
511
  arkade: new RestArkProvider(arkServerUrl),
@@ -475,7 +522,7 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
475
522
  const serverKey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
476
523
  const { program, args, keys } = swapProgramBinding(offer, serverKey);
477
524
  const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
478
- if (hex.encode(rebuilt.pkScript) !== hex.encode(offer.swapPkScript)) {
525
+ if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
479
526
  throw new Error(
480
527
  "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"
481
528
  );
@@ -508,7 +555,7 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
508
555
  spentTxid: txid
509
556
  });
510
557
  if (persisted) {
511
- await retireOfferContract(contractManager, swaps, hex.encode(offer.swapPkScript));
558
+ await retireOfferContract(contractManager, swaps, hex2.encode(offer.swapPkScript));
512
559
  }
513
560
  }
514
561
  return txid;
@@ -646,7 +693,7 @@ var validatePlan = (plan, giveBalance, dust) => {
646
693
  // src/repository.ts
647
694
  var marketsCacheKey = (network, registry) => `arkade-intents-markets-${network}-${registry}`;
648
695
  var InMemoryAssetSwapRepository = class {
649
- version = 1;
696
+ version = 2;
650
697
  swaps = /* @__PURE__ */ new Map();
651
698
  scanned = /* @__PURE__ */ new Set();
652
699
  markets = /* @__PURE__ */ new Map();
@@ -708,7 +755,7 @@ var IndexedDbAssetSwapRepository = class {
708
755
  this.dbName = dbName;
709
756
  }
710
757
  dbName;
711
- version = 1;
758
+ version = 2;
712
759
  // the promise, not the resolved database: openDatabase bumps a refcount on
713
760
  // every call including cache hits, while dispose closes once, so two
714
761
  // concurrent first calls would strand the refcount above zero and leak the
@@ -779,7 +826,7 @@ var IndexedDbAssetSwapRepository = class {
779
826
  };
780
827
 
781
828
  // src/restore.ts
782
- import { base64, hex as hex2 } from "@scure/base";
829
+ import { base64, hex as hex3 } from "@scure/base";
783
830
  import {
784
831
  Extension,
785
832
  Transaction,
@@ -815,7 +862,7 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
815
862
  let leaves;
816
863
  try {
817
864
  const script = offerVtxoScript(offer, serverPubkey);
818
- if (hex2.encode(script.pkScript) !== hex2.encode(offer.swapPkScript)) return "indeterminate";
865
+ if (hex3.encode(script.pkScript) !== hex3.encode(offer.swapPkScript)) return "indeterminate";
819
866
  leaves = {
820
867
  cancel: script.functionByName("cancel")?.leafScript,
821
868
  fulfill: script.functionByName("fulfill")?.leafScript
@@ -826,11 +873,11 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
826
873
  for (let i = 0; i < spendTx.inputsLength; i++) {
827
874
  const input = spendTx.getInput(i);
828
875
  if (!input.txid || input.index !== deposit.vout) continue;
829
- if (hex2.encode(input.txid) !== deposit.txid) continue;
876
+ if (hex3.encode(input.txid) !== deposit.txid) continue;
830
877
  for (const leaf of input.tapLeafScript ?? []) {
831
- const spent = hex2.encode(scriptFromTapLeafScript(leaf));
832
- if (leaves.cancel && spent === hex2.encode(leaves.cancel)) return "cancelled";
833
- if (leaves.fulfill && spent === hex2.encode(leaves.fulfill)) return "fulfilled";
878
+ const spent = hex3.encode(scriptFromTapLeafScript(leaf));
879
+ if (leaves.cancel && spent === hex3.encode(leaves.cancel)) return "cancelled";
880
+ if (leaves.fulfill && spent === hex3.encode(leaves.fulfill)) return "fulfilled";
834
881
  }
835
882
  }
836
883
  return "indeterminate";
@@ -865,13 +912,13 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
865
912
  found.push({
866
913
  fundingTx,
867
914
  offer: decodeOffer(payload),
868
- offerHex: hex2.encode(payload)
915
+ offerHex: hex3.encode(payload)
869
916
  });
870
917
  } catch {
871
918
  }
872
919
  }
873
920
  if (found.length === 0) return { restored: [], scannedTxids: fetchedTxids };
874
- const scripts = [...new Set(found.map((f) => hex2.encode(f.offer.swapPkScript)))];
921
+ const scripts = [...new Set(found.map((f) => hex3.encode(f.offer.swapPkScript)))];
875
922
  const { vtxos } = await indexer.getVtxos({ scripts });
876
923
  const vtxoByScriptAndTxid = new Map(vtxos.map((v) => [`${v.script}:${v.txid}`, v]));
877
924
  const txByAnyId = /* @__PURE__ */ new Map();
@@ -883,7 +930,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
883
930
  const spendTxids = /* @__PURE__ */ new Set();
884
931
  for (const { fundingTx, offer } of found) {
885
932
  const vtxo = vtxoByScriptAndTxid.get(
886
- `${hex2.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
933
+ `${hex3.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
887
934
  );
888
935
  if (vtxo?.virtualStatus.state !== "spent") continue;
889
936
  for (const txid of spendTxidsOf(vtxo)) spendTxids.add(txid);
@@ -892,7 +939,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
892
939
  const restored = [];
893
940
  const unresolved = /* @__PURE__ */ new Set();
894
941
  for (const { fundingTx, offer, offerHex } of found) {
895
- const swapPkScript = hex2.encode(offer.swapPkScript);
942
+ const swapPkScript = hex3.encode(offer.swapPkScript);
896
943
  const vtxo = vtxoByScriptAndTxid.get(`${swapPkScript}:${fundingTx.redeemTxid}`);
897
944
  if (!vtxo) {
898
945
  unresolved.add(fundingTx.redeemTxid);
@@ -949,7 +996,7 @@ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
949
996
  }
950
997
 
951
998
  // src/watch.ts
952
- import { base64 as base642, hex as hex3 } from "@scure/base";
999
+ import { base64 as base642, hex as hex4 } from "@scure/base";
953
1000
  import {
954
1001
  ArkAddress as ArkAddress2,
955
1002
  RestIndexerProvider as RestIndexerProvider3,
@@ -988,7 +1035,7 @@ async function watchOfferSwaps({
988
1035
  if (candidates.length === 0) return "indeterminate";
989
1036
  const { txs } = await indexer.getVirtualTxs(candidates);
990
1037
  return classifyDepositSpend(
991
- decodeOffer(hex3.decode(swap.offerHex)),
1038
+ decodeOffer(hex4.decode(swap.offerHex)),
992
1039
  serverPubkey,
993
1040
  txs.map((psbt) => Transaction2.fromPSBT(base642.decode(psbt))),
994
1041
  { txid: vtxo.txid, vout: vtxo.vout }
@@ -1032,9 +1079,9 @@ async function watchOfferSwaps({
1032
1079
  }
1033
1080
 
1034
1081
  // src/claim.ts
1035
- import { hex as hex5 } from "@scure/base";
1082
+ import { hex as hex6 } from "@scure/base";
1036
1083
  import { ripemd160 } from "@noble/hashes/legacy.js";
1037
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
1084
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1038
1085
  import {
1039
1086
  CSVMultisigTapscript as CSVMultisigTapscript2,
1040
1087
  claimWithPreimageIdentity,
@@ -1042,8 +1089,8 @@ import {
1042
1089
  } from "@arkade-os/sdk";
1043
1090
 
1044
1091
  // src/refund.ts
1045
- import { base64 as base643, hex as hex4 } from "@scure/base";
1046
- import { sha256 } from "@noble/hashes/sha2.js";
1092
+ import { base64 as base643, hex as hex5 } from "@scure/base";
1093
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
1047
1094
  import {
1048
1095
  CSVMultisigTapscript,
1049
1096
  ConditionWitness,
@@ -1099,7 +1146,7 @@ var LockupNeedsRecoveryError = class extends Error {
1099
1146
  }
1100
1147
  };
1101
1148
  async function findLockupVtxos(indexer, swapPkScript) {
1102
- const scripts = [hex4.encode(swapPkScript)];
1149
+ const scripts = [hex5.encode(swapPkScript)];
1103
1150
  const [spendable, recoverable] = await Promise.all([
1104
1151
  indexer.getVtxos({ scripts, spendableOnly: true }),
1105
1152
  indexer.getVtxos({ scripts, recoverableOnly: true })
@@ -1124,13 +1171,13 @@ async function findLockupVtxos(indexer, swapPkScript) {
1124
1171
  }
1125
1172
  return out;
1126
1173
  }
1127
- var hashesTo = (candidate, paymentHash) => hex4.encode(sha256(candidate)) === paymentHash;
1174
+ var hashesTo = (candidate, paymentHash) => hex5.encode(sha2562(candidate)) === paymentHash;
1128
1175
  var candidateWitnessItems = (tx, inputIndex) => [
1129
1176
  ...getArkPsbtFields(tx, inputIndex, ConditionWitness).flat(),
1130
1177
  ...tx.getInput(inputIndex).finalScriptWitness ?? []
1131
1178
  ];
1132
1179
  async function readLockupFate(indexer, input) {
1133
- const { vtxos } = await indexer.getVtxos({ scripts: [hex4.encode(input.swapPkScript)] });
1180
+ const { vtxos } = await indexer.getVtxos({ scripts: [hex5.encode(input.swapPkScript)] });
1134
1181
  const all = vtxos ?? [];
1135
1182
  if (all.length === 0) return { fate: "unknown" };
1136
1183
  const spentBy = /* @__PURE__ */ new Set();
@@ -1153,7 +1200,7 @@ async function readLockupFate(indexer, input) {
1153
1200
  for (let i = 0; i < tx.inputsLength; i++) {
1154
1201
  const spent = tx.getInput(i);
1155
1202
  if (!spent.txid) continue;
1156
- const txid = hex4.encode(spent.txid);
1203
+ const txid = hex5.encode(spent.txid);
1157
1204
  if (!all.some((vtxo) => vtxo.txid === txid && vtxo.vout === spent.index)) continue;
1158
1205
  for (const candidate of candidateWitnessItems(tx, i)) {
1159
1206
  if (hashesTo(candidate, input.paymentHash)) {
@@ -1182,7 +1229,7 @@ async function pushRefundWithoutReceiver(ark, input) {
1182
1229
  const info = await ark.getInfo();
1183
1230
  let serverUnrollScript;
1184
1231
  try {
1185
- serverUnrollScript = CSVMultisigTapscript.decode(hex4.decode(info.checkpointTapscript));
1232
+ serverUnrollScript = CSVMultisigTapscript.decode(hex5.decode(info.checkpointTapscript));
1186
1233
  } catch {
1187
1234
  throw new Error("invalid checkpointTapscript from the Arkade server");
1188
1235
  }
@@ -1293,13 +1340,13 @@ async function pushClaim(ark, input) {
1293
1340
  }
1294
1341
  }
1295
1342
  const committed = input.script.options.preimageHash;
1296
- if (hex5.encode(ripemd160(sha2562(input.preimage))) !== hex5.encode(committed)) {
1343
+ if (hex6.encode(ripemd160(sha2563(input.preimage))) !== hex6.encode(committed)) {
1297
1344
  throw new Error("preimage does not match the covenant's payment hash");
1298
1345
  }
1299
1346
  const info = await ark.getInfo();
1300
1347
  let serverUnrollScript;
1301
1348
  try {
1302
- serverUnrollScript = CSVMultisigTapscript2.decode(hex5.decode(info.checkpointTapscript));
1349
+ serverUnrollScript = CSVMultisigTapscript2.decode(hex6.decode(info.checkpointTapscript));
1303
1350
  } catch {
1304
1351
  throw new Error("invalid checkpointTapscript from the Arkade server");
1305
1352
  }
@@ -1352,8 +1399,50 @@ async function claimReceiveLockup(indexer, ark, input) {
1352
1399
  });
1353
1400
  }
1354
1401
 
1402
+ // src/refundBlocked.ts
1403
+ import {
1404
+ ForeignDescriptorError,
1405
+ WalletCannotSignError,
1406
+ contractSigner
1407
+ } from "@arkade-os/sdk";
1408
+ var RefundNotLocallyPossibleError = class extends Error {
1409
+ constructor(reason, message, options) {
1410
+ super(message, options);
1411
+ this.reason = reason;
1412
+ }
1413
+ reason;
1414
+ name = "RefundNotLocallyPossibleError";
1415
+ };
1416
+ async function senderIdentityForSwapRecord(wallet, record) {
1417
+ if (!record.signingDescriptor) {
1418
+ throw new RefundNotLocallyPossibleError(
1419
+ "no-secrets",
1420
+ "this swap record carries no signing descriptor"
1421
+ );
1422
+ }
1423
+ try {
1424
+ return await contractSigner(wallet, record.signingDescriptor);
1425
+ } catch (cause) {
1426
+ if (cause instanceof WalletCannotSignError) {
1427
+ throw new RefundNotLocallyPossibleError(
1428
+ "unsignable-wallet",
1429
+ `this wallet holds ${record.signingDescriptor} but cannot sign with it; attach its signer`,
1430
+ { cause }
1431
+ );
1432
+ }
1433
+ if (cause instanceof ForeignDescriptorError) {
1434
+ throw new RefundNotLocallyPossibleError(
1435
+ "foreign-descriptor",
1436
+ `this wallet cannot derive ${record.signingDescriptor}; the swap was created on another wallet`,
1437
+ { cause }
1438
+ );
1439
+ }
1440
+ throw cause;
1441
+ }
1442
+ }
1443
+
1355
1444
  // src/swapManager.ts
1356
- import { hex as hex6 } from "@scure/base";
1445
+ import { hex as hex7 } from "@scure/base";
1357
1446
  var RFQ_SWAP_TERMINAL_STATES = ["settled", "refunded", "failed"];
1358
1447
  var isRfqSwapTerminal = (state) => RFQ_SWAP_TERMINAL_STATES.includes(state);
1359
1448
  function nextOnchainAction(input) {
@@ -1621,7 +1710,7 @@ var RfqSwapManager = class {
1621
1710
  // ── internals ────────────────────────────────────────────────────────────
1622
1711
  track(swap) {
1623
1712
  this.monitored.set(swap.rfqId, swap);
1624
- this.byLockupScript.set(hex6.encode(swap.lockupPkScript), swap);
1713
+ this.byLockupScript.set(hex7.encode(swap.lockupPkScript), swap);
1625
1714
  }
1626
1715
  /** Drops the swap from BOTH indexes. The event index is the one that stops
1627
1716
  * a late event finding a swap that is gone; `pollSwap`'s own
@@ -1630,7 +1719,7 @@ var RfqSwapManager = class {
1630
1719
  * them from silently re-driving a cancelled swap. */
1631
1720
  untrack(rfqId) {
1632
1721
  const swap = this.monitored.get(rfqId);
1633
- if (swap) this.byLockupScript.delete(hex6.encode(swap.lockupPkScript));
1722
+ if (swap) this.byLockupScript.delete(hex7.encode(swap.lockupPkScript));
1634
1723
  this.monitored.delete(rfqId);
1635
1724
  this.refundRefused.delete(rfqId);
1636
1725
  this.lastClaimError.delete(rfqId);
@@ -1692,7 +1781,7 @@ var RfqSwapManager = class {
1692
1781
  if (!lockup) {
1693
1782
  try {
1694
1783
  const [existing] = await contracts.getContracts({
1695
- script: hex6.encode(swap.lockupPkScript)
1784
+ script: hex7.encode(swap.lockupPkScript)
1696
1785
  });
1697
1786
  if (existing) {
1698
1787
  this.registered.set(swap.rfqId, true);
@@ -1711,13 +1800,13 @@ var RfqSwapManager = class {
1711
1800
  );
1712
1801
  return;
1713
1802
  }
1714
- const script = hex6.encode(lockup.script.pkScript);
1715
- if (script !== hex6.encode(swap.lockupPkScript)) {
1803
+ const script = hex7.encode(lockup.script.pkScript);
1804
+ if (script !== hex7.encode(swap.lockupPkScript)) {
1716
1805
  this.registered.set(swap.rfqId, false);
1717
1806
  this.emitFailed(
1718
1807
  swap,
1719
1808
  new Error(
1720
- `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex6.encode(swap.lockupPkScript)}`
1809
+ `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex7.encode(swap.lockupPkScript)}`
1721
1810
  )
1722
1811
  );
1723
1812
  return;
@@ -1736,7 +1825,7 @@ var RfqSwapManager = class {
1736
1825
  * script for its whole life. Best-effort — the swap is over either way. */
1737
1826
  retireContract(swap) {
1738
1827
  if (!this.deps.contracts || !this.registered.get(swap.rfqId)) return;
1739
- void this.deps.contracts.setContractWatchState(hex6.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
1828
+ void this.deps.contracts.setContractWatchState(hex7.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
1740
1829
  }
1741
1830
  arm() {
1742
1831
  if (!this.running) return;
@@ -2142,20 +2231,20 @@ export {
2142
2231
  ONCHAIN_RECEIVE_PAIR,
2143
2232
  ONCHAIN_SECONDS_PER_BLOCK,
2144
2233
  ONCHAIN_SEND_PAIR,
2234
+ PreimageNotRecoverableError,
2145
2235
  QUOTE_OPTIONS,
2146
2236
  REFUND_MTP_LAG_SECONDS,
2147
- RFQ_PREIMAGE_TAG,
2148
2237
  RFQ_RESOLVED_STATES,
2149
2238
  RFQ_SWAP_TERMINAL_STATES,
2150
2239
  RFQ_TERMINAL_STATES,
2151
2240
  RefundNotLocallyPossibleError,
2152
2241
  RfqSwapManager,
2242
+ SOLO_REFUND_HEADROOM_SECONDS,
2153
2243
  SWAP_LOCKUP_CONTRACT_KIND,
2154
2244
  SWAP_LOCKUP_CONTRACT_LABEL,
2155
2245
  SWAP_LOCKUP_CONTRACT_TYPE,
2156
2246
  SwapRefusal,
2157
2247
  addAssetSwap,
2158
- adoptSwapDescriptor,
2159
2248
  arkadeSwapRequest,
2160
2249
  assertFundable,
2161
2250
  assertReceivable,
@@ -2164,7 +2253,6 @@ export {
2164
2253
  awaitRfqResolution,
2165
2254
  buildHtlcClaim,
2166
2255
  buildHtlcRefund,
2167
- buildPreimageMessage,
2168
2256
  cancelOffer,
2169
2257
  claimOnchainFill,
2170
2258
  claimReceiveLockup,
@@ -2176,8 +2264,6 @@ export {
2176
2264
  deriveLightningReceive,
2177
2265
  deriveOnchainReceive,
2178
2266
  deriveOnchainSend,
2179
- derivePreimage,
2180
- deriveSwapSecrets,
2181
2267
  discoverMarkets,
2182
2268
  encodeOffer,
2183
2269
  extractPreimage,
@@ -2186,7 +2272,6 @@ export {
2186
2272
  getAssetSwaps,
2187
2273
  getAssetSwapsOrThrow,
2188
2274
  httpTransport,
2189
- isDeterministicSigner,
2190
2275
  isRfqSwapTerminal,
2191
2276
  isRfqTerminal,
2192
2277
  lightningReceiveRequest,
@@ -2202,10 +2287,9 @@ export {
2202
2287
  onchainReceiveRequest,
2203
2288
  onchainSendRequest,
2204
2289
  paymentHashOf,
2205
- preimageForRfqSecrets,
2290
+ preimageForSwapRecord,
2206
2291
  pushClaim,
2207
2292
  pushRefundWithoutReceiver,
2208
- randomSwapSecrets,
2209
2293
  readLockupFate,
2210
2294
  receiveVtxoScript,
2211
2295
  refundIfUnresolved,
@@ -2218,15 +2302,12 @@ export {
2218
2302
  restoreAssetSwaps,
2219
2303
  retireSettledOfferContracts,
2220
2304
  rfqPair,
2221
- rfqSecretsOfRecord,
2222
- rfqSecretsToRecord,
2223
2305
  sealClaimPacket,
2224
- senderIdentityForRfqSecrets,
2225
2306
  senderIdentityForSwapRecord,
2226
- senderPubkeyForRfqSecrets,
2227
2307
  spendTxidsOf,
2228
2308
  spendUpdate,
2229
2309
  swapPrograms,
2310
+ swapSecretsToRecord,
2230
2311
  unilateralClaimDelay,
2231
2312
  unilateralRefundDelay,
2232
2313
  unilateralRefundWithoutReceiverDelay,
package/dist/nostr.cjs CHANGED
@@ -33,14 +33,15 @@ __export(nostr_exports, {
33
33
  RFQ_AD_KIND: () => RFQ_AD_KIND,
34
34
  RFQ_DIRECTED_KIND: () => RFQ_DIRECTED_KIND,
35
35
  RelayUnavailable: () => RelayUnavailable,
36
+ TransportClosed: () => TransportClosed,
36
37
  nostrRfqTransport: () => nostrRfqTransport
37
38
  });
38
39
  module.exports = __toCommonJS(nostr_exports);
39
40
 
40
41
  // src/rfq.ts
41
- var import_base5 = require("@scure/base");
42
+ var import_base4 = require("@scure/base");
42
43
  var import_legacy2 = require("@noble/hashes/legacy.js");
43
- var import_sdk3 = require("@arkade-os/sdk");
44
+ var import_sdk2 = require("@arkade-os/sdk");
44
45
 
45
46
  // src/onchainHtlc.ts
46
47
  var import_base = require("@scure/base");
@@ -56,23 +57,19 @@ var L1_NETWORKS = {
56
57
  regtest: { ...btc.TEST_NETWORK, bech32: "bcrt" }
57
58
  };
58
59
 
59
- // src/secrets.ts
60
- var import_base2 = require("@scure/base");
61
- var import_sha22 = require("@noble/hashes/sha2.js");
62
- var import_utils = require("@noble/hashes/utils.js");
63
- var import_secp256k1 = require("@noble/curves/secp256k1.js");
64
- var import_sdk = require("@arkade-os/sdk");
60
+ // src/rfq.ts
61
+ var import_sdk3 = require("@arkade-os/sdk");
65
62
 
66
63
  // src/claimPacket.ts
67
- var import_base3 = require("@scure/base");
68
- var import_secp256k12 = require("@noble/curves/secp256k1.js");
64
+ var import_base2 = require("@scure/base");
65
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
69
66
  var import_hkdf = require("@noble/hashes/hkdf.js");
70
- var import_sha23 = require("@noble/hashes/sha2.js");
67
+ var import_sha22 = require("@noble/hashes/sha2.js");
71
68
  var HKDF_INFO = new TextEncoder().encode("covclaimd/preimage/v1");
72
69
 
73
70
  // src/lockupContract.ts
74
- var import_base4 = require("@scure/base");
75
- var import_sdk2 = require("@arkade-os/sdk");
71
+ var import_base3 = require("@scure/base");
72
+ var import_sdk = require("@arkade-os/sdk");
76
73
 
77
74
  // src/rfq.ts
78
75
  var ARKADE_BTC = "arkade:BTC";
@@ -94,6 +91,8 @@ var SwapRefusal = class extends Error {
94
91
  }
95
92
  };
96
93
  var MIN_HEADROOM_SECONDS = 90 * 60;
94
+ var SEQUENCE_GRANULARITY_SECONDS = 512;
95
+ var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
97
96
  var MIN_CLAIM_WINDOW_SECONDS = 30 * 60;
98
97
 
99
98
  // src/nostr.ts
@@ -109,6 +108,12 @@ var RelayUnavailable = class extends Error {
109
108
  this.reasons = reasons;
110
109
  }
111
110
  };
111
+ var TransportClosed = class extends Error {
112
+ constructor() {
113
+ super("transport closed before the solver replied");
114
+ this.name = "TransportClosed";
115
+ }
116
+ };
112
117
  var closeReasons = (raw) => raw.map((entry) => {
113
118
  if (typeof entry === "string") return entry;
114
119
  const { url, reason } = entry ?? {};
@@ -208,9 +213,10 @@ var nostrRfqTransport = (options) => {
208
213
  },
209
214
  async close() {
210
215
  closed = true;
211
- subscription.close();
216
+ for (const waiter of waiters.values()) waiter.reject(new TransportClosed());
212
217
  waiters.clear();
213
218
  if (ownsPool) pool.close(relays);
219
+ else subscription.close();
214
220
  }
215
221
  };
216
222
  };
@@ -219,5 +225,6 @@ var nostrRfqTransport = (options) => {
219
225
  RFQ_AD_KIND,
220
226
  RFQ_DIRECTED_KIND,
221
227
  RelayUnavailable,
228
+ TransportClosed,
222
229
  nostrRfqTransport
223
230
  });
package/dist/nostr.d.cts CHANGED
@@ -1,7 +1,6 @@
1
- import { b as RfqTransport } from './rfq-CCAFoFtR.cjs';
1
+ import { a as RfqTransport } from './rfq-BH2yvo3O.cjs';
2
2
  import { SimplePool } from 'nostr-tools';
3
3
  import '@arkade-os/sdk';
4
- import '@arkade-os/solver-discovery';
5
4
 
6
5
  /**
7
6
  * The Nostr RFQ transport — the PRODUCTION one (docs/rfq-protocol.md § 3.1 in
@@ -71,6 +70,18 @@ declare class RelayUnavailable extends Error {
71
70
  readonly reasons: string[];
72
71
  constructor(reasons: string[]);
73
72
  }
73
+ /**
74
+ * `close()` was called while a negotiation was still waiting on a reply.
75
+ *
76
+ * Distinct from both a timeout and {@link RelayUnavailable}, which describe the
77
+ * wire; this describes a decision on our own side of it. A caller that closed
78
+ * deliberately — a user leaving the screen, a flow abandoning its request — can
79
+ * match on this and stay quiet, rather than reporting a solver failure that
80
+ * never happened.
81
+ */
82
+ declare class TransportClosed extends Error {
83
+ constructor();
84
+ }
74
85
  interface NostrRfqOptions {
75
86
  /** Relay URLs from the solver's card. The rendezvous, not solver endpoints. */
76
87
  relays: string[];
@@ -96,4 +107,4 @@ interface NostrRfqOptions {
96
107
  */
97
108
  declare const nostrRfqTransport: (options: NostrRfqOptions) => RfqTransport;
98
109
 
99
- export { type NostrRfqOptions, RFQ_AD_KIND, RFQ_DIRECTED_KIND, RelayUnavailable, nostrRfqTransport };
110
+ export { type NostrRfqOptions, RFQ_AD_KIND, RFQ_DIRECTED_KIND, RelayUnavailable, TransportClosed, nostrRfqTransport };