@arkade-os/swap 0.0.18 → 0.0.20

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.
@@ -1,6 +1,775 @@
1
- // src/onchainHtlc.ts
1
+ // src/coverage.ts
2
+ var RETIRABLE = ["fulfilled", "cancelled"];
3
+ var issuedAt = /* @__PURE__ */ new Map();
4
+ var inFlight = /* @__PURE__ */ new Map();
5
+ async function serialize(script, task) {
6
+ const previous = inFlight.get(script) ?? Promise.resolve();
7
+ const result = previous.then(task, task);
8
+ const settled = result.then(
9
+ () => {
10
+ },
11
+ () => {
12
+ }
13
+ );
14
+ inFlight.set(script, settled);
15
+ void settled.then(() => {
16
+ if (inFlight.get(script) === settled) inFlight.delete(script);
17
+ });
18
+ return result;
19
+ }
20
+ function addressOutstanding(swaps, script) {
21
+ const issued = issuedAt.get(script);
22
+ if (issued === void 0) return false;
23
+ if (swaps.some((s) => s.swapPkScript === script && s.createdAt >= issued)) {
24
+ issuedAt.delete(script);
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+ async function promoteOfferContract(manager, script, issued = Date.now()) {
30
+ await serialize(script, async () => {
31
+ await manager.setContractWatchState(script, "watched");
32
+ issuedAt.set(script, issued);
33
+ });
34
+ }
35
+ async function retireOfferContract(manager, swaps, script) {
36
+ await serialize(script, async () => {
37
+ if (swaps.some((s) => s.swapPkScript === script && !RETIRABLE.includes(s.status))) return;
38
+ if (addressOutstanding(swaps, script)) return;
39
+ try {
40
+ await manager.setContractWatchState(script, "retained");
41
+ } catch (err) {
42
+ console.warn(`[swap] could not retire offer contract ${script}`, err);
43
+ }
44
+ });
45
+ }
46
+ async function retireSettledOfferContracts(manager, swaps) {
47
+ const settled = new Set(
48
+ swaps.filter((s) => RETIRABLE.includes(s.status)).map((s) => s.swapPkScript)
49
+ );
50
+ for (const script of settled) await retireOfferContract(manager, swaps, script);
51
+ }
52
+
53
+ // src/store.ts
2
54
  import { hex } from "@scure/base";
3
55
  import { sha256 } from "@noble/hashes/sha2.js";
56
+ import { contractPreimage } from "@arkade-os/sdk";
57
+ var BTC_ASSET_ID = "btc";
58
+ var byNewest = (a, b) => b.createdAt - a.createdAt;
59
+ var getAssetSwapsOrThrow = async (repository) => {
60
+ return (await repository.getAllSwaps()).filter(
61
+ (s) => s && typeof s.id === "string" && // offer swaps carry the TLV; onchain-corridor swaps carry
62
+ // the payment hash instead — either marks a valid record
63
+ (typeof s.offerHex === "string" || typeof s.paymentHash === "string")
64
+ ).sort(byNewest);
65
+ };
66
+ var getAssetSwaps = async (repository) => {
67
+ try {
68
+ return await getAssetSwapsOrThrow(repository);
69
+ } catch {
70
+ return [];
71
+ }
72
+ };
73
+ var saveSwapOrThrow = async (repository, swap) => {
74
+ try {
75
+ await repository.saveSwap(swap);
76
+ } catch (error) {
77
+ const reason = error instanceof Error ? error.message : String(error);
78
+ throw new Error(`failed to save swap ${swap.id}: ${reason}`);
79
+ }
80
+ };
81
+ var addAssetSwap = async (repository, swap) => {
82
+ const swaps = await getAssetSwapsOrThrow(repository);
83
+ if (swaps.some((s) => s.id === swap.id)) return swaps;
84
+ await saveSwapOrThrow(repository, swap);
85
+ const at = swaps.findIndex((s) => byNewest(swap, s) <= 0);
86
+ const merged = [...swaps];
87
+ merged.splice(at === -1 ? merged.length : at, 0, swap);
88
+ return merged;
89
+ };
90
+ var updateAssetSwap = async (repository, id, changes) => {
91
+ const swaps = (await getAssetSwapsOrThrow(repository)).map(
92
+ (s) => s.id === id ? { ...s, ...changes } : s
93
+ );
94
+ const updated = swaps.find((s) => s.id === id);
95
+ if (updated) await saveSwapOrThrow(repository, updated);
96
+ return swaps;
97
+ };
98
+ var updateAssetSwapBestEffort = async (repository, id, changes) => {
99
+ try {
100
+ return { swaps: await updateAssetSwap(repository, id, changes), persisted: true };
101
+ } catch (error) {
102
+ console.warn(`[swap] failed to persist update for swap ${id}`, error);
103
+ const swaps = (await getAssetSwaps(repository)).map(
104
+ (s) => s.id === id ? { ...s, ...changes } : s
105
+ );
106
+ return { swaps, persisted: false };
107
+ }
108
+ };
109
+ var swapSecretsToRecord = (secrets) => ({
110
+ signingDescriptor: secrets.descriptor,
111
+ ..."mustPersistPreimage" in secrets && secrets.mustPersistPreimage ? { preimageHex: hex.encode(secrets.preimage) } : {},
112
+ ..."preimageSalt" in secrets && secrets.preimageSalt ? { preimageSaltHex: hex.encode(secrets.preimageSalt) } : {}
113
+ });
114
+ var decodeHex32 = (value, field) => {
115
+ const bytes = hex.decode(value);
116
+ if (bytes.length !== 32) {
117
+ throw new Error(`${field} must be 32 bytes, got ${bytes.length}`);
118
+ }
119
+ return bytes;
120
+ };
121
+ var PreimageNotRecoverableError = class extends Error {
122
+ constructor(reason, message, options) {
123
+ super(message, options);
124
+ this.reason = reason;
125
+ }
126
+ reason;
127
+ name = "PreimageNotRecoverableError";
128
+ };
129
+ var preimageForSwapRecord = async (wallet, record) => {
130
+ if (!record.signingDescriptor) {
131
+ throw new PreimageNotRecoverableError(
132
+ "no-secrets",
133
+ "this swap record carries no signing descriptor"
134
+ );
135
+ }
136
+ let stored;
137
+ let salt;
138
+ try {
139
+ stored = record.preimageHex ? decodeHex32(record.preimageHex, "preimageHex") : void 0;
140
+ salt = record.preimageSaltHex ? decodeHex32(record.preimageSaltHex, "preimageSaltHex") : void 0;
141
+ } catch (cause) {
142
+ throw new PreimageNotRecoverableError(
143
+ "malformed-record",
144
+ `this swap record's secrets projection is unreadable: ${String(cause)}`,
145
+ { cause }
146
+ );
147
+ }
148
+ let preimage;
149
+ try {
150
+ preimage = await contractPreimage(wallet, record.signingDescriptor, { stored, salt });
151
+ } catch (cause) {
152
+ throw new PreimageNotRecoverableError(
153
+ "not-derivable",
154
+ `this wallet cannot produce the preimage for ${record.signingDescriptor}`,
155
+ { cause }
156
+ );
157
+ }
158
+ if (record.paymentHash && hex.encode(sha256(preimage)) !== record.paymentHash.toLowerCase()) {
159
+ throw new PreimageNotRecoverableError(
160
+ "hash-mismatch",
161
+ "the derived preimage does not match this swap's payment hash: wrong wallet, or a tampered salt"
162
+ );
163
+ }
164
+ return preimage;
165
+ };
166
+
167
+ // src/offer.ts
168
+ import { hex as hex2 } from "@scure/base";
169
+ import { concatBytes } from "@scure/btc-signer/utils.js";
170
+ import {
171
+ ASSET_CARRIER_SATS as SDK_ASSET_CARRIER_SATS,
172
+ ArkAddress,
173
+ RestArkProvider,
174
+ RestEmulatorProvider,
175
+ RestIndexerProvider,
176
+ arkade,
177
+ asset,
178
+ getNetwork,
179
+ resolveEmulatorPubkey,
180
+ toXOnlySignerHex
181
+ } from "@arkade-os/sdk";
182
+
183
+ // src/swap-want-asset.program.json
184
+ var swap_want_asset_program_default = {
185
+ version: 0,
186
+ name: "banco-btc-to-asset",
187
+ params: [
188
+ { name: "makerWP", type: "pubkey" },
189
+ { name: "wantAmount", type: "int" },
190
+ { name: "wantAssetTxid", type: "hash" },
191
+ { name: "wantAssetGroupIndex", type: "int" },
192
+ { name: "server", type: "pubkey" },
193
+ { name: "user", type: "pubkey" }
194
+ ],
195
+ functions: {
196
+ fulfill: {
197
+ tapscript: { signers: ["$server"] },
198
+ arkadeScript: {
199
+ asm: [
200
+ 0,
201
+ "$wantAssetTxid",
202
+ "$wantAssetGroupIndex",
203
+ "INSPECTOUTASSETLOOKUP",
204
+ "VERIFY",
205
+ "$wantAmount",
206
+ "GREATERTHANOREQUAL",
207
+ "VERIFY",
208
+ 0,
209
+ "INSPECTOUTPUTSCRIPTPUBKEY",
210
+ 1,
211
+ "EQUALVERIFY",
212
+ "$makerWP",
213
+ "EQUAL"
214
+ ]
215
+ }
216
+ },
217
+ cancel: {
218
+ tapscript: { signers: ["$user", "$server"] }
219
+ }
220
+ }
221
+ };
222
+
223
+ // src/swap-want-btc.program.json
224
+ var swap_want_btc_program_default = {
225
+ version: 0,
226
+ name: "banco-asset-to-btc",
227
+ params: [
228
+ { name: "makerWP", type: "pubkey" },
229
+ { name: "wantAmount", type: "int" },
230
+ { name: "server", type: "pubkey" },
231
+ { name: "user", type: "pubkey" }
232
+ ],
233
+ functions: {
234
+ fulfill: {
235
+ tapscript: { signers: ["$server"] },
236
+ arkadeScript: {
237
+ asm: [
238
+ 0,
239
+ "INSPECTOUTPUTVALUE",
240
+ "$wantAmount",
241
+ "GREATERTHANOREQUAL",
242
+ "VERIFY",
243
+ 0,
244
+ "INSPECTOUTPUTSCRIPTPUBKEY",
245
+ 1,
246
+ "EQUALVERIFY",
247
+ "$makerWP",
248
+ "EQUAL"
249
+ ]
250
+ }
251
+ },
252
+ cancel: {
253
+ tapscript: { signers: ["$user", "$server"] }
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/offer.ts
259
+ var swapPrograms = {
260
+ wantAsset: arkade.parseArtifact(swap_want_asset_program_default),
261
+ wantBtc: arkade.parseArtifact(swap_want_btc_program_default)
262
+ };
263
+ function withExitClosure(program, exit) {
264
+ if (!exit) return program;
265
+ return {
266
+ ...program,
267
+ // typed params are authoritative: an undeclared `$exitDelay` fails
268
+ // validateProgram instead of compiling against an unbound value
269
+ params: [...program.params ?? [], { name: "exitDelay", type: "int" }],
270
+ functions: {
271
+ ...program.functions,
272
+ exit: {
273
+ tapscript: { signers: ["$user"], csv: { type: exit.type, value: "$exitDelay" } }
274
+ }
275
+ }
276
+ };
277
+ }
278
+ function swapProgramBinding(offer, serverPubkey) {
279
+ if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
280
+ throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
281
+ }
282
+ return {
283
+ program: withExitClosure(
284
+ offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
285
+ offer.exitDelay
286
+ ),
287
+ args: {
288
+ makerWP: offer.makerPkScript.subarray(2),
289
+ wantAmount: offer.wantAmount,
290
+ server: serverPubkey,
291
+ user: offer.makerPublicKey,
292
+ // internal byte order
293
+ ...offer.wantAsset && {
294
+ wantAssetTxid: offer.wantAsset.txid.slice().reverse(),
295
+ wantAssetGroupIndex: offer.wantAsset.groupIndex
296
+ },
297
+ ...offer.exitDelay && { exitDelay: offer.exitDelay.value }
298
+ },
299
+ keys: {
300
+ serverKey: serverPubkey,
301
+ userKey: offer.makerPublicKey,
302
+ emulatorKey: offer.emulatorPubkey
303
+ }
304
+ };
305
+ }
306
+ function offerVtxoScript(offer, serverPubkey) {
307
+ const { program, args, keys } = swapProgramBinding(offer, serverPubkey);
308
+ return new arkade.ArkadeProgramScript(program, args, keys);
309
+ }
310
+ var OFFER_PACKET_TYPE = 3;
311
+ var FIELDS = {
312
+ swapPkScript: { tag: 1, width: 34 },
313
+ wantAmount: { tag: 2, width: 8 },
314
+ wantAsset: { tag: 3, width: void 0 },
315
+ makerPkScript: { tag: 5, width: 34 },
316
+ makerPublicKey: { tag: 7, width: 32 },
317
+ emulatorPubkey: { tag: 8, width: 32 },
318
+ ratioNum: { tag: 9, width: 8 },
319
+ ratioDen: { tag: 10, width: 8 },
320
+ offerAsset: { tag: 11, width: void 0 },
321
+ exitTimelock: { tag: 12, width: 9 }
322
+ };
323
+ var EXIT_TYPES = ["blocks", "seconds"];
324
+ var NAMES = Object.fromEntries(Object.entries(FIELDS).map(([k, f]) => [f.tag, k]));
325
+ function u64(name, value) {
326
+ if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
327
+ throw new Error(`${name} does not fit the offer wire format (u64)`);
328
+ }
329
+ const out = new Uint8Array(8);
330
+ new DataView(out.buffer).setBigUint64(0, value, false);
331
+ return out;
332
+ }
333
+ var readU64 = (value) => new DataView(value.buffer, value.byteOffset).getBigUint64(0, false);
334
+ var setRatio = (name, value) => {
335
+ if (value === void 0 || value === BigInt(0)) return void 0;
336
+ if (value < BigInt(0)) throw new Error(`${name} does not fit the offer wire format (u64)`);
337
+ return value;
338
+ };
339
+ function tlv(type, value) {
340
+ if (value.length > 65535) throw new Error("TLV value exceeds the u16 length field");
341
+ return concatBytes(Uint8Array.of(type, value.length >> 8 & 255, value.length & 255), value);
342
+ }
343
+ function encodeOffer(offer) {
344
+ if (Boolean(offer.wantAsset) === Boolean(offer.offerAsset)) {
345
+ throw new Error("offer must carry exactly one of wantAsset or offerAsset");
346
+ }
347
+ for (const name of [
348
+ "swapPkScript",
349
+ "makerPkScript",
350
+ "makerPublicKey",
351
+ "emulatorPubkey"
352
+ ]) {
353
+ if (offer[name].length !== FIELDS[name].width) {
354
+ throw new Error(`${name} must be ${FIELDS[name].width} bytes`);
355
+ }
356
+ }
357
+ const ratioNum = setRatio("ratioNum", offer.ratioNum);
358
+ const ratioDen = setRatio("ratioDen", offer.ratioDen);
359
+ if (ratioNum === void 0 !== (ratioDen === void 0)) {
360
+ throw new Error("offer must carry both ratioNum and ratioDen, or neither");
361
+ }
362
+ const recs = [
363
+ tlv(FIELDS.swapPkScript.tag, offer.swapPkScript),
364
+ tlv(FIELDS.wantAmount.tag, u64("wantAmount", offer.wantAmount))
365
+ ];
366
+ if (offer.wantAsset) recs.push(tlv(FIELDS.wantAsset.tag, offer.wantAsset.serialize()));
367
+ if (ratioNum !== void 0) recs.push(tlv(FIELDS.ratioNum.tag, u64("ratioNum", ratioNum)));
368
+ if (ratioDen !== void 0) recs.push(tlv(FIELDS.ratioDen.tag, u64("ratioDen", ratioDen)));
369
+ if (offer.offerAsset) recs.push(tlv(FIELDS.offerAsset.tag, offer.offerAsset.serialize()));
370
+ recs.push(
371
+ tlv(FIELDS.makerPkScript.tag, offer.makerPkScript),
372
+ tlv(FIELDS.makerPublicKey.tag, offer.makerPublicKey),
373
+ tlv(FIELDS.emulatorPubkey.tag, offer.emulatorPubkey)
374
+ );
375
+ if (offer.exitDelay) recs.push(tlv(FIELDS.exitTimelock.tag, encodeExitDelay(offer.exitDelay)));
376
+ return concatBytes(...recs);
377
+ }
378
+ function encodeExitDelay(exit) {
379
+ return concatBytes(
380
+ Uint8Array.of(EXIT_TYPES.indexOf(assertExitDelay(exit).type)),
381
+ u64("exitDelay", exit.value)
382
+ );
383
+ }
384
+ function assertExitDelay(exit) {
385
+ if (EXIT_TYPES.indexOf(exit.type) < 0) {
386
+ throw new Error(`unknown exitDelay locktime type: ${exit.type}`);
387
+ }
388
+ if (exit.value <= BigInt(0)) {
389
+ throw new Error("exitDelay must be a positive relative locktime");
390
+ }
391
+ if (exit.value >> BigInt(32) > BigInt(0)) {
392
+ throw new Error("exitDelay does not fit the locktime field (u32)");
393
+ }
394
+ return exit;
395
+ }
396
+ function decodeOffer(data) {
397
+ const fields = {};
398
+ let off = 0;
399
+ while (off < data.length) {
400
+ if (off + 3 > data.length) throw new Error("truncated TLV header");
401
+ const type = data[off];
402
+ const length = data[off + 1] << 8 | data[off + 2];
403
+ off += 3;
404
+ if (off + length > data.length)
405
+ throw new Error(`truncated TLV value for type 0x${type.toString(16)}`);
406
+ const name = NAMES[type];
407
+ if (!name) throw new Error(`unknown TLV type: 0x${type.toString(16)}`);
408
+ if (fields[name] !== void 0) throw new Error(`duplicate TLV record: ${name}`);
409
+ fields[name] = data.slice(off, off + length);
410
+ off += length;
411
+ }
412
+ for (const name of ["wantAsset", "offerAsset"]) {
413
+ if (fields[name]?.length === 0) throw new Error(`missing/invalid ${name}`);
414
+ }
415
+ for (const [name, value] of Object.entries(fields)) {
416
+ const width = FIELDS[name].width;
417
+ if (width !== void 0 && value.length !== width) {
418
+ throw new Error(`missing/invalid ${name}`);
419
+ }
420
+ }
421
+ const need = (name) => {
422
+ const v = fields[name];
423
+ const len = FIELDS[name].width;
424
+ if (!v || len !== void 0 && v.length !== len)
425
+ throw new Error(`missing/invalid ${name}`);
426
+ return v;
427
+ };
428
+ const amount = need("wantAmount");
429
+ if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
430
+ throw new Error("offer must carry exactly one of wantAsset or offerAsset");
431
+ }
432
+ const readRatio = (name) => {
433
+ const raw = fields[name];
434
+ if (!raw) return void 0;
435
+ const value = readU64(raw);
436
+ if (value === BigInt(0)) throw new Error(`missing/invalid ${name}`);
437
+ return value;
438
+ };
439
+ const ratioNum = readRatio("ratioNum");
440
+ const ratioDen = readRatio("ratioDen");
441
+ if (ratioNum === void 0 !== (ratioDen === void 0)) {
442
+ throw new Error("offer must carry both ratioNum and ratioDen, or neither");
443
+ }
444
+ return {
445
+ swapPkScript: need("swapPkScript"),
446
+ wantAmount: readU64(amount),
447
+ ...fields.wantAsset && { wantAsset: asset.AssetId.fromBytes(fields.wantAsset) },
448
+ ...fields.offerAsset && { offerAsset: asset.AssetId.fromBytes(fields.offerAsset) },
449
+ makerPkScript: need("makerPkScript"),
450
+ makerPublicKey: need("makerPublicKey"),
451
+ emulatorPubkey: need("emulatorPubkey"),
452
+ ...ratioNum !== void 0 && { ratioNum },
453
+ ...ratioDen !== void 0 && { ratioDen },
454
+ ...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
455
+ };
456
+ }
457
+ function decodeExitDelay(value) {
458
+ const type = EXIT_TYPES[value[0]];
459
+ if (!type) throw new Error(`unknown exitDelay locktime type: 0x${value[0].toString(16)}`);
460
+ return { type, value: readU64(value.subarray(1)) };
461
+ }
462
+ var OFFER_CONTRACT_LABEL = "Arkade swap offer";
463
+ var OFFER_CONTRACT_KIND = "asset-swap-offer";
464
+ async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript, opts = {}) {
465
+ const contractManager = opts.contractManager ?? await wallet.getContractManager();
466
+ const client = opts.client ?? await arkade.Arkade.connect({
467
+ arkade: new RestArkProvider(arkServerUrl),
468
+ indexer: new RestIndexerProvider(arkServerUrl),
469
+ identity: wallet.identity,
470
+ // without this the row's `address` would be derived against the SDK's
471
+ // default network while its script is right — a row that disagrees with
472
+ // the address the user is about to fund
473
+ network: getNetwork(network),
474
+ contractManager
475
+ });
476
+ await contractManager.createContract(
477
+ offerContractParams(client, binding, serverPubkey, expectedPkScript)
478
+ );
479
+ await promoteOfferContract(contractManager, hex2.encode(expectedPkScript), opts.issued);
480
+ }
481
+ function offerContractParams(client, binding, serverPubkey, expectedPkScript) {
482
+ const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
483
+ const contract = new arkade.ArkadeContract(client, program, args, keys);
484
+ if (hex2.encode(contract.pkScript) !== hex2.encode(expectedPkScript)) {
485
+ throw new Error("derived covenant does not match the offer's swapPkScript");
486
+ }
487
+ return {
488
+ ...contract.toContractParams(),
489
+ label: OFFER_CONTRACT_LABEL,
490
+ metadata: { genericallySpendable: false, kind: OFFER_CONTRACT_KIND }
491
+ };
492
+ }
493
+ async function restoreOfferCoverage(wallet, arkServerUrl, swaps) {
494
+ const live = swaps.filter((s) => !RETIRABLE.includes(s.status));
495
+ if (live.length === 0) return;
496
+ const arkProvider = new RestArkProvider(arkServerUrl);
497
+ const info = await arkProvider.getInfo();
498
+ const serverPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
499
+ const contractManager = await wallet.getContractManager();
500
+ const client = await arkade.Arkade.connect({
501
+ arkade: arkProvider,
502
+ indexer: new RestIndexerProvider(arkServerUrl),
503
+ identity: wallet.identity,
504
+ network: getNetwork(info.network),
505
+ contractManager
506
+ });
507
+ const done = /* @__PURE__ */ new Set();
508
+ const covenants = [];
509
+ for (const swap of live) {
510
+ if (done.has(swap.swapPkScript)) continue;
511
+ done.add(swap.swapPkScript);
512
+ try {
513
+ const { swapPkScript: _, ...binding } = decodeOffer(hex2.decode(swap.offerHex));
514
+ covenants.push({
515
+ script: swap.swapPkScript,
516
+ issued: swap.createdAt,
517
+ params: offerContractParams(
518
+ client,
519
+ binding,
520
+ serverPubKey,
521
+ hex2.decode(swap.swapPkScript)
522
+ )
523
+ });
524
+ } catch (err) {
525
+ console.warn(`[swap] could not restore coverage for ${swap.swapPkScript}`, err);
526
+ }
527
+ }
528
+ if (covenants.length === 0) return;
529
+ const paramsList = covenants.map((c) => c.params);
530
+ if (contractManager.createContracts) {
531
+ await contractManager.createContracts(paramsList);
532
+ } else {
533
+ for (const params of paramsList) await contractManager.createContract(params);
534
+ }
535
+ for (const c of covenants) await promoteOfferContract(contractManager, c.script, c.issued);
536
+ }
537
+ function serverExitDelay(delay) {
538
+ if (typeof delay !== "bigint" || delay <= BigInt(0)) {
539
+ throw new Error(
540
+ "the server reports no usable unilateralExitDelay; pass `exitDelay` to set the offer's exit closure explicitly, or `noExit: true` to publish without one"
541
+ );
542
+ }
543
+ return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
544
+ }
545
+ async function createOffer(wallet, arkServerUrl, params) {
546
+ if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
547
+ throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
548
+ }
549
+ const [info, makerAddress, makerPublicKey] = await Promise.all([
550
+ new RestArkProvider(arkServerUrl).getInfo(),
551
+ wallet.getAddress(),
552
+ wallet.identity.xOnlyPublicKey()
553
+ ]);
554
+ const serverPubKey = hex2.decode(toXOnlySignerHex(info.signerPubkey));
555
+ const network = getNetwork(info.network);
556
+ const emuKey = hex2.decode(
557
+ toXOnlySignerHex(resolveEmulatorPubkey(network, params.emulatorPubkey))
558
+ );
559
+ const binding = {
560
+ wantAmount: params.wantAmount,
561
+ wantAsset: params.wantAsset,
562
+ offerAsset: params.offerAsset,
563
+ makerPkScript: ArkAddress.decode(makerAddress).pkScript,
564
+ makerPublicKey,
565
+ emulatorPubkey: emuKey,
566
+ // checked HERE, before the covenant is derived and registered below:
567
+ // deferring it to `encodeOffer` leaves a registered contract behind for
568
+ // an offer that then fails to encode. @see assertExitDelay
569
+ exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
570
+ };
571
+ const script = offerVtxoScript(binding, serverPubKey);
572
+ const offer = { ...binding, swapPkScript: script.pkScript };
573
+ await registerOfferContract(
574
+ wallet,
575
+ arkServerUrl,
576
+ info.network,
577
+ binding,
578
+ serverPubKey,
579
+ script.pkScript
580
+ );
581
+ const payload = encodeOffer(offer);
582
+ return {
583
+ offerHex: hex2.encode(payload),
584
+ extension: { type: OFFER_PACKET_TYPE, payload },
585
+ // VtxoScript.address owns address construction; assembling an ArkAddress
586
+ // from tweakedPublicKey here would silently miss any future step it gains
587
+ address: script.address(network.hrp, serverPubKey).encode(),
588
+ swapPkScript: script.pkScript
589
+ };
590
+ }
591
+ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
592
+ const { repository, fundingTxid, swapAddress } = opts;
593
+ const offer = decodeOffer(hex2.decode(offerHex));
594
+ const contractManager = await wallet.getContractManager();
595
+ const client = await arkade.Arkade.connect({
596
+ arkade: new RestArkProvider(arkServerUrl),
597
+ indexer: new RestIndexerProvider(arkServerUrl),
598
+ identity: wallet.identity,
599
+ // registered offers resolve their VTXOs from the contract repository
600
+ // instead of a direct indexer query; the indexer above stays as the
601
+ // fallback for offers created before registration existed
602
+ contractManager
603
+ // no `network`, unlike registerOfferContract: the row lookup is by
604
+ // script and the payout script comes from wallet.getAddress(), so the
605
+ // client's network (which only shapes address derivation) is unused here
606
+ });
607
+ const operatorPubkey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
608
+ const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
609
+ const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
610
+ if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
611
+ throw new Error(
612
+ "rebuilt covenant does not match the offer's swapPkScript \u2014 the server signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
613
+ );
614
+ }
615
+ const contract = new arkade.ArkadeContract(client, program, args, keys);
616
+ const [vtxos, makerAddress] = await Promise.all([contract.getUtxos(), wallet.getAddress()]);
617
+ if (!fundingTxid && vtxos.length > 1) {
618
+ throw new Error(
619
+ "multiple spendable deposits at the swap address \u2014 pass fundingTxid to select one"
620
+ );
621
+ }
622
+ const vtxo = fundingTxid ? vtxos.find((v) => v.txid === fundingTxid) : vtxos[0];
623
+ if (!vtxo) throw new Error("no spendable VTXO at the swap address");
624
+ const makerPkScript = ArkAddress.decode(makerAddress).pkScript;
625
+ const cancel = contract.functions.cancel().from({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value }).to(makerPkScript, BigInt(vtxo.value));
626
+ for (const a of vtxo.assets ?? []) {
627
+ cancel.withAsset({
628
+ assetId: a.assetId,
629
+ inputs: [{ vin: 0, amount: BigInt(a.amount) }],
630
+ outputs: [{ vout: 0, amount: BigInt(a.amount) }]
631
+ });
632
+ }
633
+ const swapId = fundingTxid ?? vtxo.txid;
634
+ const hasLocalRecord = (await getAssetSwapsOrThrow(repository)).some((s) => s.id === swapId);
635
+ if (hasLocalRecord) await updateAssetSwap(repository, swapId, { status: "cancelling" });
636
+ const { txid } = await cancel.send();
637
+ if (hasLocalRecord) {
638
+ const { persisted, swaps } = await updateAssetSwapBestEffort(repository, swapId, {
639
+ status: "cancelled",
640
+ spentTxid: txid
641
+ });
642
+ if (persisted) {
643
+ await retireOfferContract(contractManager, swaps, hex2.encode(offer.swapPkScript));
644
+ }
645
+ }
646
+ return txid;
647
+ }
648
+ var ASSET_CARRIER_SATS = BigInt(SDK_ASSET_CARRIER_SATS);
649
+ async function fillOffer(wallet, arkServerUrl, offerHex, opts) {
650
+ const {
651
+ fund,
652
+ payoutScript,
653
+ fundingTxid,
654
+ swapAddress,
655
+ assetCarrierSats = ASSET_CARRIER_SATS,
656
+ emulator,
657
+ emulatorPubkey
658
+ } = opts;
659
+ const offer = decodeOffer(hex2.decode(offerHex));
660
+ const wantedAssetId = offer.wantAsset?.toString();
661
+ if (fund.length === 0) {
662
+ throw new Error("fillOffer needs coins to pay wantAmount with \u2014 `fund` is empty");
663
+ }
664
+ if (wantedAssetId !== void 0) {
665
+ const supplied = fund.reduce(
666
+ (sum, coin) => sum + amountOfAsset(coin.assets, wantedAssetId),
667
+ BigInt(0)
668
+ );
669
+ if (supplied < offer.wantAmount) {
670
+ throw new Error(
671
+ `fillOffer needs ${offer.wantAmount} of ${wantedAssetId} to pay the maker, but \`fund\` declares ${supplied} \u2014 pass coins carrying it, and declare their assets`
672
+ );
673
+ }
674
+ }
675
+ const contractManager = await wallet.getContractManager();
676
+ const client = await arkade.Arkade.connect({
677
+ arkade: new RestArkProvider(arkServerUrl),
678
+ indexer: new RestIndexerProvider(arkServerUrl),
679
+ identity: wallet.identity,
680
+ contractManager,
681
+ // `fulfill` is a covenant path, so the emulator executes the arkade
682
+ // script and finalizes with arkd. Without it the spend is built and then
683
+ // refused at submission — there is no default to fall back on.
684
+ emulator: typeof emulator === "string" ? new RestEmulatorProvider(emulator) : emulator,
685
+ // Only reached for the client's own emulatorKey, which this fill never
686
+ // derives against — the offer's own key is bound below. It still has to
687
+ // resolve, and on a network with no pinned key it throws without this.
688
+ ...emulatorPubkey ? { emulatorPubkey } : {}
689
+ });
690
+ const serverKey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
691
+ const { program, args, keys } = swapProgramBinding(offer, serverKey);
692
+ const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
693
+ if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
694
+ throw new Error(
695
+ "rebuilt covenant does not match the offer's swapPkScript \u2014 the server signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
696
+ );
697
+ }
698
+ const contract = new arkade.ArkadeContract(client, program, args, keys);
699
+ const [vtxos, takerAddress] = await Promise.all([contract.getUtxos(), wallet.getAddress()]);
700
+ if (!fundingTxid && vtxos.length > 1) {
701
+ throw new Error(
702
+ "multiple spendable deposits at the swap address \u2014 pass fundingTxid to select one"
703
+ );
704
+ }
705
+ const vtxo = fundingTxid ? vtxos.find((v) => v.txid === fundingTxid) : vtxos[0];
706
+ if (!vtxo) throw new Error("no spendable VTXO at the swap address");
707
+ if (offer.offerAsset) {
708
+ const offered = offer.offerAsset.toString();
709
+ const deposited = amountOfAsset(vtxo.assets, offered);
710
+ if (deposited <= BigInt(0)) {
711
+ throw new Error(
712
+ `the deposit at the swap address carries no ${offered}, which this offer sells \u2014 filling it would pay wantAmount for nothing. The indexer may be behind, or the offer may not be backed by what it advertises`
713
+ );
714
+ }
715
+ }
716
+ const payout = payoutScript ?? ArkAddress.decode(takerAddress).pkScript;
717
+ const makerSats = wantedAssetId === void 0 ? offer.wantAmount : assetCarrierSats;
718
+ const fill = contract.functions.fulfill().from({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value }).fund(fund).to(offer.makerPkScript, makerSats).change(payout);
719
+ const held = /* @__PURE__ */ new Map();
720
+ const hold = (vin, assets) => {
721
+ for (const a of assets ?? []) {
722
+ const amount = BigInt(a.amount);
723
+ if (amount <= BigInt(0)) continue;
724
+ held.set(a.assetId, [...held.get(a.assetId) ?? [], { vin, amount }]);
725
+ }
726
+ };
727
+ hold(0, vtxo.assets);
728
+ fund.forEach((coin, i) => hold(i + 1, coin.assets));
729
+ const outputsSum = makerSats;
730
+ const inputsSum = fund.reduce((s, c) => s + BigInt(c.value), BigInt(vtxo.value));
731
+ const hasPayoutOutput = inputsSum > outputsSum;
732
+ const emitWanted = () => {
733
+ if (wantedAssetId === void 0) return;
734
+ const supplying = held.get(wantedAssetId) ?? [];
735
+ const supplied = supplying.reduce((s, i) => s + i.amount, BigInt(0));
736
+ const outputs = [{ vout: 0, amount: offer.wantAmount }];
737
+ const surplus = supplied - offer.wantAmount;
738
+ if (surplus > BigInt(0)) {
739
+ if (!hasPayoutOutput) {
740
+ throw new Error(
741
+ `fillOffer has ${surplus} of ${wantedAssetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
742
+ );
743
+ }
744
+ outputs.push({ vout: 1, amount: surplus });
745
+ }
746
+ fill.withAsset({ assetId: wantedAssetId, inputs: supplying, outputs });
747
+ held.delete(wantedAssetId);
748
+ };
749
+ emitWanted();
750
+ for (const [assetId, inputs] of held) {
751
+ const amount = inputs.reduce((s, i) => s + i.amount, BigInt(0));
752
+ if (!hasPayoutOutput) {
753
+ throw new Error(
754
+ `fillOffer has ${amount} of ${assetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
755
+ );
756
+ }
757
+ fill.withAsset({ assetId, inputs, outputs: [{ vout: 1, amount }] });
758
+ }
759
+ const { txid } = await fill.send();
760
+ return txid;
761
+ }
762
+ var amountOfAsset = (assets, assetId) => {
763
+ let total = BigInt(0);
764
+ for (const a of assets ?? []) {
765
+ if (a.assetId === assetId) total += BigInt(a.amount);
766
+ }
767
+ return total;
768
+ };
769
+
770
+ // src/onchainHtlc.ts
771
+ import { hex as hex3 } from "@scure/base";
772
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
4
773
  import { ripemd160 } from "@noble/hashes/legacy.js";
5
774
  import * as btc from "@scure/btc-signer";
6
775
  var ONCHAIN_ORDER_MARGIN_SECONDS = 2 * 60 * 60;
@@ -10,8 +779,8 @@ var LOCKTIME_THRESHOLD = 5e8;
10
779
  var ONCHAIN_SECONDS_PER_BLOCK = 600;
11
780
  var ONCHAIN_DUST_SATS = BigInt(330);
12
781
  var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
13
- var paymentHashOf = (preimage) => hex.encode(sha256(preimage));
14
- var h160FromPaymentHash = (paymentHash) => ripemd160(hex.decode(paymentHash));
782
+ var paymentHashOf = (preimage) => hex3.encode(sha2562(preimage));
783
+ var h160FromPaymentHash = (paymentHash) => ripemd160(hex3.decode(paymentHash));
15
784
  var L1_NETWORKS = {
16
785
  bitcoin: btc.NETWORK,
17
786
  testnet: btc.TEST_NETWORK,
@@ -63,7 +832,7 @@ function onchainHtlcScript(params, network) {
63
832
  );
64
833
  const controlBlockFor = (leaf) => {
65
834
  for (const [block, script] of payment.tapLeafScript ?? []) {
66
- if (script.length - 1 === leaf.length && hex.encode(script.subarray(0, leaf.length)) === hex.encode(leaf)) {
835
+ if (script.length - 1 === leaf.length && hex3.encode(script.subarray(0, leaf.length)) === hex3.encode(leaf)) {
67
836
  return btc.TaprootControlBlock.encode(block);
68
837
  }
69
838
  }
@@ -156,13 +925,13 @@ var buildHtlcRefund = (input) => buildLeafSpend({
156
925
  function extractPreimage(txHex, paymentHash) {
157
926
  let raw;
158
927
  try {
159
- raw = btc.RawTx.decode(hex.decode(txHex));
928
+ raw = btc.RawTx.decode(hex3.decode(txHex));
160
929
  } catch {
161
930
  return null;
162
931
  }
163
932
  for (const stack of raw.witnesses ?? []) {
164
933
  for (const item of stack) {
165
- if (item.length === 32 && hex.encode(sha256(item)) === paymentHash) return item;
934
+ if (item.length === 32 && hex3.encode(sha2562(item)) === paymentHash) return item;
166
935
  }
167
936
  }
168
937
  return null;
@@ -207,7 +976,7 @@ async function classifyOnchainHtlc(chain, input) {
207
976
  );
208
977
  if (!spend) return { phase: "unfunded" };
209
978
  const preimage = extractPreimage(spend.txHex, input.htlc.paymentHash);
210
- const txid = btc.Transaction.fromRaw(hex.decode(spend.txHex), {
979
+ const txid = btc.Transaction.fromRaw(hex3.decode(spend.txHex), {
211
980
  allowUnknownInputs: true,
212
981
  allowUnknownOutputs: true
213
982
  }).id;
@@ -222,11 +991,11 @@ async function classifyOnchainHtlc(chain, input) {
222
991
 
223
992
  // src/claimPacket.ts
224
993
  import { base64 } from "@scure/base";
225
- import { concatBytes } from "@scure/btc-signer/utils.js";
994
+ import { concatBytes as concatBytes2 } from "@scure/btc-signer/utils.js";
226
995
  import { gcm } from "@noble/ciphers/aes.js";
227
996
  import { secp256k1 } from "@noble/curves/secp256k1.js";
228
997
  import { hkdf } from "@noble/hashes/hkdf.js";
229
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
998
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
230
999
  var HKDF_INFO = new TextEncoder().encode("covclaimd/preimage/v1");
231
1000
  async function sealClaimPacket(input) {
232
1001
  return sealWithEntropy(
@@ -242,7 +1011,7 @@ async function sealWithEntropy(input, ephemeralKey, nonce) {
242
1011
  }
243
1012
  const ephemeralPub = secp256k1.getPublicKey(ephemeralKey, true);
244
1013
  const sharedX = secp256k1.getSharedSecret(ephemeralKey, input.covclaimdPubkey, true).subarray(1);
245
- const key = hkdf(sha2562, sharedX, ephemeralPub, HKDF_INFO, 32);
1014
+ const key = hkdf(sha2563, sharedX, ephemeralPub, HKDF_INFO, 32);
246
1015
  if (nonce.length !== 12) throw new Error("nonce must be 12 bytes");
247
1016
  const sealed = gcm(key, nonce, ephemeralPub).encrypt(input.preimage);
248
1017
  const ciphertext = new Uint8Array(33 + 12 + sealed.length);
@@ -252,9 +1021,9 @@ async function sealWithEntropy(input, ephemeralKey, nonce) {
252
1021
  return {
253
1022
  ciphertext: base64.encode(ciphertext),
254
1023
  packet: base64.encode(
255
- concatBytes(
256
- tlv(TLV_CIPHERTEXT, ciphertext),
257
- tlv(TLV_COVCLAIMD_PUBKEY, input.covclaimdPubkey)
1024
+ concatBytes2(
1025
+ tlv2(TLV_CIPHERTEXT, ciphertext),
1026
+ tlv2(TLV_COVCLAIMD_PUBKEY, input.covclaimdPubkey)
258
1027
  )
259
1028
  )
260
1029
  };
@@ -265,8 +1034,8 @@ var TLV_COVCLAIMD_PUBKEY = 3;
265
1034
  var COMPRESSED_PUBKEY_LENGTH = 33;
266
1035
  var SEALED_CIPHERTEXT_LENGTH = 93;
267
1036
  var CLAIM_PACKET_TYPE = 4;
268
- var tlv = (type, value) => Uint8Array.from([type, value.length >> 8 & 255, value.length & 255, ...value]);
269
- var appendArkadeScript = (body, arkadeScript) => Uint8Array.from([...body, ...tlv(TLV_ARKADE_SCRIPT, arkadeScript)]);
1037
+ var tlv2 = (type, value) => Uint8Array.from([type, value.length >> 8 & 255, value.length & 255, ...value]);
1038
+ var appendArkadeScript = (body, arkadeScript) => Uint8Array.from([...body, ...tlv2(TLV_ARKADE_SCRIPT, arkadeScript)]);
270
1039
  var parseTlv = (data) => {
271
1040
  let ciphertextLength;
272
1041
  let hasArkadeScript = false;
@@ -315,9 +1084,9 @@ var claimPacketShape = (b64) => {
315
1084
  };
316
1085
 
317
1086
  // src/lockupContract.ts
318
- import { hex as hex2 } from "@scure/base";
1087
+ import { hex as hex4 } from "@scure/base";
319
1088
  import {
320
- ArkAddress,
1089
+ ArkAddress as ArkAddress2,
321
1090
  VHTLCV2ContractHandler
322
1091
  } from "@arkade-os/sdk";
323
1092
  var SWAP_LOCKUP_CONTRACT_TYPE = "vhtlc-v2";
@@ -356,7 +1125,7 @@ async function registerLockupContract(contracts, script, address) {
356
1125
  await contracts.createContract({
357
1126
  type: SWAP_LOCKUP_CONTRACT_TYPE,
358
1127
  params: VHTLCV2ContractHandler.serializeParams(script.options),
359
- script: hex2.encode(script.pkScript),
1128
+ script: hex4.encode(script.pkScript),
360
1129
  address,
361
1130
  label: SWAP_LOCKUP_CONTRACT_LABEL,
362
1131
  metadata: { genericallySpendable: false, kind: SWAP_LOCKUP_CONTRACT_KIND }
@@ -366,21 +1135,21 @@ async function registerLockupContract(contracts, script, address) {
366
1135
  }
367
1136
  }
368
1137
  async function lockupContractParams(contracts, lockupAddress) {
369
- const script = hex2.encode(ArkAddress.decode(lockupAddress).pkScript);
1138
+ const script = hex4.encode(ArkAddress2.decode(lockupAddress).pkScript);
370
1139
  const [row] = await contracts.getContracts({ script });
371
1140
  if (!row) throw new LockupContractMissing(lockupAddress, script);
372
1141
  return row.params;
373
1142
  }
374
1143
 
375
1144
  // src/rfq.ts
376
- import { hex as hex3 } from "@scure/base";
1145
+ import { hex as hex5 } from "@scure/base";
377
1146
  import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
378
1147
  import {
379
- ArkAddress as ArkAddress2,
380
- RestArkProvider,
1148
+ ArkAddress as ArkAddress3,
1149
+ RestArkProvider as RestArkProvider2,
381
1150
  VHTLC,
382
- getNetwork,
383
- resolveEmulatorPubkey,
1151
+ getNetwork as getNetwork2,
1152
+ resolveEmulatorPubkey as resolveEmulatorPubkey2,
384
1153
  toXOnly
385
1154
  } from "@arkade-os/sdk";
386
1155
  import {
@@ -389,11 +1158,17 @@ import {
389
1158
  } from "@arkade-os/sdk";
390
1159
  var solverHex = (value, field) => {
391
1160
  try {
392
- return hex3.decode(value);
1161
+ return hex5.decode(value);
393
1162
  } catch {
394
1163
  throw new Error(`solver sent malformed hex for ${field}`);
395
1164
  }
396
1165
  };
1166
+ var quoteSats = (value, field) => {
1167
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) {
1168
+ throw new Error(`HTLC quote carries a non-sats ${field}: ${String(value)}`);
1169
+ }
1170
+ return value;
1171
+ };
397
1172
  var ARKADE_BTC = "arkade:BTC";
398
1173
  var LIGHTNING_BTC = "lightning:BTC";
399
1174
  var ONCHAIN_BTC = "onchain:BTC";
@@ -489,7 +1264,7 @@ var AddressMismatch = class extends Error {
489
1264
  this.quoted = quoted;
490
1265
  }
491
1266
  };
492
- var newRfqId = () => hex3.encode(crypto.getRandomValues(new Uint8Array(32)));
1267
+ var newRfqId = () => hex5.encode(crypto.getRandomValues(new Uint8Array(32)));
493
1268
  var lightningSendRequest = (input) => ({
494
1269
  v: 1,
495
1270
  type: "rfq_request",
@@ -499,18 +1274,13 @@ var lightningSendRequest = (input) => ({
499
1274
  profile: {
500
1275
  invoice: input.invoice,
501
1276
  refund_address: input.refundAddress,
502
- client_refund_pubkey: hex3.encode(input.senderPubkey)
1277
+ client_refund_pubkey: hex5.encode(input.senderPubkey)
503
1278
  }
504
1279
  });
505
1280
  var arkadeSwapRequest = (input) => {
506
1281
  if (!input.wantAsset && !input.offerAsset) {
507
1282
  throw new Error(
508
- "set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 with neither set both legs are BTC, which is not a swap"
509
- );
510
- }
511
- if (input.wantAsset && input.offerAsset) {
512
- throw new Error(
513
- "set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 asset->asset is nameable on the wire but no solver quotes it yet"
1283
+ "set at least one of wantAsset or offerAsset \u2014 with neither set both legs are BTC, which is not a swap"
514
1284
  );
515
1285
  }
516
1286
  const pair = rfqPair(
@@ -523,16 +1293,59 @@ var arkadeSwapRequest = (input) => {
523
1293
  type: "rfq_request",
524
1294
  rfq_id: input.rfqId,
525
1295
  pair,
526
- amount_side: input.amountSide,
527
- amount: input.amount,
528
- // The pair is the only place the asset ids appear. Repeating them here
529
- // would be a key the solver's `.strict()` profile schema does not
530
- // declare, and an undeclared key is `unsupported_payload` — a refusal,
531
- // not an ignored extra. Empty, not absent: `profile` is required on
532
- // every other request shape this wire carries.
533
- profile: {}
1296
+ amount_side: input.amountSide ?? "from",
1297
+ amount: canonicalAssetAmount(input.amount),
1298
+ // The pair names the assets; the profile names the trader. Both are
1299
+ // covenant parameters, and the solver's `.strict()` schema refuses
1300
+ // anything else — an undeclared key is `unsupported_payload`.
1301
+ profile: {
1302
+ maker_pk_script: normalizeMakerPkScript(input.makerPkScript),
1303
+ maker_public_key: normalizeMakerPublicKey(input.makerPublicKey)
1304
+ }
534
1305
  };
535
1306
  };
1307
+ var canonicalAssetAmount = (amount) => {
1308
+ if (typeof amount === "bigint") {
1309
+ if (amount <= 0n) throw new Error(`amount must be positive, got ${amount}`);
1310
+ return amount.toString();
1311
+ }
1312
+ if (typeof amount === "number") {
1313
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
1314
+ throw new Error(`amount must be a positive safe integer, got ${amount}`);
1315
+ }
1316
+ return String(amount);
1317
+ }
1318
+ if (!/^(0|[1-9][0-9]*)$/.test(amount) || BigInt(amount) <= 0n) {
1319
+ throw new Error(`amount must be a canonical decimal string of atomic units, got ${amount}`);
1320
+ }
1321
+ return amount;
1322
+ };
1323
+ var normalizeHexBytes = (value, expectedBytes, label) => {
1324
+ if (value === void 0 || value === null) {
1325
+ throw new Error(`${label} is required`);
1326
+ }
1327
+ const bytes = typeof value === "string" ? solverHex(value, label) : value;
1328
+ if (!(bytes instanceof Uint8Array) || bytes.length !== expectedBytes) {
1329
+ const got = bytes instanceof Uint8Array ? bytes.length : typeof bytes;
1330
+ throw new Error(`${label} must be ${expectedBytes} bytes, got ${got}`);
1331
+ }
1332
+ return hex5.encode(bytes);
1333
+ };
1334
+ var normalizeMakerPkScript = (value) => {
1335
+ const encoded = normalizeHexBytes(value, 34, "maker_pk_script");
1336
+ if (!encoded.startsWith("5100") && !encoded.startsWith("5120")) {
1337
+ throw new Error("maker_pk_script must be a taproot scriptPubKey (OP_1 <32-byte program>)");
1338
+ }
1339
+ return encoded;
1340
+ };
1341
+ var normalizeMakerPublicKey = (value) => {
1342
+ const raw = typeof value === "string" ? value : hex5.encode(value);
1343
+ const bytes = solverHex(raw, "maker_public_key");
1344
+ if (bytes.length === 33) {
1345
+ throw new Error("maker_public_key must be x-only (32 bytes), not compressed (33 bytes)");
1346
+ }
1347
+ return normalizeHexBytes(bytes, 32, "maker_public_key");
1348
+ };
536
1349
  var MIN_HEADROOM_SECONDS = 90 * 60;
537
1350
  var gateError = (reason, message) => {
538
1351
  const error = new Error(message);
@@ -580,6 +1393,9 @@ var assertFundable = (input) => {
580
1393
  }
581
1394
  if (input.now >= input.quote.valid_until)
582
1395
  fail("quote_expired", "quote expired \u2014 request a fresh one");
1396
+ if (quoteSats(input.quote.from_amount, "from_amount") <= 0 || quoteSats(input.quote.to_amount, "to_amount") <= 0) {
1397
+ fail("non_positive_amount", "quote carries a non-positive amount");
1398
+ }
583
1399
  if (input.quote.refund_locktime !== void 0 && input.quote.refund_locktime - input.now < MIN_HEADROOM_SECONDS) {
584
1400
  fail("insufficient_headroom", "refund deadline headroom below 90 minutes");
585
1401
  }
@@ -609,13 +1425,12 @@ var assertFundable = (input) => {
609
1425
  `maxFee.referenceRate must be a positive finite number, got ${referenceRate}`
610
1426
  );
611
1427
  }
612
- const fee = sameAsset ? input.quote.from_amount - input.quote.to_amount : Math.ceil(
613
- (input.quote.from_amount * referenceRate - input.quote.to_amount) / referenceRate
614
- );
615
- const allowed = Math.max(
616
- sats ?? 0,
617
- Math.floor(input.quote.from_amount * (bps ?? 0) / 1e4)
1428
+ const fromSats = quoteSats(input.quote.from_amount, "from_amount");
1429
+ const toSats = quoteSats(input.quote.to_amount, "to_amount");
1430
+ const fee = sameAsset ? fromSats - toSats : Math.ceil(
1431
+ (fromSats * referenceRate - toSats) / referenceRate
618
1432
  );
1433
+ const allowed = Math.max(sats ?? 0, Math.floor(fromSats * (bps ?? 0) / 1e4));
619
1434
  if (fee > allowed) {
620
1435
  fail("fee_too_high", `fee ${fee} exceeds the ${allowed} this client allows`);
621
1436
  }
@@ -814,7 +1629,7 @@ function lightningSendVtxoScript(params) {
814
1629
  sender: params.senderPubkey,
815
1630
  receiver: params.solverPubkey,
816
1631
  server: params.serverPubkey,
817
- preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
1632
+ preimageHash: ripemd1602(hex5.decode(params.paymentHash)),
818
1633
  refundLocktime: BigInt(params.refundLocktime),
819
1634
  unilateralClaimDelay: seconds(params.claimDelay),
820
1635
  unilateralRefundDelay: seconds(unilateralRefundDelay(params.claimDelay)),
@@ -834,7 +1649,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
834
1649
  const secrets = await provisionRefundKey(wallet);
835
1650
  const senderPubkey = secrets.pubkey;
836
1651
  const refundAddress = secrets.address;
837
- const info = await new RestArkProvider(arkServerUrl).getInfo();
1652
+ const info = await new RestArkProvider2(arkServerUrl).getInfo();
838
1653
  const quote = await transport.requestQuote(
839
1654
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
840
1655
  );
@@ -857,27 +1672,27 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
857
1672
  if (receiverPkScriptHex === void 0) {
858
1673
  throw new Error("lightning-send quote is missing profile.receiver_pk_script");
859
1674
  }
860
- if (quote.to_amount !== params.invoice.amountSats) {
1675
+ if (quoteSats(quote.to_amount, "to_amount") !== params.invoice.amountSats) {
861
1676
  throw new Error(
862
1677
  `quote to_amount ${quote.to_amount} does not match the invoice's ${params.invoice.amountSats}`
863
1678
  );
864
1679
  }
865
- if (quote.from_amount < quote.to_amount) {
1680
+ if (quoteSats(quote.from_amount, "from_amount") < quoteSats(quote.to_amount, "to_amount")) {
866
1681
  throw new Error(
867
1682
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
868
1683
  );
869
1684
  }
870
- const serverPubkey = toXOnly(hex3.decode(info.signerPubkey), "ark signer key");
871
- const network = getNetwork(info.network);
1685
+ const serverPubkey = toXOnly(hex5.decode(info.signerPubkey), "ark signer key");
1686
+ const network = getNetwork2(info.network);
872
1687
  const treeParams = {
873
- solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1688
+ solverPubkey: toXOnly(hex5.decode(quote.solver_pubkey), "solver key"),
874
1689
  refundLocktime: quote.refund_locktime,
875
1690
  serverPubkey,
876
1691
  paymentHash: params.invoice.paymentHash,
877
1692
  claimDelay,
878
1693
  refundWithoutReceiverDelay,
879
1694
  emulatorPubkey: toXOnly(
880
- hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1695
+ hex5.decode(resolveEmulatorPubkey2(network, params.emulatorPubkey)),
881
1696
  "emulator signer key"
882
1697
  ),
883
1698
  senderPubkey,
@@ -908,7 +1723,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
908
1723
  address,
909
1724
  // What the lockup must carry: the quote's `from_amount` — the invoice
910
1725
  // PLUS the corridor's fee, never the bare invoice amount.
911
- fundAmount: quote.from_amount,
1726
+ fundAmount: quoteSats(quote.from_amount, "from_amount"),
912
1727
  swapPkScript: script.pkScript,
913
1728
  script,
914
1729
  refundAddress,
@@ -918,11 +1733,116 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
918
1733
  };
919
1734
  }
920
1735
  var offerTermsFromQuote = (quote, assets) => {
921
- if (Boolean(assets.wantAsset) === Boolean(assets.offerAsset)) {
922
- throw new Error("set exactly one of wantAsset or offerAsset");
1736
+ if (!assets.wantAsset && !assets.offerAsset) {
1737
+ throw new Error("set at least one of wantAsset or offerAsset");
1738
+ }
1739
+ const wantAmount = BigInt(quote.to_amount);
1740
+ if (assets.wantAsset) return { wantAmount, wantAsset: assets.wantAsset };
1741
+ return { wantAmount, offerAsset: assets.offerAsset };
1742
+ };
1743
+ var verifyOfferAddress = (quote, derived) => {
1744
+ const quotedAddress = quote.profile?.offer_address;
1745
+ const quotedScript = quote.profile?.offer_pk_script;
1746
+ const derivedScript = hex5.encode(derived.swapPkScript);
1747
+ if (quotedAddress !== derived.address || quotedScript !== derivedScript) {
1748
+ throw new AddressMismatch([derived.address], quotedAddress);
1749
+ }
1750
+ return derived;
1751
+ };
1752
+ var assertArkadeFundable = (input) => {
1753
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
1754
+ if (!Number.isSafeInteger(input.quote.valid_until)) {
1755
+ throw gateError("quote_expired", "quote carries no valid_until");
1756
+ }
1757
+ if (now >= input.quote.valid_until) {
1758
+ throw gateError("quote_expired", `quote lapsed at ${input.quote.valid_until} (now ${now})`);
1759
+ }
1760
+ if (BigInt(input.quote.from_amount) <= 0n || BigInt(input.quote.to_amount) <= 0n) {
1761
+ throw gateError("quote_expired", "quote carries a non-positive amount");
923
1762
  }
924
- return { wantAmount: BigInt(quote.to_amount), ...assets };
925
1763
  };
1764
+ var quoteCarrierSats = (quote) => {
1765
+ const published = quote.carrier_sats === void 0 ? 0n : BigInt(quote.carrier_sats);
1766
+ return published > 0n ? published : ASSET_CARRIER_SATS;
1767
+ };
1768
+ async function requestArkadeSwap(wallet, arkServerUrl, transport, params) {
1769
+ if (!params.wantAsset && !params.offerAsset) {
1770
+ throw new Error("set at least one of wantAsset or offerAsset; BTC-to-BTC is not a swap");
1771
+ }
1772
+ const rfqId = params.rfqId ?? newRfqId();
1773
+ const amountSide = params.amountSide ?? "from";
1774
+ const [makerAddress, makerPublicKey] = await Promise.all([
1775
+ wallet.getAddress(),
1776
+ wallet.identity.xOnlyPublicKey()
1777
+ ]);
1778
+ const makerPkScript = ArkAddress3.decode(makerAddress).pkScript;
1779
+ const pair = rfqPair(
1780
+ params.offerAsset ? arkadeAssetLeg(params.offerAsset) : ARKADE_BTC,
1781
+ params.wantAsset ? arkadeAssetLeg(params.wantAsset) : ARKADE_BTC
1782
+ );
1783
+ const quote = await transport.requestQuote(
1784
+ arkadeSwapRequest({
1785
+ rfqId,
1786
+ ...params.offerAsset !== void 0 ? { offerAsset: params.offerAsset } : {},
1787
+ ...params.wantAsset !== void 0 ? { wantAsset: params.wantAsset } : {},
1788
+ amount: params.amount,
1789
+ amountSide,
1790
+ makerPkScript,
1791
+ makerPublicKey
1792
+ })
1793
+ );
1794
+ if (quote.pair !== pair) {
1795
+ throw new Error(`solver quoted ${JSON.stringify(quote.pair)}, not the requested ${pair}`);
1796
+ }
1797
+ assertArkadeFundable({ quote, ...params.now !== void 0 ? { now: params.now } : {} });
1798
+ const terms = offerTermsFromQuote(quote, {
1799
+ ...params.offerAsset !== void 0 ? { offerAsset: params.offerAsset } : {},
1800
+ ...params.wantAsset !== void 0 ? { wantAsset: params.wantAsset } : {}
1801
+ });
1802
+ const quoted = amountSide === "from" ? quote.from_amount : quote.to_amount;
1803
+ if (BigInt(quoted).toString() !== canonicalAssetAmount(params.amount)) {
1804
+ throw new Error(
1805
+ `quote ${amountSide}_amount ${quoted} does not match the requested ${canonicalAssetAmount(params.amount)}`
1806
+ );
1807
+ }
1808
+ if (params.maxFromAmount !== void 0) {
1809
+ const cap = BigInt(canonicalAssetAmount(params.maxFromAmount));
1810
+ if (BigInt(quote.from_amount) > cap) {
1811
+ throw gateError(
1812
+ "quote_amount_rejected",
1813
+ `quote from_amount ${quote.from_amount} exceeds maxFromAmount ${cap}`
1814
+ );
1815
+ }
1816
+ }
1817
+ if (params.minToAmount !== void 0) {
1818
+ const floorAmount = BigInt(canonicalAssetAmount(params.minToAmount));
1819
+ if (BigInt(quote.to_amount) < floorAmount) {
1820
+ throw gateError(
1821
+ "quote_amount_rejected",
1822
+ `quote to_amount ${quote.to_amount} is under minToAmount ${floorAmount}`
1823
+ );
1824
+ }
1825
+ }
1826
+ const offer = await createOffer(wallet, arkServerUrl, {
1827
+ wantAmount: terms.wantAmount,
1828
+ ...terms.wantAsset !== void 0 ? { wantAsset: terms.wantAsset } : {},
1829
+ ...terms.offerAsset !== void 0 ? { offerAsset: terms.offerAsset } : {},
1830
+ ...params.emulatorPubkey !== void 0 ? { emulatorPubkey: params.emulatorPubkey } : {}
1831
+ });
1832
+ verifyOfferAddress(quote, offer);
1833
+ const fundAmount = BigInt(quote.from_amount);
1834
+ return {
1835
+ rfqId,
1836
+ quote,
1837
+ pair,
1838
+ address: offer.address,
1839
+ fundAmount,
1840
+ carrierSats: params.offerAsset !== void 0 ? quoteCarrierSats(quote) : 0n,
1841
+ swapPkScript: offer.swapPkScript,
1842
+ offerHex: offer.offerHex,
1843
+ extension: offer.extension
1844
+ };
1845
+ }
926
1846
  var l1NetworkFromArk = (network) => network === "bitcoin" ? "bitcoin" : network === "regtest" ? "regtest" : "testnet";
927
1847
  var onchainSendRequest = (input) => ({
928
1848
  v: 1,
@@ -933,9 +1853,9 @@ var onchainSendRequest = (input) => ({
933
1853
  amount: input.amount,
934
1854
  profile: {
935
1855
  payment_hash: input.paymentHash,
936
- payout_pubkey: hex3.encode(input.payoutPubkey),
1856
+ payout_pubkey: hex5.encode(input.payoutPubkey),
937
1857
  refund_address: input.refundAddress,
938
- client_refund_pubkey: hex3.encode(input.senderPubkey)
1858
+ client_refund_pubkey: hex5.encode(input.senderPubkey)
939
1859
  }
940
1860
  });
941
1861
  var lightningReceiveRequest = (input) => ({
@@ -948,7 +1868,7 @@ var lightningReceiveRequest = (input) => ({
948
1868
  profile: {
949
1869
  payment_hash: input.paymentHash,
950
1870
  payout_address: input.payoutAddress,
951
- payout_pubkey: hex3.encode(input.payoutPubkey),
1871
+ payout_pubkey: hex5.encode(input.payoutPubkey),
952
1872
  ...input.claimPacket === void 0 ? {} : { claim_packet: input.claimPacket }
953
1873
  }
954
1874
  });
@@ -962,9 +1882,9 @@ var onchainReceiveRequest = (input) => ({
962
1882
  profile: {
963
1883
  payment_hash: input.paymentHash,
964
1884
  ...input.claimPacket === void 0 ? {} : { claim_packet: input.claimPacket },
965
- refund_pubkey: hex3.encode(input.refundPubkey),
1885
+ refund_pubkey: hex5.encode(input.refundPubkey),
966
1886
  payout_address: input.payoutAddress,
967
- payout_pubkey: hex3.encode(input.payoutPubkey)
1887
+ payout_pubkey: hex5.encode(input.payoutPubkey)
968
1888
  }
969
1889
  });
970
1890
  function deriveOnchainSend(input) {
@@ -980,7 +1900,7 @@ function deriveOnchainSend(input) {
980
1900
  throw new Error("onchain-send quote is missing a binding field");
981
1901
  }
982
1902
  const treeParams = {
983
- solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
1903
+ solverPubkey: toXOnly(hex5.decode(quote.solver_pubkey), "solver key"),
984
1904
  refundLocktime,
985
1905
  serverPubkey: input.serverPubkey,
986
1906
  paymentHash: input.paymentHash,
@@ -988,7 +1908,7 @@ function deriveOnchainSend(input) {
988
1908
  emulatorPubkey: input.emulatorPubkey,
989
1909
  senderPubkey: input.senderPubkey,
990
1910
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
991
- refundPkScript: ArkAddress2.decode(input.refundAddress).pkScript
1911
+ refundPkScript: ArkAddress3.decode(input.refundAddress).pkScript
992
1912
  };
993
1913
  const { script, address } = matchQuotedLockup(
994
1914
  quote,
@@ -999,7 +1919,7 @@ function deriveOnchainSend(input) {
999
1919
  const htlcParams = {
1000
1920
  paymentHash: input.paymentHash,
1001
1921
  claimKey: input.payoutPubkey,
1002
- refundKey: toXOnly(hex3.decode(htlcPubkey), "solver L1 htlc key"),
1922
+ refundKey: toXOnly(hex5.decode(htlcPubkey), "solver L1 htlc key"),
1003
1923
  refundLocktime: htlcLocktime
1004
1924
  };
1005
1925
  const htlc = onchainHtlcScript(htlcParams, input.l1Network);
@@ -1013,7 +1933,8 @@ function deriveOnchainSend(input) {
1013
1933
  l1Network: input.l1Network,
1014
1934
  refundLocktime,
1015
1935
  htlcLocktime,
1016
- minConfirmations
1936
+ minConfirmations,
1937
+ expectedAmount: quoteSats(quote.to_amount, "to_amount")
1017
1938
  };
1018
1939
  }
1019
1940
  async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
@@ -1024,10 +1945,10 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
1024
1945
  "[swap] this swap's preimage cannot be re-derived from the seed and MUST be persisted with the record before funding"
1025
1946
  );
1026
1947
  }
1027
- const paymentHash = hex3.encode(secrets.paymentHash);
1948
+ const paymentHash = hex5.encode(secrets.paymentHash);
1028
1949
  const senderPubkey = secrets.pubkey;
1029
1950
  const [info, refundAddress] = await Promise.all([
1030
- new RestArkProvider(arkServerUrl).getInfo(),
1951
+ new RestArkProvider2(arkServerUrl).getInfo(),
1031
1952
  wallet.getAddress()
1032
1953
  ]);
1033
1954
  const quote = await transport.requestQuote(
@@ -1042,14 +1963,14 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
1042
1963
  })
1043
1964
  );
1044
1965
  assertQuotedAmount(quote, params.amountSide, params.amount);
1045
- const network = getNetwork(info.network);
1966
+ const network = getNetwork2(info.network);
1046
1967
  const derived = deriveOnchainSend({
1047
1968
  quote,
1048
1969
  paymentHash,
1049
1970
  payoutPubkey: params.payoutPubkey,
1050
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
1971
+ serverPubkey: toXOnly(hex5.decode(info.signerPubkey), "ark signer key"),
1051
1972
  emulatorPubkey: toXOnly(
1052
- hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
1973
+ hex5.decode(resolveEmulatorPubkey2(network, params.emulatorPubkey)),
1053
1974
  "emulator signer key"
1054
1975
  ),
1055
1976
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -1076,7 +1997,8 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
1076
1997
  rfqId,
1077
1998
  quote,
1078
1999
  address: derived.address,
1079
- fundAmount: quote.from_amount,
2000
+ fundAmount: quoteSats(quote.from_amount, "from_amount"),
2001
+ expectedAmount: quoteSats(quote.to_amount, "to_amount"),
1080
2002
  swapPkScript: derived.swapPkScript,
1081
2003
  script: derived.script,
1082
2004
  refundAddress,
@@ -1089,7 +2011,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
1089
2011
  };
1090
2012
  }
1091
2013
  var assertQuotedAmount = (quote, amountSide, amount) => {
1092
- const quoted = amountSide === "from" ? quote.from_amount : quote.to_amount;
2014
+ const quoted = amountSide === "from" ? quoteSats(quote.from_amount, "from_amount") : quoteSats(quote.to_amount, "to_amount");
1093
2015
  if (quoted !== amount) {
1094
2016
  throw new Error(
1095
2017
  `quote ${amountSide === "from" ? "from_amount" : "to_amount"} ${quoted} does not match the requested ${amount} \u2014 not this trade's quote`
@@ -1148,7 +2070,7 @@ var assertReceivable = (input) => {
1148
2070
  `a payment at the deadline would leave under ${minClaimWindow}s to claim before the solver's refund opens`
1149
2071
  );
1150
2072
  }
1151
- if (input.maxPayAmount !== void 0 && input.quote.from_amount > input.maxPayAmount) {
2073
+ if (input.maxPayAmount !== void 0 && quoteSats(input.quote.from_amount, "from_amount") > input.maxPayAmount) {
1152
2074
  throw gateError(
1153
2075
  "price_too_high",
1154
2076
  `quote asks ${input.quote.from_amount} sats, above the ${input.maxPayAmount} ceiling`
@@ -1164,7 +2086,7 @@ function receiveVtxoScript(params) {
1164
2086
  sender: params.solverPubkey,
1165
2087
  receiver: params.payoutPubkey,
1166
2088
  server: params.serverPubkey,
1167
- preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
2089
+ preimageHash: ripemd1602(hex5.decode(params.paymentHash)),
1168
2090
  refundLocktime: BigInt(params.refundLocktime),
1169
2091
  unilateralClaimDelay: seconds(params.claimDelay),
1170
2092
  unilateralRefundDelay: seconds(unilateralRefundDelay(params.claimDelay)),
@@ -1189,7 +2111,7 @@ function deriveLightningReceive(input) {
1189
2111
  throw new Error("lightning-receive quote is missing a binding field");
1190
2112
  }
1191
2113
  const treeParams = {
1192
- solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
2114
+ solverPubkey: toXOnly(hex5.decode(quote.solver_pubkey), "solver key"),
1193
2115
  refundLocktime,
1194
2116
  serverPubkey: input.serverPubkey,
1195
2117
  paymentHash: input.paymentHash,
@@ -1197,7 +2119,7 @@ function deriveLightningReceive(input) {
1197
2119
  emulatorPubkey: input.emulatorPubkey,
1198
2120
  solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
1199
2121
  payoutPubkey: input.payoutPubkey,
1200
- payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
2122
+ payoutPkScript: ArkAddress3.decode(input.payoutAddress).pkScript
1201
2123
  };
1202
2124
  const matched = matchQuotedLockup(
1203
2125
  quote,
@@ -1226,10 +2148,10 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1226
2148
  );
1227
2149
  }
1228
2150
  const preimage = secrets.preimage;
1229
- const paymentHash = hex3.encode(secrets.paymentHash);
2151
+ const paymentHash = hex5.encode(secrets.paymentHash);
1230
2152
  const payoutPubkey = secrets.pubkey;
1231
2153
  const [info, payoutAddress] = await Promise.all([
1232
- new RestArkProvider(arkServerUrl).getInfo(),
2154
+ new RestArkProvider2(arkServerUrl).getInfo(),
1233
2155
  wallet.getAddress()
1234
2156
  ]);
1235
2157
  const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
@@ -1248,15 +2170,15 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1248
2170
  })
1249
2171
  );
1250
2172
  assertQuotedAmount(quote, params.amountSide, params.amount);
1251
- const network = getNetwork(info.network);
2173
+ const network = getNetwork2(info.network);
1252
2174
  const derived = deriveLightningReceive({
1253
2175
  quote,
1254
2176
  paymentHash,
1255
2177
  payoutPubkey,
1256
2178
  payoutAddress,
1257
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
2179
+ serverPubkey: toXOnly(hex5.decode(info.signerPubkey), "ark signer key"),
1258
2180
  emulatorPubkey: toXOnly(
1259
- hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
2181
+ hex5.decode(resolveEmulatorPubkey2(network, params.emulatorPubkey)),
1260
2182
  "emulator signer key"
1261
2183
  ),
1262
2184
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -1279,8 +2201,8 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
1279
2201
  rfqId,
1280
2202
  quote,
1281
2203
  invoice: derived.invoice,
1282
- payAmount: quote.from_amount,
1283
- expectedAmount: quote.to_amount,
2204
+ payAmount: quoteSats(quote.from_amount, "from_amount"),
2205
+ expectedAmount: quoteSats(quote.to_amount, "to_amount"),
1284
2206
  invoiceExpiresAt: payDeadline,
1285
2207
  address: derived.address,
1286
2208
  swapPkScript: derived.swapPkScript,
@@ -1304,7 +2226,7 @@ function deriveOnchainReceive(input) {
1304
2226
  throw new Error("onchain-receive quote is missing a binding field");
1305
2227
  }
1306
2228
  const treeParams = {
1307
- solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
2229
+ solverPubkey: toXOnly(hex5.decode(quote.solver_pubkey), "solver key"),
1308
2230
  refundLocktime,
1309
2231
  serverPubkey: input.serverPubkey,
1310
2232
  paymentHash: input.paymentHash,
@@ -1312,7 +2234,7 @@ function deriveOnchainReceive(input) {
1312
2234
  emulatorPubkey: input.emulatorPubkey,
1313
2235
  solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
1314
2236
  payoutPubkey: input.payoutPubkey,
1315
- payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
2237
+ payoutPkScript: ArkAddress3.decode(input.payoutAddress).pkScript
1316
2238
  };
1317
2239
  const { script, address } = matchQuotedLockup(
1318
2240
  quote,
@@ -1323,7 +2245,7 @@ function deriveOnchainReceive(input) {
1323
2245
  const htlc = onchainHtlcScript(
1324
2246
  {
1325
2247
  paymentHash: input.paymentHash,
1326
- claimKey: toXOnly(hex3.decode(claimPubkey), "solver L1 claim key"),
2248
+ claimKey: toXOnly(hex5.decode(claimPubkey), "solver L1 claim key"),
1327
2249
  refundKey: input.refundPubkey,
1328
2250
  refundLocktime: htlcLocktime
1329
2251
  },
@@ -1349,10 +2271,10 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1349
2271
  );
1350
2272
  }
1351
2273
  const preimage = secrets.preimage;
1352
- const paymentHash = hex3.encode(secrets.paymentHash);
2274
+ const paymentHash = hex5.encode(secrets.paymentHash);
1353
2275
  const payoutPubkey = secrets.pubkey;
1354
2276
  const [info, payoutAddress] = await Promise.all([
1355
- new RestArkProvider(arkServerUrl).getInfo(),
2277
+ new RestArkProvider2(arkServerUrl).getInfo(),
1356
2278
  wallet.getAddress()
1357
2279
  ]);
1358
2280
  const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
@@ -1372,16 +2294,16 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1372
2294
  })
1373
2295
  );
1374
2296
  assertQuotedAmount(quote, params.amountSide, params.amount);
1375
- const network = getNetwork(info.network);
2297
+ const network = getNetwork2(info.network);
1376
2298
  const derived = deriveOnchainReceive({
1377
2299
  quote,
1378
2300
  paymentHash,
1379
2301
  payoutPubkey,
1380
2302
  payoutAddress,
1381
2303
  refundPubkey: params.refundPubkey,
1382
- serverPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
2304
+ serverPubkey: toXOnly(hex5.decode(info.signerPubkey), "ark signer key"),
1383
2305
  emulatorPubkey: toXOnly(
1384
- hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
2306
+ hex5.decode(resolveEmulatorPubkey2(network, params.emulatorPubkey)),
1385
2307
  "emulator signer key"
1386
2308
  ),
1387
2309
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -1406,8 +2328,8 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1406
2328
  rfqId,
1407
2329
  quote,
1408
2330
  address: derived.address,
1409
- fundAmount: quote.from_amount,
1410
- expectedAmount: quote.to_amount,
2331
+ fundAmount: quoteSats(quote.from_amount, "from_amount"),
2332
+ expectedAmount: quoteSats(quote.to_amount, "to_amount"),
1411
2333
  swapPkScript: derived.swapPkScript,
1412
2334
  script: derived.script,
1413
2335
  htlc: derived.htlc,
@@ -1418,6 +2340,29 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
1418
2340
  }
1419
2341
 
1420
2342
  export {
2343
+ RETIRABLE,
2344
+ retireOfferContract,
2345
+ retireSettledOfferContracts,
2346
+ BTC_ASSET_ID,
2347
+ getAssetSwapsOrThrow,
2348
+ getAssetSwaps,
2349
+ addAssetSwap,
2350
+ updateAssetSwap,
2351
+ updateAssetSwapBestEffort,
2352
+ swapSecretsToRecord,
2353
+ PreimageNotRecoverableError,
2354
+ preimageForSwapRecord,
2355
+ swapPrograms,
2356
+ offerVtxoScript,
2357
+ OFFER_PACKET_TYPE,
2358
+ encodeOffer,
2359
+ decodeOffer,
2360
+ OFFER_CONTRACT_KIND,
2361
+ restoreOfferCoverage,
2362
+ createOffer,
2363
+ cancelOffer,
2364
+ ASSET_CARRIER_SATS,
2365
+ fillOffer,
1421
2366
  ONCHAIN_ORDER_MARGIN_SECONDS,
1422
2367
  ONCHAIN_CLAIM_MARGIN_SECONDS,
1423
2368
  MAX_MIN_CONFIRMATIONS,
@@ -1465,6 +2410,9 @@ export {
1465
2410
  newRfqId,
1466
2411
  lightningSendRequest,
1467
2412
  arkadeSwapRequest,
2413
+ canonicalAssetAmount,
2414
+ normalizeMakerPkScript,
2415
+ normalizeMakerPublicKey,
1468
2416
  MIN_HEADROOM_SECONDS,
1469
2417
  verifyLockupAddress,
1470
2418
  assertFundable,
@@ -1479,6 +2427,9 @@ export {
1479
2427
  lightningSendVtxoScript,
1480
2428
  requestLightningSend,
1481
2429
  offerTermsFromQuote,
2430
+ verifyOfferAddress,
2431
+ assertArkadeFundable,
2432
+ requestArkadeSwap,
1482
2433
  onchainSendRequest,
1483
2434
  lightningReceiveRequest,
1484
2435
  onchainReceiveRequest,