@arkade-os/swap 0.0.12 → 0.1.0-rc.1

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
@@ -12,6 +12,33 @@ React Native does not, so install `react-native-get-random-values` (or `expo-cry
12
12
  it before this package. `crypto.subtle` is not used. `EventSource` and `WebSocket` are needed only
13
13
  by the watch and relay transports, both of which take an injected implementation.
14
14
 
15
+ The v2 swap client API is tracked in [V2_API.md](./V2_API.md). That document is
16
+ the package-level developer UX note for the new client surface as it lands; the
17
+ current README still documents the existing package exports and protocol
18
+ building blocks.
19
+
20
+ **The covenant-deriving entry points need a wallet and nothing else.** `createOffer`,
21
+ `requestLightningSend`, `requestLightningReceive`, `requestOnchainSend` and
22
+ `requestOnchainReceive` take their server facts from `wallet.getArkadeInfo()`: the network and
23
+ signer key, plus the unilateral-exit delay for the four `request*` calls. The wallet is the single
24
+ place that knows which server it speaks to, so there is no URL to thread through and no second
25
+ `/v1/info` round-trip *per call*. Each entrypoint still performs its own live read (deliberately —
26
+ covenant derivation requires live info and fails closed offline); a session creating many offers
27
+ pays one read per offer until the SDK grows a `CachingClientTransport`-style memo (the NArk
28
+ reference's answer), noted as follow-up on `ArkadeInfo`.
29
+
30
+ No offer entrypoint here takes a server URL. `cancelOffer` and `watchOfferSwaps` need more than
31
+ server info — cancel broadcasts the refund and falls back to the indexer for a deposit made
32
+ before contract registration existed, and the watcher reads spending transactions — so they
33
+ ask the wallet for those too: `wallet.getArkadeReader()` for chain reads and
34
+ `wallet.getArkadeBroadcaster()` for `submitTx`/`finalizeTx`. On a service-worker wallet both
35
+ are proxied to the worker, so these reads stay on the wallet's own connection.
36
+ The RFQ restore/refund/claim helpers still take provider instances. An `ArkadeReader`
37
+ satisfies their *indexer* parameter structurally — `restoreAssetSwaps` can be fed
38
+ `await wallet.getArkadeReader()` today — while `arkadeRefunder` and `claim`/`refund` also
39
+ want an ark provider (`getInfo` plus the broadcast pair), buildable from
40
+ `wallet.getArkadeInfo()` and `wallet.getArkadeBroadcaster()`.
41
+
15
42
  ## Roles
16
43
 
17
44
  Arkade Intents names two participants:
@@ -76,7 +103,7 @@ funds an offer should keep cancelling within reach.
76
103
 
77
104
  1. **`offer`** — the swap covenant itself. Two program JSONs (want-BTC / want-asset), the
78
105
  `Offer` type, the TLV wire codec (`encodeOffer`/`decodeOffer`, `OFFER_PACKET_TYPE`), address
79
- derivation (`offerVtxoScript`), and the user-side operations `createOffer`/`cancelOffer`. Identical
106
+ derivation (`offerContract`), and the user-side operations `createOffer`/`cancelOffer`. Identical
80
107
  offers always derive identical swap addresses — the program JSONs are hashed into the address,
81
108
  so their bytes are frozen (guarded by a golden test).
82
109
  2. **`markets`** — solver discovery and pricing guardrails: `discoverMarkets` (1-hour cached
@@ -117,9 +144,25 @@ uncached discovery.
117
144
  | `IndexedDbAssetSwapRepository` | `@arkade-os/swap` | the browser (or a polyfilled IndexedDB) |
118
145
  | `SQLiteAssetSwapRepository` | `@arkade-os/swap/repositories/sqlite` | React Native, over your SQLite driver |
119
146
  | `RealmAssetSwapRepository` | `@arkade-os/swap/repositories/realm` | React Native, over your Realm instance |
147
+ | `nodeSwapRepository()` | `@arkade-os/swap/node` | Node — file-backed SQLite, opened for you |
148
+
149
+ Neither React Native subpath adds a dependency: they take the SDK's structural `SQLExecutor` /
150
+ `RealmLike` handles, so you pass the database you already opened.
151
+
152
+ `@arkade-os/swap/node` is the exception, and the only entry point that imports `node:` builtins —
153
+ which is why it is a separate subpath rather than something the main entry falls back to. It opens
154
+ the database itself, under the platform config directory (XDG / `~/Library/Application Support` /
155
+ `%APPDATA%`) at `arkade/swaps/swaps-<network>.sqlite`, and it is the one backend whose disposal
156
+ closes a connection:
157
+
158
+ ```ts
159
+ import { nodeSwapRepository } from "@arkade-os/swap/node";
160
+
161
+ await using swaps = nodeSwapRepository({ network: "mainnet" }); // or { path } to choose the file
162
+ ```
120
163
 
121
- Neither subpath adds a dependency: they take the SDK's structural `SQLExecutor` / `RealmLike`
122
- handles, so you pass the database you already opened.
164
+ Every other backend's `[Symbol.asyncDispose]` is a no-op, because you opened the handle and it is
165
+ yours to close. This one opened it, so it closes it.
123
166
 
124
167
  All four carry both record types: asset swaps and the monitored RFQ swaps
125
168
  (`saveRfqSwap` / `getRfqSwap` / `getAllRfqSwaps` / `removeRfqSwap`). Each keeps them in a store of their own — a
@@ -128,8 +171,8 @@ Realm — since the two record types have different keys and no consumer wants t
128
171
 
129
172
  **Records are stored whole.** The SQLite and Realm backends serialize each record to **JSON** in a
130
173
  `data` column, with only `status` / `createdAt` (and an RFQ record's `state` / `updatedAt`) mapped
131
- out for querying — so a field they do not know about survives, which is what the `quote`-shaped
132
- extension in `MIGRATION.md` relies on. It is also what keeps an RFQ record's corridor `profile`
174
+ out for querying — so a field they do not know about survives, which is what a consumer's
175
+ cast-extended record relies on. It is also what keeps an RFQ record's corridor `profile`
133
176
  intact: `profile.hashlock` is a nested object holding the payment hash and any preimage material, and
134
177
  a field-mapped backend is exactly what would lose it. JSON is
135
178
  the boundary, though, and it is narrower than IndexedDB's structured clone: a `Date` in a
@@ -183,15 +226,17 @@ const realm = await Realm.open({
183
226
  const swaps = new RealmAssetSwapRepository(realm);
184
227
  ```
185
228
 
186
- Four classes land in your Realm namespace: `ArkadeAssetSwap`, `ArkadeRfqSwap`,
229
+ Five classes land in your Realm namespace: `ArkadeAssetSwap`, `ArkadeRfqSwap`, `ArkadeSwapRecord`,
187
230
  `ArkadeAssetSwapScannedTxid`, `ArkadeAssetSwapMarketsCache`. Unlike SQLite there is no prefix option
188
231
  — a Realm schema name is baked into the schema objects you register — so reconcile against your own
189
232
  models by name.
190
233
 
191
- `ArkadeRfqSwap` arrived after the other three. **If you already shipped them, add it and bump
192
- `schemaVersion` again**: Realm creates schemas at open, so a config still listing three fails on the
193
- first RFQ read rather than at open. SQLite needs nothing its DDL runs `CREATE TABLE IF NOT EXISTS`
194
- on every init, so the table appears on the next operation.
234
+ `ArkadeRfqSwap` and `ArkadeSwapRecord` arrived after the first three. **If you already shipped an
235
+ earlier set, add the new ones and bump `schemaVersion` again**: Realm creates schemas at open, so a
236
+ config listing fewer fails on the first read of the missing one rather than at open. Spreading
237
+ `AssetSwapRealmSchemas` rather than listing names by hand is what keeps that from happening again.
238
+ SQLite needs nothing — its DDL runs `CREATE TABLE IF NOT EXISTS` on every init, so a new table
239
+ appears on the next operation.
195
240
 
196
241
  ## Creating an offer
197
242
 
@@ -200,11 +245,11 @@ the rest:
200
245
 
201
246
  ```ts
202
247
  // BTC -> asset
203
- const o = await createOffer(wallet, ARK, { wantAmount: 1000n, wantAsset });
248
+ const o = await createOffer(wallet, { wantAmount: 1000n, wantAsset });
204
249
  await wallet.send({ address: o.address, amount: 1000, extensions: [o.extension] });
205
250
 
206
251
  // asset -> BTC (the sats are the VTXO carrier for the asset)
207
- const o = await createOffer(wallet, ARK, { wantAmount: 1000n, offerAsset });
252
+ const o = await createOffer(wallet, { wantAmount: 1000n, offerAsset });
208
253
  await wallet.send({
209
254
  address: o.address,
210
255
  amount: 500,
@@ -238,7 +283,7 @@ offer bytes themselves are recoverable from the funding tx if the record is lost
238
283
  ## Live status
239
284
 
240
285
  ```ts
241
- const watcher = await watchOfferSwaps({ wallet, arkServerUrl: ARK, repository, onUpdate: render });
286
+ const watcher = await watchOfferSwaps({ wallet, repository, onUpdate: render });
242
287
  // later
243
288
  watcher.stop();
244
289
  ```
@@ -260,7 +305,7 @@ repository.
260
305
  ## Cancelling: the refund path
261
306
 
262
307
  ```ts
263
- const txid = await cancelOffer(wallet, ARK, swap.offerHex, {
308
+ const txid = await cancelOffer(wallet, swap.offerHex, {
264
309
  repository,
265
310
  fundingTxid: swap.fundingTxid,
266
311
  swapAddress: swap.swapAddress,
@@ -322,7 +367,7 @@ message anywhere: **acceptance is funding**.
322
367
  import { httpTransport, requestLightningSend } from "@arkade-os/swap";
323
368
 
324
369
  // invoice facts from YOUR OWN decoder — the module takes facts, not a decoder
325
- const swap = await requestLightningSend(wallet, arkServerUrl, httpTransport(solverUrl), {
370
+ const swap = await requestLightningSend(wallet, httpTransport(solverUrl), {
326
371
  invoice: { raw: bolt11, paymentHash, amountSats, expiresAt },
327
372
  });
328
373
  // quote verified against the LOCAL derivation and gated; now fund and go offline:
@@ -395,7 +440,7 @@ import {
395
440
  swapSecretsToRecord,
396
441
  } from "@arkade-os/swap";
397
442
 
398
- const swap = await requestOnchainSend(wallet, arkServerUrl, httpTransport(solverUrl), {
443
+ const swap = await requestOnchainSend(wallet, httpTransport(solverUrl), {
399
444
  amount: 100_000,
400
445
  amountSide: "to",
401
446
  payoutPubkey,
@@ -829,7 +874,7 @@ Notes from before 0.0.1, kept for consumers who tracked the branch.
829
874
  silently overwritten.
830
875
  - **`readLockupFate` names the spends it observed.** `claimed` and `returned` now carry
831
876
  `spends: readonly LockupSpend[]`, one per spent lockup output, with the `checkpointTxid` that
832
- `spentBy` names and the `arkTxid` that rode it. History correlation wants `arkTxid`; the
877
+ `spentBy` names and the `txid` that rode it. History correlation wants `txid`; the
833
878
  checkpoint txid is the wrong value to correlate on alone. `unknown` and `open` claim no spend.
834
879
 
835
880
  - **Every derived address changed again, in both corridors — the unilateral ladder was re-spaced.**
@@ -901,8 +946,8 @@ stored?)`. The returned `ProvisionedKey` / `ProvisionedClaimSecret` replace `Swa
901
946
  quote at `verifyLockupAddress`. Upgrade both sides before expecting fills.
902
947
  - **`cancelOffer` and `restoreAssetSwaps` take an options object.** `cancelOffer(wallet, url,
903
948
  offerHex, { repository, fundingTxid?, swapAddress? })` — the repository is required because the
904
- call now records its own outcome. `restoreAssetSwaps(indexer, txs, existingIds, { serverPubkey,
905
- scanned? })` — the server key is required because a spend is classified by rebuilding the
949
+ call now records its own outcome. `restoreAssetSwaps(indexer, txs, existingIds, { operatorPubkey,
950
+ scanned? })` — the operator key is required because a spend is classified by rebuilding the
906
951
  covenant and matching the leaf it took.
907
952
  - **`isCancelSpend` is gone**, replaced by `classifySpend`, and `Tx.assets` with it. The old test
908
953
  read what a transaction moved, which a wallet reports as a _net_ delta: once the deposit is a
@@ -944,7 +989,7 @@ scanned? })` — the server key is required because a spend is classified by reb
944
989
  kind: "lightning_receive",
945
990
  lockupAddress: result.address,
946
991
  profile: {
947
- ...rfqSecretsProfile(result.secrets, result.treeParams.paymentHash),
992
+ ...rfqSecretsProfile(result.secrets, result.contractParams.paymentHash),
948
993
  expectedAmount: result.expectedAmount,
949
994
  payoutAddress: result.payoutAddress,
950
995
  },
@@ -1014,7 +1059,7 @@ scanned? })` — the server key is required because a spend is classified by reb
1014
1059
  when `persisted` is true — the callback is documented as following a persisted change, and a
1015
1060
  consumer caching from it must not run ahead of the store.
1016
1061
  - **`lightningSendProgram` and `htlcSendProgram` are gone** along with the program-artifact layer
1017
- they compiled. Derive scripts through `lightningSendVtxoScript` / `onchainHtlcScript`.
1062
+ they compiled. Derive scripts through `lightningSendContract` / `onchainHtlcScript`.
1018
1063
  - **The receive corridors are wired, and the wire shape settled.** `lightningReceiveRequest` is
1019
1064
  new; `onchainReceiveRequest`'s profile now matches the shipped solver schema (`payment_hash`,
1020
1065
  `claim_packet`, `refund_pubkey`, `payout_address`, `payout_pubkey` — the earlier
@@ -1025,7 +1070,7 @@ scanned? })` — the server key is required because a spend is classified by reb
1025
1070
  corridor's fee — and refuses quotes whose `to_amount` reprices the invoice; solvers charge
1026
1071
  per-corridor fees on all four pairs, and funding the bare invoice amount underfunds by exactly
1027
1072
  the fee.
1028
- - **`lightningSendVtxoScript` takes two new required fields**: `senderPubkey` (the trader's VHTLC
1073
+ - **`lightningSendContract` takes two new required fields**: `senderPubkey` (the trader's VHTLC
1029
1074
  sender key — generate, persist, see `requestLightningSend`) and `receiverPkScript` (the solver's
1030
1075
  claim destination, from `profile.receiver_pk_script`). Callers that built the lockup directly
1031
1076
  must supply both; callers going through `requestLightningSend` are unaffected.
@@ -9,6 +9,7 @@ var MAX_MIN_CONFIRMATIONS = 6;
9
9
  var LOCKTIME_THRESHOLD = 5e8;
10
10
  var ONCHAIN_SECONDS_PER_BLOCK = 600;
11
11
  var ONCHAIN_DUST_SATS = BigInt(330);
12
+ var ONCHAIN_CLAIM_VSIZE = 152;
12
13
  var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
13
14
  var paymentHashOf = (preimage) => hex.encode(sha256(preimage));
14
15
  var h160FromPaymentHash = (paymentHash) => ripemd160(hex.decode(paymentHash));
@@ -314,9 +315,8 @@ import { hex as hex3 } from "@scure/base";
314
315
  import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
315
316
  import {
316
317
  ArkAddress as ArkAddress2,
317
- RestArkProvider,
318
318
  VHTLC,
319
- getNetwork,
319
+ networkFromArkadeInfo,
320
320
  resolveEmulatorPubkey,
321
321
  toXOnly
322
322
  } from "@arkade-os/sdk";
@@ -343,11 +343,14 @@ var ONCHAIN_SEND_PAIR = rfqPair(ARKADE_BTC, ONCHAIN_BTC);
343
343
  var ONCHAIN_RECEIVE_PAIR = rfqPair(ONCHAIN_BTC, ARKADE_BTC);
344
344
  var RFQ_TERMINAL_STATES = ["settled", "refused", "expired", "refunded", "stuck"];
345
345
  var SwapRefusal = class extends Error {
346
+ /** Literal-typed so the v2 error taxonomy's union discriminates on `name`
347
+ * — a `string` here collapses the discriminant for every member. Same value
348
+ * the constructor has always set, moved to a field initializer. */
349
+ name = "SwapRefusal";
346
350
  reason;
347
351
  rfqId;
348
352
  constructor(reason, rfqId) {
349
353
  super(`solver refused: ${reason}`);
350
- this.name = "SwapRefusal";
351
354
  this.reason = reason;
352
355
  this.rfqId = rfqId;
353
356
  }
@@ -448,6 +451,10 @@ var assertFundable = (input) => {
448
451
  const fail = (reason, message) => {
449
452
  throw gateError(reason, message);
450
453
  };
454
+ if (input.quote.valid_until === void 0) {
455
+ fail("quote_malformed", "quote carries no valid_until");
456
+ }
457
+ assertFinite(input.quote.valid_until, "quote_malformed", "quote valid_until");
451
458
  if (input.invoiceExpiresAt !== void 0 && input.now >= input.invoiceExpiresAt) {
452
459
  fail("invoice_expired", "invoice expired");
453
460
  }
@@ -650,22 +657,22 @@ var relayTransport = (relayUrl, options) => {
650
657
  };
651
658
  var SEQUENCE_GRANULARITY_SECONDS = 512;
652
659
  var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
653
- var unilateralClaimDelay = (serverExitDelaySeconds) => {
654
- if (!Number.isFinite(serverExitDelaySeconds) || serverExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
660
+ var unilateralClaimDelay = (operatorExitDelaySeconds) => {
661
+ if (!Number.isFinite(operatorExitDelaySeconds) || operatorExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
655
662
  throw new Error(
656
- `server exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${serverExitDelaySeconds}`
663
+ `operator exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${operatorExitDelaySeconds}`
657
664
  );
658
665
  }
659
- if (serverExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
666
+ if (operatorExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
660
667
  throw new Error(
661
- `server exit delay ${serverExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
668
+ `operator exit delay ${operatorExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
662
669
  );
663
670
  }
664
- return Math.ceil(serverExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
671
+ return Math.ceil(operatorExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
665
672
  };
666
673
  var unilateralRefundDelay = (claimDelay) => claimDelay;
667
674
  var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay + SOLO_REFUND_HEADROOM_SECONDS;
668
- function lightningSendVtxoScript(params) {
675
+ function lightningSendContract(params) {
669
676
  const seconds = (value) => ({
670
677
  type: "seconds",
671
678
  value: BigInt(value)
@@ -673,7 +680,7 @@ function lightningSendVtxoScript(params) {
673
680
  return new VHTLC.ScriptV2({
674
681
  sender: params.senderPubkey,
675
682
  receiver: params.solverPubkey,
676
- server: params.serverPubkey,
683
+ server: params.operatorPubkey,
677
684
  preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
678
685
  refundLocktime: BigInt(params.refundLocktime),
679
686
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -689,15 +696,8 @@ function lightningSendVtxoScript(params) {
689
696
  }
690
697
  });
691
698
  }
692
- async function requestLightningSend(wallet, arkServerUrl, transport, params) {
693
- const rfqId = params.rfqId ?? newRfqId();
694
- const secrets = await provisionRefundKey(wallet);
695
- const senderPubkey = secrets.pubkey;
696
- const refundAddress = secrets.address;
697
- const info = await new RestArkProvider(arkServerUrl).getInfo();
698
- const quote = await transport.requestQuote(
699
- lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
700
- );
699
+ function deriveLightningSend(input) {
700
+ const { quote } = input;
701
701
  if (quote.refund_locktime === void 0) {
702
702
  throw new Error("lightning-send quote is missing refund_locktime");
703
703
  }
@@ -705,6 +705,43 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
705
705
  if (receiverPkScriptHex === void 0) {
706
706
  throw new Error("lightning-send quote is missing profile.receiver_pk_script");
707
707
  }
708
+ const contractParams = {
709
+ solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
710
+ refundLocktime: quote.refund_locktime,
711
+ operatorPubkey: input.operatorPubkey,
712
+ paymentHash: input.paymentHash,
713
+ claimDelay: input.claimDelay,
714
+ emulatorPubkey: input.emulatorPubkey,
715
+ senderPubkey: input.senderPubkey,
716
+ receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
717
+ refundPkScript: input.refundPkScript
718
+ };
719
+ const matched = matchQuotedLockup(
720
+ quote,
721
+ input.hrp,
722
+ input.operatorPubkey,
723
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
724
+ );
725
+ return {
726
+ address: matched.address,
727
+ swapPkScript: matched.script.pkScript,
728
+ script: matched.script,
729
+ contractParams: {
730
+ ...contractParams,
731
+ ...matched.legacy !== void 0 && { legacy: matched.legacy }
732
+ },
733
+ refundLocktime: quote.refund_locktime
734
+ };
735
+ }
736
+ async function requestLightningSend(wallet, transport, params) {
737
+ const rfqId = params.rfqId ?? newRfqId();
738
+ const secrets = await provisionRefundKey(wallet);
739
+ const senderPubkey = secrets.pubkey;
740
+ const refundAddress = secrets.address;
741
+ const info = await wallet.getArkadeInfo({ requireLive: true });
742
+ const quote = await transport.requestQuote(
743
+ lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
744
+ );
708
745
  if (quote.to_amount !== params.invoice.amountSats) {
709
746
  throw new Error(
710
747
  `quote to_amount ${quote.to_amount} does not match the invoice's ${params.invoice.amountSats}`
@@ -715,34 +752,22 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
715
752
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
716
753
  );
717
754
  }
718
- const serverPubkey = toXOnly(hex3.decode(info.signerPubkey), "ark signer key");
719
- const network = getNetwork(info.network);
720
- const treeParams = {
721
- solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
722
- refundLocktime: quote.refund_locktime,
723
- serverPubkey,
755
+ const operatorPubkey = toXOnly(hex3.decode(info.signerPubkey), "ark signer key");
756
+ const network = networkFromArkadeInfo(info);
757
+ const derived = deriveLightningSend({
758
+ quote,
724
759
  paymentHash: params.invoice.paymentHash,
725
- claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
760
+ senderPubkey,
761
+ refundPkScript: secrets.pkScript,
762
+ operatorPubkey,
726
763
  emulatorPubkey: toXOnly(
727
764
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
728
765
  "emulator signer key"
729
766
  ),
730
- senderPubkey,
731
- receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
732
- refundPkScript: secrets.pkScript
733
- };
734
- const matched = matchQuotedLockup(
735
- quote,
736
- network.hrp,
737
- serverPubkey,
738
- (legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
739
- );
740
- const script = matched.script;
741
- const address = matched.address;
742
- const matchedTreeParams = {
743
- ...treeParams,
744
- ...matched.legacy !== void 0 && { legacy: matched.legacy }
745
- };
767
+ claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
768
+ hrp: network.hrp
769
+ });
770
+ const { address, script, contractParams } = derived;
746
771
  assertFundable({
747
772
  quote,
748
773
  invoiceExpiresAt: params.invoice.expiresAt,
@@ -756,12 +781,12 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
756
781
  // What the lockup must carry: the quote's `from_amount` — the invoice
757
782
  // PLUS the corridor's fee, never the bare invoice amount.
758
783
  fundAmount: quote.from_amount,
759
- swapPkScript: script.pkScript,
784
+ swapPkScript: derived.swapPkScript,
760
785
  script,
761
786
  refundAddress,
762
787
  senderPubkey,
763
788
  secrets,
764
- treeParams: matchedTreeParams
789
+ contractParams
765
790
  };
766
791
  }
767
792
  var offerTermsFromQuote = (quote, assets) => {
@@ -826,10 +851,10 @@ function deriveOnchainSend(input) {
826
851
  if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
827
852
  throw new Error("onchain-send quote is missing a binding field");
828
853
  }
829
- const treeParams = {
854
+ const contractParams = {
830
855
  solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
831
856
  refundLocktime,
832
- serverPubkey: input.serverPubkey,
857
+ operatorPubkey: input.operatorPubkey,
833
858
  paymentHash: input.paymentHash,
834
859
  claimDelay: input.claimDelay,
835
860
  emulatorPubkey: input.emulatorPubkey,
@@ -840,8 +865,8 @@ function deriveOnchainSend(input) {
840
865
  const { script, address } = matchQuotedLockup(
841
866
  quote,
842
867
  input.hrp,
843
- input.serverPubkey,
844
- (legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
868
+ input.operatorPubkey,
869
+ (legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
845
870
  );
846
871
  const htlcParams = {
847
872
  paymentHash: input.paymentHash,
@@ -863,7 +888,7 @@ function deriveOnchainSend(input) {
863
888
  minConfirmations
864
889
  };
865
890
  }
866
- async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
891
+ async function requestOnchainSend(wallet, transport, params) {
867
892
  const rfqId = params.rfqId ?? newRfqId();
868
893
  const secrets = await provisionClaimSecret(wallet, { preimage: params.preimage });
869
894
  if (secrets.mustPersistPreimage) {
@@ -874,7 +899,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
874
899
  const paymentHash = hex3.encode(secrets.paymentHash);
875
900
  const senderPubkey = secrets.pubkey;
876
901
  const [info, refundAddress] = await Promise.all([
877
- new RestArkProvider(arkServerUrl).getInfo(),
902
+ wallet.getArkadeInfo({ requireLive: true }),
878
903
  wallet.getAddress()
879
904
  ]);
880
905
  const quote = await transport.requestQuote(
@@ -889,12 +914,12 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
889
914
  })
890
915
  );
891
916
  assertQuotedAmount(quote, params.amountSide, params.amount);
892
- const network = getNetwork(info.network);
917
+ const network = networkFromArkadeInfo(info);
893
918
  const derived = deriveOnchainSend({
894
919
  quote,
895
920
  paymentHash,
896
921
  payoutPubkey: params.payoutPubkey,
897
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
922
+ operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
898
923
  emulatorPubkey: toXOnly(
899
924
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
900
925
  "emulator signer key"
@@ -931,6 +956,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
931
956
  htlcParams: derived.htlcParams,
932
957
  l1Network: derived.l1Network,
933
958
  minConfirmations: derived.minConfirmations,
959
+ refundLocktime: derived.refundLocktime,
934
960
  senderPubkey,
935
961
  secrets
936
962
  };
@@ -1002,7 +1028,7 @@ var assertReceivable = (input) => {
1002
1028
  );
1003
1029
  }
1004
1030
  };
1005
- function receiveVtxoScript(params) {
1031
+ function lightningReceiveContract(params) {
1006
1032
  const seconds = (value) => ({
1007
1033
  type: "seconds",
1008
1034
  value: BigInt(value)
@@ -1010,7 +1036,7 @@ function receiveVtxoScript(params) {
1010
1036
  return new VHTLC.ScriptV2({
1011
1037
  sender: params.solverPubkey,
1012
1038
  receiver: params.payoutPubkey,
1013
- server: params.serverPubkey,
1039
+ server: params.operatorPubkey,
1014
1040
  preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
1015
1041
  refundLocktime: BigInt(params.refundLocktime),
1016
1042
  unilateralClaimDelay: seconds(params.claimDelay),
@@ -1035,10 +1061,10 @@ function deriveLightningReceive(input) {
1035
1061
  if (refundLocktime === void 0 || invoice === void 0 || solverRefundPkScriptHex === void 0) {
1036
1062
  throw new Error("lightning-receive quote is missing a binding field");
1037
1063
  }
1038
- const treeParams = {
1064
+ const contractParams = {
1039
1065
  solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1040
1066
  refundLocktime,
1041
- serverPubkey: input.serverPubkey,
1067
+ operatorPubkey: input.operatorPubkey,
1042
1068
  paymentHash: input.paymentHash,
1043
1069
  claimDelay: input.claimDelay,
1044
1070
  emulatorPubkey: input.emulatorPubkey,
@@ -1049,8 +1075,8 @@ function deriveLightningReceive(input) {
1049
1075
  const matched = matchQuotedLockup(
1050
1076
  quote,
1051
1077
  input.hrp,
1052
- input.serverPubkey,
1053
- (legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
1078
+ input.operatorPubkey,
1079
+ (legacy) => lightningReceiveContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
1054
1080
  );
1055
1081
  return {
1056
1082
  address: matched.address,
@@ -1058,13 +1084,13 @@ function deriveLightningReceive(input) {
1058
1084
  script: matched.script,
1059
1085
  invoice,
1060
1086
  refundLocktime,
1061
- treeParams: {
1062
- ...treeParams,
1087
+ contractParams: {
1088
+ ...contractParams,
1063
1089
  ...matched.legacy !== void 0 && { legacy: matched.legacy }
1064
1090
  }
1065
1091
  };
1066
1092
  }
1067
- async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
1093
+ async function requestLightningReceive(wallet, transport, params) {
1068
1094
  const rfqId = params.rfqId ?? newRfqId();
1069
1095
  const secrets = await provisionClaimSecret(wallet);
1070
1096
  if (secrets.mustPersistPreimage) {
@@ -1076,7 +1102,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1076
1102
  const paymentHash = hex3.encode(secrets.paymentHash);
1077
1103
  const payoutPubkey = secrets.pubkey;
1078
1104
  const [info, payoutAddress] = await Promise.all([
1079
- new RestArkProvider(arkServerUrl).getInfo(),
1105
+ wallet.getArkadeInfo({ requireLive: true }),
1080
1106
  wallet.getAddress()
1081
1107
  ]);
1082
1108
  const claimPacket = await sealClaimPacket({
@@ -1095,13 +1121,13 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1095
1121
  })
1096
1122
  );
1097
1123
  assertQuotedAmount(quote, params.amountSide, params.amount);
1098
- const network = getNetwork(info.network);
1124
+ const network = networkFromArkadeInfo(info);
1099
1125
  const derived = deriveLightningReceive({
1100
1126
  quote,
1101
1127
  paymentHash,
1102
1128
  payoutPubkey,
1103
1129
  payoutAddress,
1104
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1130
+ operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1105
1131
  emulatorPubkey: toXOnly(
1106
1132
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1107
1133
  "emulator signer key"
@@ -1135,7 +1161,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1135
1161
  payoutAddress,
1136
1162
  payoutPubkey,
1137
1163
  secrets,
1138
- treeParams: derived.treeParams
1164
+ contractParams: derived.contractParams
1139
1165
  };
1140
1166
  }
1141
1167
  function deriveOnchainReceive(input) {
@@ -1150,10 +1176,10 @@ function deriveOnchainReceive(input) {
1150
1176
  if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
1151
1177
  throw new Error("onchain-receive quote is missing a binding field");
1152
1178
  }
1153
- const treeParams = {
1179
+ const contractParams = {
1154
1180
  solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1155
1181
  refundLocktime,
1156
- serverPubkey: input.serverPubkey,
1182
+ operatorPubkey: input.operatorPubkey,
1157
1183
  paymentHash: input.paymentHash,
1158
1184
  claimDelay: input.claimDelay,
1159
1185
  emulatorPubkey: input.emulatorPubkey,
@@ -1164,8 +1190,11 @@ function deriveOnchainReceive(input) {
1164
1190
  const { script, address } = matchQuotedLockup(
1165
1191
  quote,
1166
1192
  input.hrp,
1167
- input.serverPubkey,
1168
- (legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
1193
+ input.operatorPubkey,
1194
+ (legacy) => lightningReceiveContract({
1195
+ ...contractParams,
1196
+ ...legacy !== void 0 && { legacy }
1197
+ })
1169
1198
  );
1170
1199
  const htlc = onchainHtlcScript(
1171
1200
  {
@@ -1187,7 +1216,7 @@ function deriveOnchainReceive(input) {
1187
1216
  minConfirmations
1188
1217
  };
1189
1218
  }
1190
- async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1219
+ async function requestOnchainReceive(wallet, transport, params) {
1191
1220
  const rfqId = params.rfqId ?? newRfqId();
1192
1221
  const secrets = await provisionClaimSecret(wallet);
1193
1222
  if (secrets.mustPersistPreimage) {
@@ -1199,7 +1228,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1199
1228
  const paymentHash = hex3.encode(secrets.paymentHash);
1200
1229
  const payoutPubkey = secrets.pubkey;
1201
1230
  const [info, payoutAddress] = await Promise.all([
1202
- new RestArkProvider(arkServerUrl).getInfo(),
1231
+ wallet.getArkadeInfo({ requireLive: true }),
1203
1232
  wallet.getAddress()
1204
1233
  ]);
1205
1234
  const claimPacket = await sealClaimPacket({
@@ -1219,14 +1248,14 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1219
1248
  })
1220
1249
  );
1221
1250
  assertQuotedAmount(quote, params.amountSide, params.amount);
1222
- const network = getNetwork(info.network);
1251
+ const network = networkFromArkadeInfo(info);
1223
1252
  const derived = deriveOnchainReceive({
1224
1253
  quote,
1225
1254
  paymentHash,
1226
1255
  payoutPubkey,
1227
1256
  payoutAddress,
1228
1257
  refundPubkey: params.refundPubkey,
1229
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1258
+ operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1230
1259
  emulatorPubkey: toXOnly(
1231
1260
  hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1232
1261
  "emulator signer key"
@@ -1271,6 +1300,7 @@ export {
1271
1300
  LOCKTIME_THRESHOLD,
1272
1301
  ONCHAIN_SECONDS_PER_BLOCK,
1273
1302
  ONCHAIN_DUST_SATS,
1303
+ ONCHAIN_CLAIM_VSIZE,
1274
1304
  newPreimage,
1275
1305
  paymentHashOf,
1276
1306
  L1_NETWORKS,
@@ -1317,9 +1347,10 @@ export {
1317
1347
  unilateralClaimDelay,
1318
1348
  unilateralRefundDelay,
1319
1349
  unilateralRefundWithoutReceiverDelay,
1320
- lightningSendVtxoScript,
1350
+ lightningSendContract,
1321
1351
  requestLightningSend,
1322
1352
  offerTermsFromQuote,
1353
+ l1NetworkFromArk,
1323
1354
  onchainSendRequest,
1324
1355
  lightningReceiveRequest,
1325
1356
  onchainReceiveRequest,
@@ -1328,7 +1359,7 @@ export {
1328
1359
  MIN_CLAIM_WINDOW_SECONDS,
1329
1360
  verifyReceiveInvoice,
1330
1361
  assertReceivable,
1331
- receiveVtxoScript,
1362
+ lightningReceiveContract,
1332
1363
  deriveLightningReceive,
1333
1364
  requestLightningReceive,
1334
1365
  deriveOnchainReceive,