@arkade-os/swap 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,11 @@ Node-specific APIs, so it runs in Node, the browser, and React Native alike. Fou
7
7
  ship — in-memory (anywhere, nothing outlives the process), IndexedDB (browser), SQLite and Realm
8
8
  (React Native, on subpath entry points) — see "Storage backends" below.
9
9
 
10
+ The one global the core API requires is `crypto.getRandomValues`. Node and browsers have it;
11
+ React Native does not, so install `react-native-get-random-values` (or `expo-crypto`) and import
12
+ it before this package. `crypto.subtle` is not used. `EventSource` and `WebSocket` are needed only
13
+ by the watch and relay transports, both of which take an injected implementation.
14
+
10
15
  ## Roles
11
16
 
12
17
  Arkade Intents names two participants:
@@ -6,6 +6,7 @@ import * as btc from "@scure/btc-signer";
6
6
  var ONCHAIN_ORDER_MARGIN_SECONDS = 2 * 60 * 60;
7
7
  var ONCHAIN_CLAIM_MARGIN_SECONDS = 90 * 60;
8
8
  var MAX_MIN_CONFIRMATIONS = 6;
9
+ var LOCKTIME_THRESHOLD = 5e8;
9
10
  var ONCHAIN_SECONDS_PER_BLOCK = 600;
10
11
  var ONCHAIN_DUST_SATS = BigInt(330);
11
12
  var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
@@ -25,6 +26,11 @@ function onchainHtlcScript(params, network) {
25
26
  `refundLocktime must be a positive unix timestamp, got ${params.refundLocktime}`
26
27
  );
27
28
  }
29
+ if (params.refundLocktime < LOCKTIME_THRESHOLD) {
30
+ throw new Error(
31
+ `refundLocktime ${params.refundLocktime} is below LOCKTIME_THRESHOLD (${LOCKTIME_THRESHOLD}) and would be interpreted as a block height`
32
+ );
33
+ }
28
34
  if (!Object.hasOwn(L1_NETWORKS, network)) {
29
35
  throw new Error(
30
36
  `unknown L1 network '${String(network)}' \u2014 expected one of ${Object.keys(L1_NETWORKS).join(", ")}`
@@ -211,6 +217,7 @@ async function classifyOnchainHtlc(chain, input) {
211
217
 
212
218
  // src/claimPacket.ts
213
219
  import { base64 } from "@scure/base";
220
+ import { gcm } from "@noble/ciphers/aes.js";
214
221
  import { secp256k1 } from "@noble/curves/secp256k1.js";
215
222
  import { hkdf } from "@noble/hashes/hkdf.js";
216
223
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
@@ -231,20 +238,7 @@ async function sealWithEntropy(input, ephemeralKey, nonce) {
231
238
  const sharedX = secp256k1.getSharedSecret(ephemeralKey, input.covclaimdPubkey, true).subarray(1);
232
239
  const key = hkdf(sha2562, sharedX, ephemeralPub, HKDF_INFO, 32);
233
240
  if (nonce.length !== 12) throw new Error("nonce must be 12 bytes");
234
- const aesKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, [
235
- "encrypt"
236
- ]);
237
- const sealed = new Uint8Array(
238
- await crypto.subtle.encrypt(
239
- {
240
- name: "AES-GCM",
241
- iv: nonce,
242
- additionalData: ephemeralPub
243
- },
244
- aesKey,
245
- input.preimage
246
- )
247
- );
241
+ const sealed = gcm(key, nonce, ephemeralPub).encrypt(input.preimage);
248
242
  const packet = new Uint8Array(33 + 12 + sealed.length);
249
243
  packet.set(ephemeralPub, 0);
250
244
  packet.set(nonce, 33);
@@ -318,19 +312,13 @@ import {
318
312
  RestArkProvider,
319
313
  VHTLC,
320
314
  getNetwork,
321
- resolveEmulatorPubkey
315
+ resolveEmulatorPubkey,
316
+ toXOnly
322
317
  } from "@arkade-os/sdk";
323
318
  import {
324
319
  provisionClaimSecret,
325
320
  provisionRefundKey
326
321
  } from "@arkade-os/sdk";
327
- var xOnly = (key, label) => {
328
- if (key.length === 32) return key;
329
- if (key.length !== 33 || key[0] !== 2 && key[0] !== 3) {
330
- throw new Error(`${label} is not a compressed or x-only public key`);
331
- }
332
- return key.slice(1);
333
- };
334
322
  var solverHex = (value, field) => {
335
323
  try {
336
324
  return hex3.decode(value);
@@ -651,10 +639,8 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
651
639
  const rfqId = params.rfqId ?? newRfqId();
652
640
  const secrets = await provisionRefundKey(wallet);
653
641
  const senderPubkey = secrets.pubkey;
654
- const [info, refundAddress] = await Promise.all([
655
- new RestArkProvider(arkServerUrl).getInfo(),
656
- wallet.getAddress()
657
- ]);
642
+ const refundAddress = secrets.address;
643
+ const info = await new RestArkProvider(arkServerUrl).getInfo();
658
644
  const quote = await transport.requestQuote(
659
645
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
660
646
  );
@@ -675,21 +661,21 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
675
661
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
676
662
  );
677
663
  }
678
- const serverPubkey = xOnly(hex3.decode(info.signerPubkey), "ark signer key");
664
+ const serverPubkey = toXOnly(hex3.decode(info.signerPubkey), "ark signer key");
679
665
  const network = getNetwork(info.network);
680
666
  const treeParams = {
681
- solverPubkey: xOnly(hex3.decode(quote.solver_pubkey), "solver key"),
667
+ solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
682
668
  refundLocktime: quote.refund_locktime,
683
669
  serverPubkey,
684
670
  paymentHash: params.invoice.paymentHash,
685
671
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
686
- emulatorPubkey: xOnly(
672
+ emulatorPubkey: toXOnly(
687
673
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
688
674
  "emulator signer key"
689
675
  ),
690
676
  senderPubkey,
691
677
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
692
- refundPkScript: ArkAddress2.decode(refundAddress).pkScript
678
+ refundPkScript: secrets.pkScript
693
679
  };
694
680
  const script = lightningSendVtxoScript(treeParams);
695
681
  const address = script.address(network.hrp, serverPubkey).encode();
@@ -778,7 +764,7 @@ function deriveOnchainSend(input) {
778
764
  throw new Error("onchain-send quote is missing a binding field");
779
765
  }
780
766
  const script = lightningSendVtxoScript({
781
- solverPubkey: xOnly(hex3.decode(quote.solver_pubkey), "solver key"),
767
+ solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
782
768
  refundLocktime,
783
769
  serverPubkey: input.serverPubkey,
784
770
  paymentHash: input.paymentHash,
@@ -793,7 +779,7 @@ function deriveOnchainSend(input) {
793
779
  const htlcParams = {
794
780
  paymentHash: input.paymentHash,
795
781
  claimKey: input.payoutPubkey,
796
- refundKey: xOnly(hex3.decode(htlcPubkey), "solver L1 htlc key"),
782
+ refundKey: toXOnly(hex3.decode(htlcPubkey), "solver L1 htlc key"),
797
783
  refundLocktime: htlcLocktime
798
784
  };
799
785
  const htlc = onchainHtlcScript(htlcParams, input.l1Network);
@@ -840,8 +826,8 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
840
826
  quote,
841
827
  paymentHash,
842
828
  payoutPubkey: params.payoutPubkey,
843
- serverPubkey: xOnly(hex3.decode(info.signerPubkey), "ark signer key"),
844
- emulatorPubkey: xOnly(
829
+ serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
830
+ emulatorPubkey: toXOnly(
845
831
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
846
832
  "emulator signer key"
847
833
  ),
@@ -984,7 +970,7 @@ function deriveLightningReceive(input) {
984
970
  throw new Error("lightning-receive quote is missing a binding field");
985
971
  }
986
972
  const treeParams = {
987
- solverPubkey: xOnly(hex3.decode(quote.solver_pubkey), "solver key"),
973
+ solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
988
974
  refundLocktime,
989
975
  serverPubkey: input.serverPubkey,
990
976
  paymentHash: input.paymentHash,
@@ -1036,8 +1022,8 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1036
1022
  paymentHash,
1037
1023
  payoutPubkey,
1038
1024
  payoutAddress,
1039
- serverPubkey: xOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1040
- emulatorPubkey: xOnly(
1025
+ serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1026
+ emulatorPubkey: toXOnly(
1041
1027
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1042
1028
  "emulator signer key"
1043
1029
  ),
@@ -1086,7 +1072,7 @@ function deriveOnchainReceive(input) {
1086
1072
  throw new Error("onchain-receive quote is missing a binding field");
1087
1073
  }
1088
1074
  const script = receiveVtxoScript({
1089
- solverPubkey: xOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1075
+ solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1090
1076
  refundLocktime,
1091
1077
  serverPubkey: input.serverPubkey,
1092
1078
  paymentHash: input.paymentHash,
@@ -1101,7 +1087,7 @@ function deriveOnchainReceive(input) {
1101
1087
  const htlc = onchainHtlcScript(
1102
1088
  {
1103
1089
  paymentHash: input.paymentHash,
1104
- claimKey: xOnly(hex3.decode(claimPubkey), "solver L1 claim key"),
1090
+ claimKey: toXOnly(hex3.decode(claimPubkey), "solver L1 claim key"),
1105
1091
  refundKey: input.refundPubkey,
1106
1092
  refundLocktime: htlcLocktime
1107
1093
  },
@@ -1157,8 +1143,8 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1157
1143
  payoutPubkey,
1158
1144
  payoutAddress,
1159
1145
  refundPubkey: params.refundPubkey,
1160
- serverPubkey: xOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1161
- emulatorPubkey: xOnly(
1146
+ serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1147
+ emulatorPubkey: toXOnly(
1162
1148
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1163
1149
  "emulator signer key"
1164
1150
  ),
@@ -1199,6 +1185,7 @@ export {
1199
1185
  ONCHAIN_ORDER_MARGIN_SECONDS,
1200
1186
  ONCHAIN_CLAIM_MARGIN_SECONDS,
1201
1187
  MAX_MIN_CONFIRMATIONS,
1188
+ LOCKTIME_THRESHOLD,
1202
1189
  ONCHAIN_SECONDS_PER_BLOCK,
1203
1190
  ONCHAIN_DUST_SATS,
1204
1191
  newPreimage,
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  LIGHTNING_BTC: () => LIGHTNING_BTC,
40
40
  LIGHTNING_RECEIVE_PAIR: () => LIGHTNING_RECEIVE_PAIR,
41
41
  LIGHTNING_SEND_PAIR: () => LIGHTNING_SEND_PAIR,
42
+ LOCKTIME_THRESHOLD: () => LOCKTIME_THRESHOLD,
42
43
  LockupAmountMismatchError: () => LockupAmountMismatchError,
43
44
  LockupContractMissing: () => LockupContractMissing,
44
45
  LockupNeedsRecoveryError: () => LockupNeedsRecoveryError,
@@ -608,8 +609,8 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
608
609
  // script and the payout script comes from wallet.getAddress(), so the
609
610
  // client's network (which only shapes address derivation) is unused here
610
611
  });
611
- const serverKey = swapAddress ? import_sdk2.ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
612
- const { program, args, keys } = swapProgramBinding(offer, serverKey);
612
+ const operatorPubkey = swapAddress ? import_sdk2.ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
613
+ const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
613
614
  const rebuilt = new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
614
615
  if (import_base2.hex.encode(rebuilt.pkScript) !== import_base2.hex.encode(offer.swapPkScript)) {
615
616
  throw new Error(
@@ -1050,6 +1051,7 @@ var btc = __toESM(require("@scure/btc-signer"), 1);
1050
1051
  var ONCHAIN_ORDER_MARGIN_SECONDS = 2 * 60 * 60;
1051
1052
  var ONCHAIN_CLAIM_MARGIN_SECONDS = 90 * 60;
1052
1053
  var MAX_MIN_CONFIRMATIONS = 6;
1054
+ var LOCKTIME_THRESHOLD = 5e8;
1053
1055
  var ONCHAIN_SECONDS_PER_BLOCK = 600;
1054
1056
  var ONCHAIN_DUST_SATS = BigInt(330);
1055
1057
  var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
@@ -1069,6 +1071,11 @@ function onchainHtlcScript(params, network) {
1069
1071
  `refundLocktime must be a positive unix timestamp, got ${params.refundLocktime}`
1070
1072
  );
1071
1073
  }
1074
+ if (params.refundLocktime < LOCKTIME_THRESHOLD) {
1075
+ throw new Error(
1076
+ `refundLocktime ${params.refundLocktime} is below LOCKTIME_THRESHOLD (${LOCKTIME_THRESHOLD}) and would be interpreted as a block height`
1077
+ );
1078
+ }
1072
1079
  if (!Object.hasOwn(L1_NETWORKS, network)) {
1073
1080
  throw new Error(
1074
1081
  `unknown L1 network '${String(network)}' \u2014 expected one of ${Object.keys(L1_NETWORKS).join(", ")}`
@@ -1723,6 +1730,7 @@ var import_sdk10 = require("@arkade-os/sdk");
1723
1730
 
1724
1731
  // src/claimPacket.ts
1725
1732
  var import_base8 = require("@scure/base");
1733
+ var import_aes = require("@noble/ciphers/aes.js");
1726
1734
  var import_secp256k1 = require("@noble/curves/secp256k1.js");
1727
1735
  var import_hkdf = require("@noble/hashes/hkdf.js");
1728
1736
  var import_sha23 = require("@noble/hashes/sha2.js");
@@ -1743,20 +1751,7 @@ async function sealWithEntropy(input, ephemeralKey, nonce) {
1743
1751
  const sharedX = import_secp256k1.secp256k1.getSharedSecret(ephemeralKey, input.covclaimdPubkey, true).subarray(1);
1744
1752
  const key = (0, import_hkdf.hkdf)(import_sha23.sha256, sharedX, ephemeralPub, HKDF_INFO, 32);
1745
1753
  if (nonce.length !== 12) throw new Error("nonce must be 12 bytes");
1746
- const aesKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, [
1747
- "encrypt"
1748
- ]);
1749
- const sealed = new Uint8Array(
1750
- await crypto.subtle.encrypt(
1751
- {
1752
- name: "AES-GCM",
1753
- iv: nonce,
1754
- additionalData: ephemeralPub
1755
- },
1756
- aesKey,
1757
- input.preimage
1758
- )
1759
- );
1754
+ const sealed = (0, import_aes.gcm)(key, nonce, ephemeralPub).encrypt(input.preimage);
1760
1755
  const packet = new Uint8Array(33 + 12 + sealed.length);
1761
1756
  packet.set(ephemeralPub, 0);
1762
1757
  packet.set(nonce, 33);
@@ -1820,13 +1815,6 @@ async function lockupContractParams(contracts, lockupAddress) {
1820
1815
  }
1821
1816
 
1822
1817
  // src/rfq.ts
1823
- var xOnly = (key, label) => {
1824
- if (key.length === 32) return key;
1825
- if (key.length !== 33 || key[0] !== 2 && key[0] !== 3) {
1826
- throw new Error(`${label} is not a compressed or x-only public key`);
1827
- }
1828
- return key.slice(1);
1829
- };
1830
1818
  var solverHex = (value, field) => {
1831
1819
  try {
1832
1820
  return import_base10.hex.decode(value);
@@ -2147,10 +2135,8 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2147
2135
  const rfqId = params.rfqId ?? newRfqId();
2148
2136
  const secrets = await (0, import_sdk10.provisionRefundKey)(wallet);
2149
2137
  const senderPubkey = secrets.pubkey;
2150
- const [info, refundAddress] = await Promise.all([
2151
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2152
- wallet.getAddress()
2153
- ]);
2138
+ const refundAddress = secrets.address;
2139
+ const info = await new import_sdk9.RestArkProvider(arkServerUrl).getInfo();
2154
2140
  const quote = await transport.requestQuote(
2155
2141
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
2156
2142
  );
@@ -2171,21 +2157,21 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2171
2157
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
2172
2158
  );
2173
2159
  }
2174
- const serverPubkey = xOnly(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2160
+ const serverPubkey = (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2175
2161
  const network = (0, import_sdk9.getNetwork)(info.network);
2176
2162
  const treeParams = {
2177
- solverPubkey: xOnly(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2163
+ solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2178
2164
  refundLocktime: quote.refund_locktime,
2179
2165
  serverPubkey,
2180
2166
  paymentHash: params.invoice.paymentHash,
2181
2167
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
2182
- emulatorPubkey: xOnly(
2168
+ emulatorPubkey: (0, import_sdk9.toXOnly)(
2183
2169
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2184
2170
  "emulator signer key"
2185
2171
  ),
2186
2172
  senderPubkey,
2187
2173
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
2188
- refundPkScript: import_sdk9.ArkAddress.decode(refundAddress).pkScript
2174
+ refundPkScript: secrets.pkScript
2189
2175
  };
2190
2176
  const script = lightningSendVtxoScript(treeParams);
2191
2177
  const address = script.address(network.hrp, serverPubkey).encode();
@@ -2274,7 +2260,7 @@ function deriveOnchainSend(input) {
2274
2260
  throw new Error("onchain-send quote is missing a binding field");
2275
2261
  }
2276
2262
  const script = lightningSendVtxoScript({
2277
- solverPubkey: xOnly(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2263
+ solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2278
2264
  refundLocktime,
2279
2265
  serverPubkey: input.serverPubkey,
2280
2266
  paymentHash: input.paymentHash,
@@ -2289,7 +2275,7 @@ function deriveOnchainSend(input) {
2289
2275
  const htlcParams = {
2290
2276
  paymentHash: input.paymentHash,
2291
2277
  claimKey: input.payoutPubkey,
2292
- refundKey: xOnly(import_base10.hex.decode(htlcPubkey), "solver L1 htlc key"),
2278
+ refundKey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(htlcPubkey), "solver L1 htlc key"),
2293
2279
  refundLocktime: htlcLocktime
2294
2280
  };
2295
2281
  const htlc = onchainHtlcScript(htlcParams, input.l1Network);
@@ -2336,8 +2322,8 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2336
2322
  quote,
2337
2323
  paymentHash,
2338
2324
  payoutPubkey: params.payoutPubkey,
2339
- serverPubkey: xOnly(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2340
- emulatorPubkey: xOnly(
2325
+ serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2326
+ emulatorPubkey: (0, import_sdk9.toXOnly)(
2341
2327
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2342
2328
  "emulator signer key"
2343
2329
  ),
@@ -2480,7 +2466,7 @@ function deriveLightningReceive(input) {
2480
2466
  throw new Error("lightning-receive quote is missing a binding field");
2481
2467
  }
2482
2468
  const treeParams = {
2483
- solverPubkey: xOnly(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2469
+ solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2484
2470
  refundLocktime,
2485
2471
  serverPubkey: input.serverPubkey,
2486
2472
  paymentHash: input.paymentHash,
@@ -2532,8 +2518,8 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2532
2518
  paymentHash,
2533
2519
  payoutPubkey,
2534
2520
  payoutAddress,
2535
- serverPubkey: xOnly(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2536
- emulatorPubkey: xOnly(
2521
+ serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2522
+ emulatorPubkey: (0, import_sdk9.toXOnly)(
2537
2523
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2538
2524
  "emulator signer key"
2539
2525
  ),
@@ -2582,7 +2568,7 @@ function deriveOnchainReceive(input) {
2582
2568
  throw new Error("onchain-receive quote is missing a binding field");
2583
2569
  }
2584
2570
  const script = receiveVtxoScript({
2585
- solverPubkey: xOnly(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2571
+ solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2586
2572
  refundLocktime,
2587
2573
  serverPubkey: input.serverPubkey,
2588
2574
  paymentHash: input.paymentHash,
@@ -2597,7 +2583,7 @@ function deriveOnchainReceive(input) {
2597
2583
  const htlc = onchainHtlcScript(
2598
2584
  {
2599
2585
  paymentHash: input.paymentHash,
2600
- claimKey: xOnly(import_base10.hex.decode(claimPubkey), "solver L1 claim key"),
2586
+ claimKey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(claimPubkey), "solver L1 claim key"),
2601
2587
  refundKey: input.refundPubkey,
2602
2588
  refundLocktime: htlcLocktime
2603
2589
  },
@@ -2653,8 +2639,8 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2653
2639
  payoutPubkey,
2654
2640
  payoutAddress,
2655
2641
  refundPubkey: params.refundPubkey,
2656
- serverPubkey: xOnly(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2657
- emulatorPubkey: xOnly(
2642
+ serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2643
+ emulatorPubkey: (0, import_sdk9.toXOnly)(
2658
2644
  import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2659
2645
  "emulator signer key"
2660
2646
  ),
@@ -2759,6 +2745,7 @@ async function findLockupVtxos(indexer, swapPkScript) {
2759
2745
  [recoverable.vtxos ?? [], true]
2760
2746
  ]) {
2761
2747
  for (const vtxo of vtxos) {
2748
+ if (vtxo.isUnrolled) continue;
2762
2749
  const key = `${vtxo.txid}:${vtxo.vout}`;
2763
2750
  if (seen.has(key)) continue;
2764
2751
  seen.add(key);
@@ -2781,10 +2768,17 @@ async function readLockupFate(indexer, input) {
2781
2768
  const { vtxos } = await indexer.getVtxos({ scripts: [import_base11.hex.encode(input.swapPkScript)] });
2782
2769
  const all = vtxos ?? [];
2783
2770
  if (all.length === 0) return { fate: "unknown" };
2771
+ const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk11.hasTerminalSpend)(vtxo));
2772
+ if (exited.length > 0) {
2773
+ return {
2774
+ fate: "exited",
2775
+ outpoints: exited.map((vtxo) => ({ txid: vtxo.txid, vout: vtxo.vout }))
2776
+ };
2777
+ }
2784
2778
  const spentBy = /* @__PURE__ */ new Map();
2785
2779
  let everySpendNamed = true;
2786
2780
  for (const vtxo of all) {
2787
- if (!vtxo.isSpent && !vtxo.spentBy && !vtxo.settledBy) return { fate: "open" };
2781
+ if (!(0, import_sdk11.hasTerminalSpend)(vtxo)) return { fate: "open" };
2788
2782
  if (vtxo.spentBy)
2789
2783
  spentBy.set(vtxo.spentBy, {
2790
2784
  checkpointTxid: vtxo.spentBy,
@@ -3672,12 +3666,36 @@ var RfqSwapManager = class {
3672
3666
  this.setState(swap, fate.fate === "claimed" ? "settled" : "refunded");
3673
3667
  return;
3674
3668
  }
3675
- if (swap.kind === "lightning_receive") return this.driveReceiveClaim(swap);
3669
+ if (swap.kind === "lightning_receive") {
3670
+ return fate.fate === "exited" ? this.blockExitedLockup(swap, fate) : this.driveReceiveClaim(swap);
3671
+ }
3676
3672
  if (swap.kind === "onchain_send" && swap.state !== "claimed") {
3677
3673
  if (await this.driveOnchain(swap) === "handled") return;
3678
3674
  }
3675
+ if (fate.fate === "exited") {
3676
+ const claiming = swap.state === "claimable" || swap.state === "claimed";
3677
+ if (this.config.now() < swap.refundLocktime && claiming) return;
3678
+ return this.blockExitedLockup(swap, fate);
3679
+ }
3679
3680
  await this.driveArkadeRefund(swap);
3680
3681
  }
3682
+ /**
3683
+ * The lockup was unilaterally exited: its outputs sit onchain under the
3684
+ * VHTLC script, where no offchain claim or refund can reach them.
3685
+ *
3686
+ * `needs_counterparty` rather than a terminal state, because the money still
3687
+ * needs action and the swap can still end either way — an onchain claim can
3688
+ * reveal the preimage, an onchain refund can return it — and that state is
3689
+ * documented as re-checked every pass. It must be set from HERE and not from
3690
+ * inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
3691
+ * on the very next pass.
3692
+ */
3693
+ blockExitedLockup(swap, fate) {
3694
+ this.block(
3695
+ swap,
3696
+ `the lockup was unilaterally exited (${fate.outpoints.length} output(s) onchain), so no offchain spend can move it \u2014 complete the unroll and spend it onchain`
3697
+ );
3698
+ }
3681
3699
  /**
3682
3700
  * The receive leg's whole state machine: claim the solver-funded lockup
3683
3701
  * while the window is open, and recognise the shapes in which it can be
@@ -4182,6 +4200,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4182
4200
  LIGHTNING_BTC,
4183
4201
  LIGHTNING_RECEIVE_PAIR,
4184
4202
  LIGHTNING_SEND_PAIR,
4203
+ LOCKTIME_THRESHOLD,
4185
4204
  LockupAmountMismatchError,
4186
4205
  LockupContractMissing,
4187
4206
  LockupNeedsRecoveryError,
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ProvisionedKey, ProvisionedClaimSecret, asset, IWallet, arkade, RestIndexerProvider, Transaction, IContractManager, VHTLC, Identity, ActivityResolver } from '@arkade-os/sdk';
2
- import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-Dso34L4D.cjs';
3
- export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-Dso34L4D.cjs';
2
+ import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-C8FXlmHY.cjs';
3
+ export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-C8FXlmHY.cjs';
4
4
  import { Network, LocalCardInput, DiscoveredMarket, Side, OfferPlan } from '@arkade-os/solver-discovery';
5
- import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-C-aq5LDJ.cjs';
6
- export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LightningReceiveTreeParams, i as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, j as MIN_CLAIM_WINDOW_SECONDS, k as MIN_HEADROOM_SECONDS, l as ONCHAIN_BTC, m as ONCHAIN_CLAIM_MARGIN_SECONDS, n as ONCHAIN_DUST_SATS, o as ONCHAIN_ORDER_MARGIN_SECONDS, p as ONCHAIN_RECEIVE_PAIR, q as ONCHAIN_SECONDS_PER_BLOCK, r as ONCHAIN_SEND_PAIR, s as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, t as RelaySocket, u as RfqQuote, v as RfqRefusalReason, w as RfqStatus, x as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, y as SwapRefusal, z as arkadeAssetLeg, B as arkadeSwapRequest, D as assertFundable, E as assertReceivable, F as awaitOnchainFill, G as buildHtlcClaim, J as buildHtlcRefund, K as claimOnchainFill, N as classifyOnchainHtlc, P as deriveLightningReceive, Q as deriveOnchainReceive, T as deriveOnchainSend, U as extractPreimage, V as httpTransport, W as lightningReceiveRequest, X as lightningSendRequest, Y as lightningSendVtxoScript, Z as newPreimage, _ as newRfqId, $ as offerTermsFromQuote, a0 as onchainHtlcScript, a1 as onchainReceiveRequest, a2 as onchainSendRequest, a3 as paymentHashOf, a4 as receiveVtxoScript, a5 as relayTransport, a6 as requestLightningReceive, a7 as requestLightningSend, a8 as requestOnchainReceive, a9 as requestOnchainSend, aa as rfqPair, ab as unilateralClaimDelay, ac as unilateralRefundDelay, ad as unilateralRefundWithoutReceiverDelay, ae as verifyLockupAddress, af as verifyReceiveInvoice } from './rfq-C-aq5LDJ.cjs';
5
+ import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-DglvHMNC.cjs';
6
+ export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-DglvHMNC.cjs';
7
7
 
8
8
  /**
9
9
  * Which wallet key signs this leg. Stored at `profile.signer`.
@@ -633,7 +633,7 @@ interface ClaimPacketInput {
633
633
  covclaimdPubkey: Uint8Array;
634
634
  }
635
635
  /**
636
- * Seal a preimage to covclaimd. WebCrypto AES-GCM, hence async.
636
+ * Seal a preimage to covclaimd.
637
637
  *
638
638
  * The ephemeral key and nonce are generated here and CANNOT be supplied by a
639
639
  * caller. That is the point of this signature: AES-GCM under a repeated
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ProvisionedKey, ProvisionedClaimSecret, asset, IWallet, arkade, RestIndexerProvider, Transaction, IContractManager, VHTLC, Identity, ActivityResolver } from '@arkade-os/sdk';
2
- import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-BcZ9LXRP.js';
3
- export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-BcZ9LXRP.js';
2
+ import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-Cw2EvvZG.js';
3
+ export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-Cw2EvvZG.js';
4
4
  import { Network, LocalCardInput, DiscoveredMarket, Side, OfferPlan } from '@arkade-os/solver-discovery';
5
- import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-C-aq5LDJ.js';
6
- export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LightningReceiveTreeParams, i as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, j as MIN_CLAIM_WINDOW_SECONDS, k as MIN_HEADROOM_SECONDS, l as ONCHAIN_BTC, m as ONCHAIN_CLAIM_MARGIN_SECONDS, n as ONCHAIN_DUST_SATS, o as ONCHAIN_ORDER_MARGIN_SECONDS, p as ONCHAIN_RECEIVE_PAIR, q as ONCHAIN_SECONDS_PER_BLOCK, r as ONCHAIN_SEND_PAIR, s as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, t as RelaySocket, u as RfqQuote, v as RfqRefusalReason, w as RfqStatus, x as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, y as SwapRefusal, z as arkadeAssetLeg, B as arkadeSwapRequest, D as assertFundable, E as assertReceivable, F as awaitOnchainFill, G as buildHtlcClaim, J as buildHtlcRefund, K as claimOnchainFill, N as classifyOnchainHtlc, P as deriveLightningReceive, Q as deriveOnchainReceive, T as deriveOnchainSend, U as extractPreimage, V as httpTransport, W as lightningReceiveRequest, X as lightningSendRequest, Y as lightningSendVtxoScript, Z as newPreimage, _ as newRfqId, $ as offerTermsFromQuote, a0 as onchainHtlcScript, a1 as onchainReceiveRequest, a2 as onchainSendRequest, a3 as paymentHashOf, a4 as receiveVtxoScript, a5 as relayTransport, a6 as requestLightningReceive, a7 as requestLightningSend, a8 as requestOnchainReceive, a9 as requestOnchainSend, aa as rfqPair, ab as unilateralClaimDelay, ac as unilateralRefundDelay, ad as unilateralRefundWithoutReceiverDelay, ae as verifyLockupAddress, af as verifyReceiveInvoice } from './rfq-C-aq5LDJ.js';
5
+ import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-DglvHMNC.js';
6
+ export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-DglvHMNC.js';
7
7
 
8
8
  /**
9
9
  * Which wallet key signs this leg. Stored at `profile.signer`.
@@ -633,7 +633,7 @@ interface ClaimPacketInput {
633
633
  covclaimdPubkey: Uint8Array;
634
634
  }
635
635
  /**
636
- * Seal a preimage to covclaimd. WebCrypto AES-GCM, hence async.
636
+ * Seal a preimage to covclaimd.
637
637
  *
638
638
  * The ephemeral key and nonce are generated here and CANNOT be supplied by a
639
639
  * caller. That is the point of this signature: AES-GCM under a repeated
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  LIGHTNING_BTC,
6
6
  LIGHTNING_RECEIVE_PAIR,
7
7
  LIGHTNING_SEND_PAIR,
8
+ LOCKTIME_THRESHOLD,
8
9
  LockupContractMissing,
9
10
  LockupRegistrationFailed,
10
11
  MAX_MIN_CONFIRMATIONS,
@@ -62,7 +63,7 @@ import {
62
63
  unilateralRefundWithoutReceiverDelay,
63
64
  verifyLockupAddress,
64
65
  verifyReceiveInvoice
65
- } from "./chunk-TU4NGZDP.js";
66
+ } from "./chunk-5NPYNQ5V.js";
66
67
  import {
67
68
  InMemoryAssetSwapRepository,
68
69
  marketsCacheKey
@@ -526,8 +527,8 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
526
527
  // script and the payout script comes from wallet.getAddress(), so the
527
528
  // client's network (which only shapes address derivation) is unused here
528
529
  });
529
- const serverKey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
530
- const { program, args, keys } = swapProgramBinding(offer, serverKey);
530
+ const operatorPubkey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
531
+ const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
531
532
  const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
532
533
  if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
533
534
  throw new Error(
@@ -1411,6 +1412,7 @@ import {
1411
1412
  assertSubmittedArkTxid,
1412
1413
  buildOffchainTx,
1413
1414
  getArkPsbtFields,
1415
+ hasTerminalSpend,
1414
1416
  matchServerCheckpoints
1415
1417
  } from "@arkade-os/sdk";
1416
1418
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -1471,6 +1473,7 @@ async function findLockupVtxos(indexer, swapPkScript) {
1471
1473
  [recoverable.vtxos ?? [], true]
1472
1474
  ]) {
1473
1475
  for (const vtxo of vtxos) {
1476
+ if (vtxo.isUnrolled) continue;
1474
1477
  const key = `${vtxo.txid}:${vtxo.vout}`;
1475
1478
  if (seen.has(key)) continue;
1476
1479
  seen.add(key);
@@ -1493,10 +1496,17 @@ async function readLockupFate(indexer, input) {
1493
1496
  const { vtxos } = await indexer.getVtxos({ scripts: [hex7.encode(input.swapPkScript)] });
1494
1497
  const all = vtxos ?? [];
1495
1498
  if (all.length === 0) return { fate: "unknown" };
1499
+ const exited = all.filter((vtxo) => vtxo.isUnrolled && !hasTerminalSpend(vtxo));
1500
+ if (exited.length > 0) {
1501
+ return {
1502
+ fate: "exited",
1503
+ outpoints: exited.map((vtxo) => ({ txid: vtxo.txid, vout: vtxo.vout }))
1504
+ };
1505
+ }
1496
1506
  const spentBy = /* @__PURE__ */ new Map();
1497
1507
  let everySpendNamed = true;
1498
1508
  for (const vtxo of all) {
1499
- if (!vtxo.isSpent && !vtxo.spentBy && !vtxo.settledBy) return { fate: "open" };
1509
+ if (!hasTerminalSpend(vtxo)) return { fate: "open" };
1500
1510
  if (vtxo.spentBy)
1501
1511
  spentBy.set(vtxo.spentBy, {
1502
1512
  checkpointTxid: vtxo.spentBy,
@@ -2388,12 +2398,36 @@ var RfqSwapManager = class {
2388
2398
  this.setState(swap, fate.fate === "claimed" ? "settled" : "refunded");
2389
2399
  return;
2390
2400
  }
2391
- if (swap.kind === "lightning_receive") return this.driveReceiveClaim(swap);
2401
+ if (swap.kind === "lightning_receive") {
2402
+ return fate.fate === "exited" ? this.blockExitedLockup(swap, fate) : this.driveReceiveClaim(swap);
2403
+ }
2392
2404
  if (swap.kind === "onchain_send" && swap.state !== "claimed") {
2393
2405
  if (await this.driveOnchain(swap) === "handled") return;
2394
2406
  }
2407
+ if (fate.fate === "exited") {
2408
+ const claiming = swap.state === "claimable" || swap.state === "claimed";
2409
+ if (this.config.now() < swap.refundLocktime && claiming) return;
2410
+ return this.blockExitedLockup(swap, fate);
2411
+ }
2395
2412
  await this.driveArkadeRefund(swap);
2396
2413
  }
2414
+ /**
2415
+ * The lockup was unilaterally exited: its outputs sit onchain under the
2416
+ * VHTLC script, where no offchain claim or refund can reach them.
2417
+ *
2418
+ * `needs_counterparty` rather than a terminal state, because the money still
2419
+ * needs action and the swap can still end either way — an onchain claim can
2420
+ * reveal the preimage, an onchain refund can return it — and that state is
2421
+ * documented as re-checked every pass. It must be set from HERE and not from
2422
+ * inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
2423
+ * on the very next pass.
2424
+ */
2425
+ blockExitedLockup(swap, fate) {
2426
+ this.block(
2427
+ swap,
2428
+ `the lockup was unilaterally exited (${fate.outpoints.length} output(s) onchain), so no offchain spend can move it \u2014 complete the unroll and spend it onchain`
2429
+ );
2430
+ }
2397
2431
  /**
2398
2432
  * The receive leg's whole state machine: claim the solver-funded lockup
2399
2433
  * while the window is open, and recognise the shapes in which it can be
@@ -2899,6 +2933,7 @@ export {
2899
2933
  LIGHTNING_BTC,
2900
2934
  LIGHTNING_RECEIVE_PAIR,
2901
2935
  LIGHTNING_SEND_PAIR,
2936
+ LOCKTIME_THRESHOLD,
2902
2937
  LockupAmountMismatchError,
2903
2938
  LockupContractMissing,
2904
2939
  LockupNeedsRecoveryError,
package/dist/nostr.cjs CHANGED
@@ -62,6 +62,7 @@ var import_sdk3 = require("@arkade-os/sdk");
62
62
 
63
63
  // src/claimPacket.ts
64
64
  var import_base2 = require("@scure/base");
65
+ var import_aes = require("@noble/ciphers/aes.js");
65
66
  var import_secp256k1 = require("@noble/curves/secp256k1.js");
66
67
  var import_hkdf = require("@noble/hashes/hkdf.js");
67
68
  var import_sha22 = require("@noble/hashes/sha2.js");
package/dist/nostr.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { x as RfqTransport } from './rfq-C-aq5LDJ.cjs';
1
+ import { y as RfqTransport } from './rfq-DglvHMNC.cjs';
2
2
  import { SimplePool } from 'nostr-tools';
3
3
  import '@arkade-os/sdk';
4
4
 
package/dist/nostr.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { x as RfqTransport } from './rfq-C-aq5LDJ.js';
1
+ import { y as RfqTransport } from './rfq-DglvHMNC.js';
2
2
  import { SimplePool } from 'nostr-tools';
3
3
  import '@arkade-os/sdk';
4
4
 
package/dist/nostr.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  expectQuote,
3
3
  pairOf
4
- } from "./chunk-TU4NGZDP.js";
4
+ } from "./chunk-5NPYNQ5V.js";
5
5
 
6
6
  // src/nostr.ts
7
7
  import {
@@ -1,8 +1,8 @@
1
1
  import { RealmLike } from '@arkade-os/sdk/repositories/realm';
2
- import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Dso34L4D.cjs';
2
+ import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-C8FXlmHY.cjs';
3
3
  import '@arkade-os/solver-discovery';
4
4
  import '@arkade-os/sdk';
5
- import '../../rfq-C-aq5LDJ.cjs';
5
+ import '../../rfq-DglvHMNC.cjs';
6
6
 
7
7
  /**
8
8
  * Realm backend for React Native.
@@ -1,8 +1,8 @@
1
1
  import { RealmLike } from '@arkade-os/sdk/repositories/realm';
2
- import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-BcZ9LXRP.js';
2
+ import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Cw2EvvZG.js';
3
3
  import '@arkade-os/solver-discovery';
4
4
  import '@arkade-os/sdk';
5
- import '../../rfq-C-aq5LDJ.js';
5
+ import '../../rfq-DglvHMNC.js';
6
6
 
7
7
  /**
8
8
  * Realm backend for React Native.
@@ -1,8 +1,8 @@
1
1
  import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
2
- import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Dso34L4D.cjs';
2
+ import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-C8FXlmHY.cjs';
3
3
  import '@arkade-os/solver-discovery';
4
4
  import '@arkade-os/sdk';
5
- import '../../rfq-C-aq5LDJ.cjs';
5
+ import '../../rfq-DglvHMNC.cjs';
6
6
 
7
7
  /**
8
8
  * SQLite backend over the SDK's `SQLExecutor`, so any driver plugs in
@@ -1,8 +1,8 @@
1
1
  import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
2
- import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-BcZ9LXRP.js';
2
+ import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Cw2EvvZG.js';
3
3
  import '@arkade-os/solver-discovery';
4
4
  import '@arkade-os/sdk';
5
- import '../../rfq-C-aq5LDJ.js';
5
+ import '../../rfq-DglvHMNC.js';
6
6
 
7
7
  /**
8
8
  * SQLite backend over the SDK's `SQLExecutor`, so any driver plugs in
@@ -1,6 +1,6 @@
1
1
  import { DiscoveredMarket } from '@arkade-os/solver-discovery';
2
2
  import { IWallet, ProvisionedKey, ProvisionedClaimSecret, RestArkProvider, RestIndexerProvider, VHTLC, Identity, IContractManager } from '@arkade-os/sdk';
3
- import { w as RfqStatus, x as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, s as OnchainHtlcPhase } from './rfq-C-aq5LDJ.cjs';
3
+ import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-DglvHMNC.cjs';
4
4
 
5
5
  type AssetSwapStatus = "pending" | "cancelling" | "fulfilled" | "cancelled" | "recoverable" | "awaiting_fill" | "claimable" | "claimed" | "refunded_l1";
6
6
  /** The sentinel asset id for BTC itself, as opposed to a 68-hex asset id.
@@ -227,12 +227,15 @@ interface LockupVtxo {
227
227
  * It is still the trader's money and it is still visible, which is why
228
228
  * {@link findLockupVtxos} returns it. What it is not is refundable by
229
229
  * {@link pushRefundWithoutReceiver}: that builds an offchain Ark
230
- * transaction, and the SDK's own predicates make the two states mutually
231
- * exclusive — `canSpendOffchain` is false exactly when `canRecoverOnchain`
232
- * is true (`wallet/vtxo.ts`), and the latter is documented as "must be
233
- * recovered into a fresh batch rather than spent offchain". Holding the
234
- * trader's `sender` key does not change that; a sweep removes the leaf from
235
- * the live tree, not the signature from the trader.
230
+ * transaction, and the SDK's own predicates put the two states on opposite
231
+ * sides — `canSpendOffchain` is false whenever `canRecoverOnchain` is true
232
+ * (`wallet/vtxo.ts`), and the latter is documented as "must be recovered
233
+ * into a fresh batch rather than spent offchain". Holding the trader's
234
+ * `sender` key does not change that; a sweep removes the leaf from the live
235
+ * tree, not the signature from the trader.
236
+ *
237
+ * Opposite, but not exhaustive: an unrolled output satisfies neither, and
238
+ * {@link findLockupVtxos} drops those before they reach this type at all.
236
239
  *
237
240
  * `packages/boltz-swap` splits on exactly this fact rather than working
238
241
  * around it: `settleRefundWithoutReceiver` sends a live VTXO through an
@@ -321,6 +324,18 @@ declare class LockupNeedsRecoveryError extends Error {
321
324
  * That function refuses the recoverable ones by name rather than submitting a
322
325
  * spend the server must reject.
323
326
  *
327
+ * **Unrolled outputs are the one exception, and are dropped.** A unilaterally
328
+ * exited output lives onchain behind its CSV; no offchain spend of any leaf can
329
+ * reach it, and `LockupVtxo` carries no field to say so, so a caller could not
330
+ * tell it apart from a live one. Whether arkd returns such an output under
331
+ * `spendableOnly` is not determinable from here, so the exclusion is made
332
+ * defensively rather than assumed. It costs the two waiting callers nothing
333
+ * they wanted: `awaitLockupFunding` keeps waiting for a claimable lockup
334
+ * instead of publishing `P` into a spend that cannot land, and
335
+ * `refundIfUnresolved` reports `nothing_to_refund` instead of grinding a doomed
336
+ * push to its deadline. The manager reads the exit through
337
+ * {@link readLockupFate}, which queries unfiltered and reports it as `exited`.
338
+ *
324
339
  * This read — not the RFQ's reported state — is the authority on whether
325
340
  * there is anything left at the lockup.
326
341
  *
@@ -374,6 +389,29 @@ type LockupFate =
374
389
  fate: "returned";
375
390
  spends: readonly LockupSpend[];
376
391
  }
392
+ /**
393
+ * At least one output was unilaterally exited: it sits onchain under the
394
+ * VHTLC script, where no offchain claim or refund can reach it.
395
+ *
396
+ * Not terminal, and not a loss. The money is still under the same script
397
+ * with the same leaves, so `completeUnroll` plus an onchain spend can still
398
+ * end the swap either way — which is why this outranks `open`: an output
399
+ * that is "still unspent" but unreachable is not a swap that is merely
400
+ * running.
401
+ *
402
+ * It outranks a verdict too, on a lockup where a sibling output was claimed
403
+ * or returned. That is the rule `open` already sets, not a new one: the
404
+ * unspent test short-circuits before any witness is read, so a partially
405
+ * resolved lockup has never reported `claimed`/`returned`. What changes is
406
+ * only that such a lockup now says why it is unresolved.
407
+ */
408
+ | {
409
+ fate: "exited";
410
+ outpoints: readonly {
411
+ txid: string;
412
+ vout: number;
413
+ }[];
414
+ }
377
415
  /** Nothing was learned: no outputs visible, an output spent by nothing the
378
416
  * indexer names, a spend it could not produce, or a blob that would not
379
417
  * decode. Never an answer. */
@@ -409,6 +447,11 @@ type LockupFate =
409
447
  * response to `unknown` is the same as to `open`: keep watching, and let the
410
448
  * refund timelock — which no outage can move — be what ends the wait.
411
449
  *
450
+ * **An exit is read before anything else, and over the whole set.** It is the
451
+ * one fact that makes an unspent output unreachable, so it outranks `open`; and
452
+ * it is scanned across every output rather than in outpoint order, so which
453
+ * output happens to come first cannot change the answer.
454
+ *
412
455
  * Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
413
456
  * cached, the same posture {@link findLockupVtxos} already establishes.
414
457
  */
@@ -447,9 +490,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
447
490
  * {@link refundIfUnresolved}, which retries.
448
491
  *
449
492
  * **Swept outputs are refused, not attempted.** This is an OFFCHAIN spend, and
450
- * a swept output is no longer a live leaf: `canSpendOffchain` and
451
- * `canRecoverOnchain` are mutually exclusive by construction, so a recoverable
452
- * input cannot be spent this way whatever key signs it (see
493
+ * a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
494
+ * `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
495
+ * whatever key signs it (see
453
496
  * {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
454
497
  * transaction, a single swept output would take the live ones down with it —
455
498
  * so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
@@ -1162,7 +1205,9 @@ interface RfqSwapManagerDeps {
1162
1205
  * `settled`; a lockup fully spent by anything else ends it `refunded`.
1163
1206
  * Anything the indexer could not answer is `unknown`, which is NOT an
1164
1207
  * answer: the pass carries on to the steps below, whose deadlines an indexer
1165
- * outage has no bearing on.
1208
+ * outage has no bearing on. `exited` — an output unilaterally taken onchain
1209
+ * — ends neither the swap nor the pass: it blocks the Arkade half below,
1210
+ * with the L1 half left running.
1166
1211
  * 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
1167
1212
  * {@link nextOnchainAction}. On a receive it is the lockup itself, and it
1168
1213
  * ends the pass: that leg has no step 3.
@@ -1493,6 +1538,18 @@ declare class RfqSwapManager {
1493
1538
  private arm;
1494
1539
  private pollSwap;
1495
1540
  private runPass;
1541
+ /**
1542
+ * The lockup was unilaterally exited: its outputs sit onchain under the
1543
+ * VHTLC script, where no offchain claim or refund can reach them.
1544
+ *
1545
+ * `needs_counterparty` rather than a terminal state, because the money still
1546
+ * needs action and the swap can still end either way — an onchain claim can
1547
+ * reveal the preimage, an onchain refund can return it — and that state is
1548
+ * documented as re-checked every pass. It must be set from HERE and not from
1549
+ * inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
1550
+ * on the very next pass.
1551
+ */
1552
+ private blockExitedLockup;
1496
1553
  /**
1497
1554
  * The receive leg's whole state machine: claim the solver-funded lockup
1498
1555
  * while the window is open, and recognise the shapes in which it can be
@@ -1,6 +1,6 @@
1
1
  import { DiscoveredMarket } from '@arkade-os/solver-discovery';
2
2
  import { IWallet, ProvisionedKey, ProvisionedClaimSecret, RestArkProvider, RestIndexerProvider, VHTLC, Identity, IContractManager } from '@arkade-os/sdk';
3
- import { w as RfqStatus, x as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, s as OnchainHtlcPhase } from './rfq-C-aq5LDJ.js';
3
+ import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-DglvHMNC.js';
4
4
 
5
5
  type AssetSwapStatus = "pending" | "cancelling" | "fulfilled" | "cancelled" | "recoverable" | "awaiting_fill" | "claimable" | "claimed" | "refunded_l1";
6
6
  /** The sentinel asset id for BTC itself, as opposed to a 68-hex asset id.
@@ -227,12 +227,15 @@ interface LockupVtxo {
227
227
  * It is still the trader's money and it is still visible, which is why
228
228
  * {@link findLockupVtxos} returns it. What it is not is refundable by
229
229
  * {@link pushRefundWithoutReceiver}: that builds an offchain Ark
230
- * transaction, and the SDK's own predicates make the two states mutually
231
- * exclusive — `canSpendOffchain` is false exactly when `canRecoverOnchain`
232
- * is true (`wallet/vtxo.ts`), and the latter is documented as "must be
233
- * recovered into a fresh batch rather than spent offchain". Holding the
234
- * trader's `sender` key does not change that; a sweep removes the leaf from
235
- * the live tree, not the signature from the trader.
230
+ * transaction, and the SDK's own predicates put the two states on opposite
231
+ * sides — `canSpendOffchain` is false whenever `canRecoverOnchain` is true
232
+ * (`wallet/vtxo.ts`), and the latter is documented as "must be recovered
233
+ * into a fresh batch rather than spent offchain". Holding the trader's
234
+ * `sender` key does not change that; a sweep removes the leaf from the live
235
+ * tree, not the signature from the trader.
236
+ *
237
+ * Opposite, but not exhaustive: an unrolled output satisfies neither, and
238
+ * {@link findLockupVtxos} drops those before they reach this type at all.
236
239
  *
237
240
  * `packages/boltz-swap` splits on exactly this fact rather than working
238
241
  * around it: `settleRefundWithoutReceiver` sends a live VTXO through an
@@ -321,6 +324,18 @@ declare class LockupNeedsRecoveryError extends Error {
321
324
  * That function refuses the recoverable ones by name rather than submitting a
322
325
  * spend the server must reject.
323
326
  *
327
+ * **Unrolled outputs are the one exception, and are dropped.** A unilaterally
328
+ * exited output lives onchain behind its CSV; no offchain spend of any leaf can
329
+ * reach it, and `LockupVtxo` carries no field to say so, so a caller could not
330
+ * tell it apart from a live one. Whether arkd returns such an output under
331
+ * `spendableOnly` is not determinable from here, so the exclusion is made
332
+ * defensively rather than assumed. It costs the two waiting callers nothing
333
+ * they wanted: `awaitLockupFunding` keeps waiting for a claimable lockup
334
+ * instead of publishing `P` into a spend that cannot land, and
335
+ * `refundIfUnresolved` reports `nothing_to_refund` instead of grinding a doomed
336
+ * push to its deadline. The manager reads the exit through
337
+ * {@link readLockupFate}, which queries unfiltered and reports it as `exited`.
338
+ *
324
339
  * This read — not the RFQ's reported state — is the authority on whether
325
340
  * there is anything left at the lockup.
326
341
  *
@@ -374,6 +389,29 @@ type LockupFate =
374
389
  fate: "returned";
375
390
  spends: readonly LockupSpend[];
376
391
  }
392
+ /**
393
+ * At least one output was unilaterally exited: it sits onchain under the
394
+ * VHTLC script, where no offchain claim or refund can reach it.
395
+ *
396
+ * Not terminal, and not a loss. The money is still under the same script
397
+ * with the same leaves, so `completeUnroll` plus an onchain spend can still
398
+ * end the swap either way — which is why this outranks `open`: an output
399
+ * that is "still unspent" but unreachable is not a swap that is merely
400
+ * running.
401
+ *
402
+ * It outranks a verdict too, on a lockup where a sibling output was claimed
403
+ * or returned. That is the rule `open` already sets, not a new one: the
404
+ * unspent test short-circuits before any witness is read, so a partially
405
+ * resolved lockup has never reported `claimed`/`returned`. What changes is
406
+ * only that such a lockup now says why it is unresolved.
407
+ */
408
+ | {
409
+ fate: "exited";
410
+ outpoints: readonly {
411
+ txid: string;
412
+ vout: number;
413
+ }[];
414
+ }
377
415
  /** Nothing was learned: no outputs visible, an output spent by nothing the
378
416
  * indexer names, a spend it could not produce, or a blob that would not
379
417
  * decode. Never an answer. */
@@ -409,6 +447,11 @@ type LockupFate =
409
447
  * response to `unknown` is the same as to `open`: keep watching, and let the
410
448
  * refund timelock — which no outage can move — be what ends the wait.
411
449
  *
450
+ * **An exit is read before anything else, and over the whole set.** It is the
451
+ * one fact that makes an unspent output unreachable, so it outranks `open`; and
452
+ * it is scanned across every output rather than in outpoint order, so which
453
+ * output happens to come first cannot change the answer.
454
+ *
412
455
  * Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
413
456
  * cached, the same posture {@link findLockupVtxos} already establishes.
414
457
  */
@@ -447,9 +490,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
447
490
  * {@link refundIfUnresolved}, which retries.
448
491
  *
449
492
  * **Swept outputs are refused, not attempted.** This is an OFFCHAIN spend, and
450
- * a swept output is no longer a live leaf: `canSpendOffchain` and
451
- * `canRecoverOnchain` are mutually exclusive by construction, so a recoverable
452
- * input cannot be spent this way whatever key signs it (see
493
+ * a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
494
+ * `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
495
+ * whatever key signs it (see
453
496
  * {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
454
497
  * transaction, a single swept output would take the live ones down with it —
455
498
  * so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
@@ -1162,7 +1205,9 @@ interface RfqSwapManagerDeps {
1162
1205
  * `settled`; a lockup fully spent by anything else ends it `refunded`.
1163
1206
  * Anything the indexer could not answer is `unknown`, which is NOT an
1164
1207
  * answer: the pass carries on to the steps below, whose deadlines an indexer
1165
- * outage has no bearing on.
1208
+ * outage has no bearing on. `exited` — an output unilaterally taken onchain
1209
+ * — ends neither the swap nor the pass: it blocks the Arkade half below,
1210
+ * with the L1 half left running.
1166
1211
  * 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
1167
1212
  * {@link nextOnchainAction}. On a receive it is the lockup itself, and it
1168
1213
  * ends the pass: that leg has no step 3.
@@ -1493,6 +1538,18 @@ declare class RfqSwapManager {
1493
1538
  private arm;
1494
1539
  private pollSwap;
1495
1540
  private runPass;
1541
+ /**
1542
+ * The lockup was unilaterally exited: its outputs sit onchain under the
1543
+ * VHTLC script, where no offchain claim or refund can reach them.
1544
+ *
1545
+ * `needs_counterparty` rather than a terminal state, because the money still
1546
+ * needs action and the swap can still end either way — an onchain claim can
1547
+ * reveal the preimage, an onchain refund can return it — and that state is
1548
+ * documented as re-checked every pass. It must be set from HERE and not from
1549
+ * inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
1550
+ * on the very next pass.
1551
+ */
1552
+ private blockExitedLockup;
1496
1553
  /**
1497
1554
  * The receive leg's whole state machine: claim the solver-funded lockup
1498
1555
  * while the window is open, and recognise the shapes in which it can be
@@ -9,6 +9,13 @@ declare const ONCHAIN_ORDER_MARGIN_SECONDS: number;
9
9
  declare const ONCHAIN_CLAIM_MARGIN_SECONDS: number;
10
10
  /** Bounds on the confirmation depth a quote may demand. */
11
11
  declare const MAX_MIN_CONFIRMATIONS = 6;
12
+ /**
13
+ * BIP65's boundary between the two things an absolute locktime can mean.
14
+ * Below it consensus reads the value as a block height; at or above it, as a
15
+ * unix timestamp. 500,000,000 itself is 1985-07-05 and is a timestamp, so the
16
+ * comparison against it is strict.
17
+ */
18
+ declare const LOCKTIME_THRESHOLD = 500000000;
12
19
  /** Conservative block interval for converting depths into wall-clock time. */
13
20
  declare const ONCHAIN_SECONDS_PER_BLOCK = 600;
14
21
  /**
@@ -239,7 +246,7 @@ declare const ONCHAIN_SEND_PAIR: string;
239
246
  /** On-board: a Bitcoin-L1 HTLC in, Arkade sats out. */
240
247
  declare const ONCHAIN_RECEIVE_PAIR: string;
241
248
  /** The closed refusal set. Treat any unknown reason as a generic decline. */
242
- type RfqRefusalReason = "unsupported_pair" | "unsupported_payload" | "amount_out_of_range" | "exposure_cap" | "invoice_expired" | "quote_conflict" | "pricing_unavailable";
249
+ type RfqRefusalReason = "unsupported_pair" | "unsupported_payload" | "amount_out_of_range" | "exposure_cap" | "invoice_expired" | "quote_conflict" | "pricing_unavailable" | "rate_limited";
243
250
  /** Lifecycle vocabulary; states after which nothing more will happen. */
244
251
  declare const RFQ_TERMINAL_STATES: readonly ["settled", "refused", "expired", "refunded", "stuck"];
245
252
  /** A refusal from the solver, carrying its closed-set reason. */
@@ -480,12 +487,11 @@ interface InvoiceFacts {
480
487
  * throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
481
488
  * for older records; a repeat write is a no-op.
482
489
  *
483
- * The `sender` key comes from the wallet a fresh HD descriptor per call, or
484
- * the wallet's static key and is returned as `senderPubkey` plus `secrets`.
485
- * `secrets` holds only a public descriptor; the signer re-derives from the
486
- * wallet, so nothing secret is at rest. Persist `secrets` with the record
487
- * anyway: it is how the refund signer is found again. `nonInteractiveRefund`
488
- * recovers the funds even without it — but it needs the SOLVER's active
490
+ * The `sender` key is the wallet's identity key, reused by {@link
491
+ * provisionRefundKey} — returned as `senderPubkey` plus `secrets`. `secrets`
492
+ * holds only a public descriptor; the signer re-derives from the wallet, so
493
+ * nothing secret is at rest. Persist `secrets` with the record anyway: it is
494
+ * how the refund signer is found again. `nonInteractiveRefund` recovers the funds even without it — but it needs the SOLVER's active
489
495
  * cooperation, not just infrastructure uptime.
490
496
  */
491
497
  declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, transport: RfqTransport, params: {
@@ -508,7 +514,8 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
508
514
  * `lockup` (with `address`): without it the manager can only poll, and
509
515
  * cannot retire the row this call just wrote. */
510
516
  script: InstanceType<typeof VHTLC.ScriptV2>;
511
- /** Where a failed swap refunds. */
517
+ /** Where a failed swap refunds — the same address `secrets.pkScript` was
518
+ * decoded from, so the quote and the covenant always name one script. */
512
519
  refundAddress: string;
513
520
  /** The VHTLC `sender` x-only key, bound into the covenant. Public. */
514
521
  senderPubkey: Uint8Array;
@@ -521,7 +528,9 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
521
528
  * Returned so a consumer can persist the swap without re-deriving any of
522
529
  * it. Half of these are not on the quote: `serverPubkey` and `claimDelay`
523
530
  * come from this wallet's own `getInfo()`, `emulatorPubkey` from a
524
- * per-network pin, `refundPkScript` from decoding an address.
531
+ * per-network pin, `refundPkScript` from `secrets` decoded from the
532
+ * refund address at provisioning time, the same address this call returns
533
+ * as `refundAddress`.
525
534
  *
526
535
  * All public. Persisting them is optional: this call also registers the
527
536
  * lockup as a contract, and that row is where `rebuildRfqSwap` takes its
@@ -993,4 +1002,4 @@ declare function requestOnchainReceive(wallet: IWallet, arkServerUrl: string, tr
993
1002
  secrets: ProvisionedClaimSecret;
994
1003
  }>;
995
1004
 
996
- export { offerTermsFromQuote as $, ARKADE_ASSET as A, arkadeSwapRequest as B, type ChainSource as C, assertFundable as D, assertReceivable as E, awaitOnchainFill as F, buildHtlcClaim as G, type HtlcUtxo as H, type InvoiceFacts as I, buildHtlcRefund as J, claimOnchainFill as K, LIGHTNING_BTC as L, MAX_MIN_CONFIRMATIONS as M, classifyOnchainHtlc as N, type OnchainNetwork as O, deriveLightningReceive as P, deriveOnchainReceive as Q, RFQ_TERMINAL_STATES as R, SOLO_REFUND_HEADROOM_SECONDS as S, deriveOnchainSend as T, extractPreimage as U, httpTransport as V, lightningReceiveRequest as W, lightningSendRequest as X, lightningSendVtxoScript as Y, newPreimage as Z, newRfqId as _, type OnchainHtlc as a, onchainHtlcScript as a0, onchainReceiveRequest as a1, onchainSendRequest as a2, paymentHashOf as a3, receiveVtxoScript as a4, relayTransport as a5, requestLightningReceive as a6, requestLightningSend as a7, requestOnchainReceive as a8, requestOnchainSend as a9, rfqPair as aa, unilateralClaimDelay as ab, unilateralRefundDelay as ac, unilateralRefundWithoutReceiverDelay as ad, verifyLockupAddress as ae, verifyReceiveInvoice as af, type OnchainHtlcParams as b, ARKADE_BTC as c, AddressMismatch as d, type ChainUtxo as e, LIGHTNING_RECEIVE_PAIR as f, LIGHTNING_SEND_PAIR as g, type LightningReceiveTreeParams as h, type LightningSendTreeParams as i, MIN_CLAIM_WINDOW_SECONDS as j, MIN_HEADROOM_SECONDS as k, ONCHAIN_BTC as l, ONCHAIN_CLAIM_MARGIN_SECONDS as m, ONCHAIN_DUST_SATS as n, ONCHAIN_ORDER_MARGIN_SECONDS as o, ONCHAIN_RECEIVE_PAIR as p, ONCHAIN_SECONDS_PER_BLOCK as q, ONCHAIN_SEND_PAIR as r, type OnchainHtlcPhase as s, type RelaySocket as t, type RfqQuote as u, type RfqRefusalReason as v, type RfqStatus as w, type RfqTransport as x, SwapRefusal as y, arkadeAssetLeg as z };
1005
+ export { newRfqId as $, ARKADE_ASSET as A, arkadeAssetLeg as B, type ChainSource as C, arkadeSwapRequest as D, assertFundable as E, assertReceivable as F, awaitOnchainFill as G, type HtlcUtxo as H, type InvoiceFacts as I, buildHtlcClaim as J, buildHtlcRefund as K, LIGHTNING_BTC as L, MAX_MIN_CONFIRMATIONS as M, claimOnchainFill as N, type OnchainNetwork as O, classifyOnchainHtlc as P, deriveLightningReceive as Q, RFQ_TERMINAL_STATES as R, SOLO_REFUND_HEADROOM_SECONDS as S, deriveOnchainReceive as T, deriveOnchainSend as U, extractPreimage as V, httpTransport as W, lightningReceiveRequest as X, lightningSendRequest as Y, lightningSendVtxoScript as Z, newPreimage as _, type OnchainHtlc as a, offerTermsFromQuote as a0, onchainHtlcScript as a1, onchainReceiveRequest as a2, onchainSendRequest as a3, paymentHashOf as a4, receiveVtxoScript as a5, relayTransport as a6, requestLightningReceive as a7, requestLightningSend as a8, requestOnchainReceive as a9, requestOnchainSend as aa, rfqPair as ab, unilateralClaimDelay as ac, unilateralRefundDelay as ad, unilateralRefundWithoutReceiverDelay as ae, verifyLockupAddress as af, verifyReceiveInvoice as ag, type OnchainHtlcParams as b, ARKADE_BTC as c, AddressMismatch as d, type ChainUtxo as e, LIGHTNING_RECEIVE_PAIR as f, LIGHTNING_SEND_PAIR as g, LOCKTIME_THRESHOLD as h, type LightningReceiveTreeParams as i, type LightningSendTreeParams as j, MIN_CLAIM_WINDOW_SECONDS as k, MIN_HEADROOM_SECONDS as l, ONCHAIN_BTC as m, ONCHAIN_CLAIM_MARGIN_SECONDS as n, ONCHAIN_DUST_SATS as o, ONCHAIN_ORDER_MARGIN_SECONDS as p, ONCHAIN_RECEIVE_PAIR as q, ONCHAIN_SECONDS_PER_BLOCK as r, ONCHAIN_SEND_PAIR as s, type OnchainHtlcPhase as t, type RelaySocket as u, type RfqQuote as v, type RfqRefusalReason as w, type RfqStatus as x, type RfqTransport as y, SwapRefusal as z };
@@ -9,6 +9,13 @@ declare const ONCHAIN_ORDER_MARGIN_SECONDS: number;
9
9
  declare const ONCHAIN_CLAIM_MARGIN_SECONDS: number;
10
10
  /** Bounds on the confirmation depth a quote may demand. */
11
11
  declare const MAX_MIN_CONFIRMATIONS = 6;
12
+ /**
13
+ * BIP65's boundary between the two things an absolute locktime can mean.
14
+ * Below it consensus reads the value as a block height; at or above it, as a
15
+ * unix timestamp. 500,000,000 itself is 1985-07-05 and is a timestamp, so the
16
+ * comparison against it is strict.
17
+ */
18
+ declare const LOCKTIME_THRESHOLD = 500000000;
12
19
  /** Conservative block interval for converting depths into wall-clock time. */
13
20
  declare const ONCHAIN_SECONDS_PER_BLOCK = 600;
14
21
  /**
@@ -239,7 +246,7 @@ declare const ONCHAIN_SEND_PAIR: string;
239
246
  /** On-board: a Bitcoin-L1 HTLC in, Arkade sats out. */
240
247
  declare const ONCHAIN_RECEIVE_PAIR: string;
241
248
  /** The closed refusal set. Treat any unknown reason as a generic decline. */
242
- type RfqRefusalReason = "unsupported_pair" | "unsupported_payload" | "amount_out_of_range" | "exposure_cap" | "invoice_expired" | "quote_conflict" | "pricing_unavailable";
249
+ type RfqRefusalReason = "unsupported_pair" | "unsupported_payload" | "amount_out_of_range" | "exposure_cap" | "invoice_expired" | "quote_conflict" | "pricing_unavailable" | "rate_limited";
243
250
  /** Lifecycle vocabulary; states after which nothing more will happen. */
244
251
  declare const RFQ_TERMINAL_STATES: readonly ["settled", "refused", "expired", "refunded", "stuck"];
245
252
  /** A refusal from the solver, carrying its closed-set reason. */
@@ -480,12 +487,11 @@ interface InvoiceFacts {
480
487
  * throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
481
488
  * for older records; a repeat write is a no-op.
482
489
  *
483
- * The `sender` key comes from the wallet a fresh HD descriptor per call, or
484
- * the wallet's static key and is returned as `senderPubkey` plus `secrets`.
485
- * `secrets` holds only a public descriptor; the signer re-derives from the
486
- * wallet, so nothing secret is at rest. Persist `secrets` with the record
487
- * anyway: it is how the refund signer is found again. `nonInteractiveRefund`
488
- * recovers the funds even without it — but it needs the SOLVER's active
490
+ * The `sender` key is the wallet's identity key, reused by {@link
491
+ * provisionRefundKey} — returned as `senderPubkey` plus `secrets`. `secrets`
492
+ * holds only a public descriptor; the signer re-derives from the wallet, so
493
+ * nothing secret is at rest. Persist `secrets` with the record anyway: it is
494
+ * how the refund signer is found again. `nonInteractiveRefund` recovers the funds even without it — but it needs the SOLVER's active
489
495
  * cooperation, not just infrastructure uptime.
490
496
  */
491
497
  declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, transport: RfqTransport, params: {
@@ -508,7 +514,8 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
508
514
  * `lockup` (with `address`): without it the manager can only poll, and
509
515
  * cannot retire the row this call just wrote. */
510
516
  script: InstanceType<typeof VHTLC.ScriptV2>;
511
- /** Where a failed swap refunds. */
517
+ /** Where a failed swap refunds — the same address `secrets.pkScript` was
518
+ * decoded from, so the quote and the covenant always name one script. */
512
519
  refundAddress: string;
513
520
  /** The VHTLC `sender` x-only key, bound into the covenant. Public. */
514
521
  senderPubkey: Uint8Array;
@@ -521,7 +528,9 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
521
528
  * Returned so a consumer can persist the swap without re-deriving any of
522
529
  * it. Half of these are not on the quote: `serverPubkey` and `claimDelay`
523
530
  * come from this wallet's own `getInfo()`, `emulatorPubkey` from a
524
- * per-network pin, `refundPkScript` from decoding an address.
531
+ * per-network pin, `refundPkScript` from `secrets` decoded from the
532
+ * refund address at provisioning time, the same address this call returns
533
+ * as `refundAddress`.
525
534
  *
526
535
  * All public. Persisting them is optional: this call also registers the
527
536
  * lockup as a contract, and that row is where `rebuildRfqSwap` takes its
@@ -993,4 +1002,4 @@ declare function requestOnchainReceive(wallet: IWallet, arkServerUrl: string, tr
993
1002
  secrets: ProvisionedClaimSecret;
994
1003
  }>;
995
1004
 
996
- export { offerTermsFromQuote as $, ARKADE_ASSET as A, arkadeSwapRequest as B, type ChainSource as C, assertFundable as D, assertReceivable as E, awaitOnchainFill as F, buildHtlcClaim as G, type HtlcUtxo as H, type InvoiceFacts as I, buildHtlcRefund as J, claimOnchainFill as K, LIGHTNING_BTC as L, MAX_MIN_CONFIRMATIONS as M, classifyOnchainHtlc as N, type OnchainNetwork as O, deriveLightningReceive as P, deriveOnchainReceive as Q, RFQ_TERMINAL_STATES as R, SOLO_REFUND_HEADROOM_SECONDS as S, deriveOnchainSend as T, extractPreimage as U, httpTransport as V, lightningReceiveRequest as W, lightningSendRequest as X, lightningSendVtxoScript as Y, newPreimage as Z, newRfqId as _, type OnchainHtlc as a, onchainHtlcScript as a0, onchainReceiveRequest as a1, onchainSendRequest as a2, paymentHashOf as a3, receiveVtxoScript as a4, relayTransport as a5, requestLightningReceive as a6, requestLightningSend as a7, requestOnchainReceive as a8, requestOnchainSend as a9, rfqPair as aa, unilateralClaimDelay as ab, unilateralRefundDelay as ac, unilateralRefundWithoutReceiverDelay as ad, verifyLockupAddress as ae, verifyReceiveInvoice as af, type OnchainHtlcParams as b, ARKADE_BTC as c, AddressMismatch as d, type ChainUtxo as e, LIGHTNING_RECEIVE_PAIR as f, LIGHTNING_SEND_PAIR as g, type LightningReceiveTreeParams as h, type LightningSendTreeParams as i, MIN_CLAIM_WINDOW_SECONDS as j, MIN_HEADROOM_SECONDS as k, ONCHAIN_BTC as l, ONCHAIN_CLAIM_MARGIN_SECONDS as m, ONCHAIN_DUST_SATS as n, ONCHAIN_ORDER_MARGIN_SECONDS as o, ONCHAIN_RECEIVE_PAIR as p, ONCHAIN_SECONDS_PER_BLOCK as q, ONCHAIN_SEND_PAIR as r, type OnchainHtlcPhase as s, type RelaySocket as t, type RfqQuote as u, type RfqRefusalReason as v, type RfqStatus as w, type RfqTransport as x, SwapRefusal as y, arkadeAssetLeg as z };
1005
+ export { newRfqId as $, ARKADE_ASSET as A, arkadeAssetLeg as B, type ChainSource as C, arkadeSwapRequest as D, assertFundable as E, assertReceivable as F, awaitOnchainFill as G, type HtlcUtxo as H, type InvoiceFacts as I, buildHtlcClaim as J, buildHtlcRefund as K, LIGHTNING_BTC as L, MAX_MIN_CONFIRMATIONS as M, claimOnchainFill as N, type OnchainNetwork as O, classifyOnchainHtlc as P, deriveLightningReceive as Q, RFQ_TERMINAL_STATES as R, SOLO_REFUND_HEADROOM_SECONDS as S, deriveOnchainReceive as T, deriveOnchainSend as U, extractPreimage as V, httpTransport as W, lightningReceiveRequest as X, lightningSendRequest as Y, lightningSendVtxoScript as Z, newPreimage as _, type OnchainHtlc as a, offerTermsFromQuote as a0, onchainHtlcScript as a1, onchainReceiveRequest as a2, onchainSendRequest as a3, paymentHashOf as a4, receiveVtxoScript as a5, relayTransport as a6, requestLightningReceive as a7, requestLightningSend as a8, requestOnchainReceive as a9, requestOnchainSend as aa, rfqPair as ab, unilateralClaimDelay as ac, unilateralRefundDelay as ad, unilateralRefundWithoutReceiverDelay as ae, verifyLockupAddress as af, verifyReceiveInvoice as ag, type OnchainHtlcParams as b, ARKADE_BTC as c, AddressMismatch as d, type ChainUtxo as e, LIGHTNING_RECEIVE_PAIR as f, LIGHTNING_SEND_PAIR as g, LOCKTIME_THRESHOLD as h, type LightningReceiveTreeParams as i, type LightningSendTreeParams as j, MIN_CLAIM_WINDOW_SECONDS as k, MIN_HEADROOM_SECONDS as l, ONCHAIN_BTC as m, ONCHAIN_CLAIM_MARGIN_SECONDS as n, ONCHAIN_DUST_SATS as o, ONCHAIN_ORDER_MARGIN_SECONDS as p, ONCHAIN_RECEIVE_PAIR as q, ONCHAIN_SECONDS_PER_BLOCK as r, ONCHAIN_SEND_PAIR as s, type OnchainHtlcPhase as t, type RelaySocket as u, type RfqQuote as v, type RfqRefusalReason as w, type RfqStatus as x, type RfqTransport as y, SwapRefusal as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/swap",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "Client-side Arkade Intents asset swaps: discover markets, quote, create/track/cancel offers, restore from chain.",
6
6
  "repository": {
@@ -65,11 +65,12 @@
65
65
  "license": "MIT",
66
66
  "dependencies": {
67
67
  "@arkade-os/solver-discovery": "0.2.3",
68
+ "@noble/ciphers": "2.1.1",
68
69
  "@noble/curves": "2.0.1",
69
70
  "@noble/hashes": "2.0.1",
70
71
  "@scure/base": "2.0.0",
71
72
  "@scure/btc-signer": "2.0.1",
72
- "@arkade-os/sdk": "0.4.65"
73
+ "@arkade-os/sdk": "0.4.67"
73
74
  },
74
75
  "peerDependencies": {
75
76
  "nostr-tools": "^2.12.0"
@@ -90,8 +91,8 @@
90
91
  "scripts": {
91
92
  "build": "tsup src/index.ts src/nostr.ts src/repositories/sqlite/index.ts src/repositories/realm/index.ts --format esm,cjs --dts --clean",
92
93
  "typecheck": "tsc --noEmit",
93
- "format": "prettier --write src test",
94
- "lint": "prettier --check src test",
94
+ "format": "biome format --write src test",
95
+ "lint": "biome format src test",
95
96
  "test": "vitest run --exclude test/e2e",
96
97
  "test:unit": "vitest run --exclude test/e2e",
97
98
  "test:integration": "vitest run test/e2e/** --exclude test/e2e/rfq*.test.ts",