@arkade-os/sdk 0.4.46 → 0.4.48-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/adapters/expo.cjs +5 -5
  2. package/dist/adapters/expo.d.cts +2 -2
  3. package/dist/adapters/expo.d.ts +2 -2
  4. package/dist/adapters/expo.js +3 -3
  5. package/dist/adapters/indexedDB.cjs +5 -5
  6. package/dist/adapters/indexedDB.js +4 -4
  7. package/dist/{ark-C9ognsOV.d.cts → ark-CoJgwTi_.d.cts} +26 -2
  8. package/dist/{ark-C9ognsOV.d.ts → ark-CoJgwTi_.d.ts} +26 -2
  9. package/dist/{asyncStorageTaskQueue-CmBYVlxw.d.cts → asyncStorageTaskQueue-CwknfM_j.d.cts} +1 -1
  10. package/dist/{asyncStorageTaskQueue-BAKZ5cS5.d.ts → asyncStorageTaskQueue-D2Zl7yAB.d.ts} +1 -1
  11. package/dist/{chunk-SOXGAA52.cjs → chunk-3HUZAIKI.cjs} +14 -14
  12. package/dist/{chunk-SOXGAA52.cjs.map → chunk-3HUZAIKI.cjs.map} +1 -1
  13. package/dist/{chunk-R2SVAM5Q.js → chunk-4AIHKC65.js} +3 -3
  14. package/dist/{chunk-R2SVAM5Q.js.map → chunk-4AIHKC65.js.map} +1 -1
  15. package/dist/{chunk-FWG4TSPN.js → chunk-C7TXXTMS.js} +4 -5
  16. package/dist/chunk-C7TXXTMS.js.map +1 -0
  17. package/dist/{chunk-7IYIFMF4.cjs → chunk-CFQO5MCM.cjs} +94 -20
  18. package/dist/chunk-CFQO5MCM.cjs.map +1 -0
  19. package/dist/{chunk-6TQJOD72.cjs → chunk-EA3IWZ4W.cjs} +7 -7
  20. package/dist/{chunk-6TQJOD72.cjs.map → chunk-EA3IWZ4W.cjs.map} +1 -1
  21. package/dist/{chunk-CWJ7ZCP7.js → chunk-GM6JRQP5.js} +87 -16
  22. package/dist/chunk-GM6JRQP5.js.map +1 -0
  23. package/dist/{chunk-MQB5H4X5.cjs → chunk-L5ND2FJJ.cjs} +4 -5
  24. package/dist/chunk-L5ND2FJJ.cjs.map +1 -0
  25. package/dist/{chunk-D46YDM36.cjs → chunk-RJMFTTRJ.cjs} +1586 -286
  26. package/dist/chunk-RJMFTTRJ.cjs.map +1 -0
  27. package/dist/chunk-RQSA7ADZ.cjs +2286 -0
  28. package/dist/chunk-RQSA7ADZ.cjs.map +1 -0
  29. package/dist/{chunk-P6VBP3CT.js → chunk-SV4PCGKM.js} +1388 -100
  30. package/dist/chunk-SV4PCGKM.js.map +1 -0
  31. package/dist/chunk-XN3554FT.js +2244 -0
  32. package/dist/chunk-XN3554FT.js.map +1 -0
  33. package/dist/{chunk-JTCQ6QXQ.js → chunk-ZRY2KSK5.js} +4 -4
  34. package/dist/{chunk-JTCQ6QXQ.js.map → chunk-ZRY2KSK5.js.map} +1 -1
  35. package/dist/contracts/handlers/index.cjs +11 -7
  36. package/dist/contracts/handlers/index.d.cts +3 -3
  37. package/dist/contracts/handlers/index.d.ts +3 -3
  38. package/dist/contracts/handlers/index.js +2 -2
  39. package/dist/{delegate-aOd5qINr.d.ts → delegate-CuaDqqQi.d.ts} +1 -1
  40. package/dist/{delegate-DVEgyRwI.d.cts → delegate-Dl87_zMA.d.cts} +1 -1
  41. package/dist/index-BC_G0VTh.d.cts +512 -0
  42. package/dist/index-CKwpZEpw.d.ts +512 -0
  43. package/dist/index.cjs +221 -161
  44. package/dist/index.d.cts +3129 -2089
  45. package/dist/index.d.ts +3129 -2089
  46. package/dist/index.js +4 -4
  47. package/dist/repositories/realm/index.cjs +19 -19
  48. package/dist/repositories/realm/index.d.cts +2 -2
  49. package/dist/repositories/realm/index.d.ts +2 -2
  50. package/dist/repositories/realm/index.js +5 -5
  51. package/dist/repositories/sqlite/index.cjs +18 -18
  52. package/dist/repositories/sqlite/index.d.cts +2 -2
  53. package/dist/repositories/sqlite/index.d.ts +2 -2
  54. package/dist/repositories/sqlite/index.js +5 -5
  55. package/dist/{taskRunner-B48-V-Pb.d.ts → taskRunner-Df8YQpn1.d.ts} +1 -1
  56. package/dist/{taskRunner-nNecmQ2C.d.cts → taskRunner-RYihSj9E.d.cts} +1 -1
  57. package/dist/wallet/expo/background.cjs +14 -14
  58. package/dist/wallet/expo/background.d.cts +3 -3
  59. package/dist/wallet/expo/background.d.ts +3 -3
  60. package/dist/wallet/expo/background.js +6 -6
  61. package/dist/wallet/expo/index.cjs +14 -14
  62. package/dist/wallet/expo/index.d.cts +4 -4
  63. package/dist/wallet/expo/index.d.ts +4 -4
  64. package/dist/wallet/expo/index.js +5 -5
  65. package/dist/{wallet-BUg3D4WJ.d.cts → wallet-B6s7_5bJ.d.cts} +136 -3
  66. package/dist/{wallet-DM0oShTu.d.ts → wallet-C61huH8i.d.ts} +136 -3
  67. package/dist/worker/expo/index.cjs +9 -9
  68. package/dist/worker/expo/index.d.cts +4 -4
  69. package/dist/worker/expo/index.d.ts +4 -4
  70. package/dist/worker/expo/index.js +5 -5
  71. package/package.json +1 -1
  72. package/dist/chunk-4KXOCVXP.cjs +0 -973
  73. package/dist/chunk-4KXOCVXP.cjs.map +0 -1
  74. package/dist/chunk-7IYIFMF4.cjs.map +0 -1
  75. package/dist/chunk-CWJ7ZCP7.js.map +0 -1
  76. package/dist/chunk-D46YDM36.cjs.map +0 -1
  77. package/dist/chunk-FWG4TSPN.js.map +0 -1
  78. package/dist/chunk-MQB5H4X5.cjs.map +0 -1
  79. package/dist/chunk-P6VBP3CT.js.map +0 -1
  80. package/dist/chunk-QKC2T54O.js +0 -960
  81. package/dist/chunk-QKC2T54O.js.map +0 -1
  82. package/dist/index-CPzL4tqg.d.ts +0 -259
  83. package/dist/index-D8vmtWWO.d.cts +0 -259
@@ -1,11 +1,12 @@
1
- import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, baseFetch, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-CWJ7ZCP7.js';
2
- import { contractHandlers, isMainnetDescriptor, descriptorIsOurs, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-QKC2T54O.js';
3
- import { VtxoScript, sequenceToTimelock, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-FWG4TSPN.js';
4
- import { sha256, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr, equalBytes as equalBytes$1 } from '@scure/btc-signer/utils.js';
5
- import { SigHash, Script, p2tr, RawWitness, Address, OutScript, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
6
- import { hex, base58, base64 } from '@scure/base';
1
+ import { Transaction, setArkPsbtField, PrevArkTxField, ConditionWitness, AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, craftToSpendTx, getArkPsbtFields, CosignerPublicKey, BufferReader, BufferWriter, OP_RETURN_EMPTY_PKSCRIPT, VtxoTaprootTree, baseFetch, Intent, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError } from './chunk-GM6JRQP5.js';
2
+ import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, DefaultVtxo, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, BoardingContractHandler } from './chunk-XN3554FT.js';
3
+ import { VtxoScript, CSVMultisigTapscript, DEFAULT_NETWORK, ArkAddress, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, timelockToSequence, DEFAULT_NETWORK_NAME, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-C7TXXTMS.js';
4
+ import { __export } from './chunk-NSBPE2FW.js';
5
+ import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
6
+ import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
7
+ import { hex, base58, base64, bech32m } from '@scure/base';
7
8
  import * as musig2 from '@scure/btc-signer/musig2.js';
8
- import { equalBytes, bytesToNumberBE } from '@noble/curves/utils.js';
9
+ import { equalBytes as equalBytes$1, bytesToNumberBE } from '@noble/curves/utils.js';
9
10
  import { signAsync, schnorr as schnorr$1, Point } from '@noble/secp256k1';
10
11
  import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
11
12
  import { Script as Script$1 } from '@scure/btc-signer/script.js';
@@ -1512,6 +1513,62 @@ var UnknownPacket = class {
1512
1513
  }
1513
1514
  };
1514
1515
 
1516
+ // src/extension/emulator/packet.ts
1517
+ var EmulatorPacket = class _EmulatorPacket {
1518
+ constructor(entries) {
1519
+ this.entries = entries;
1520
+ }
1521
+ entries;
1522
+ /** PACKET_TYPE is the 1-byte TLV type tag used in the Extension envelope. */
1523
+ static PACKET_TYPE = 1;
1524
+ static create(entries) {
1525
+ if (entries.length === 0) {
1526
+ throw new Error("empty emulator packet");
1527
+ }
1528
+ for (const entry of entries) {
1529
+ if (entry.script.length === 0) {
1530
+ throw new Error(`empty script for vin ${entry.vin}`);
1531
+ }
1532
+ }
1533
+ const seen = /* @__PURE__ */ new Set();
1534
+ for (const entry of entries) {
1535
+ if (seen.has(entry.vin)) {
1536
+ throw new Error(`duplicate vin ${entry.vin}`);
1537
+ }
1538
+ seen.add(entry.vin);
1539
+ }
1540
+ return new _EmulatorPacket(entries);
1541
+ }
1542
+ static fromBytes(data) {
1543
+ const reader = new BufferReader(data);
1544
+ const entryCount = reader.readCompactSize();
1545
+ const entries = [];
1546
+ for (let i = 0; i < entryCount; i++) {
1547
+ const vin = reader.readUint16LE();
1548
+ const script = reader.readCompactSlice();
1549
+ const witness = reader.readCompactSlice();
1550
+ entries.push({ vin, script, witness });
1551
+ }
1552
+ if (reader.remaining() > 0) {
1553
+ throw new Error(`unexpected ${reader.remaining()} trailing bytes`);
1554
+ }
1555
+ return _EmulatorPacket.create(entries);
1556
+ }
1557
+ type() {
1558
+ return _EmulatorPacket.PACKET_TYPE;
1559
+ }
1560
+ serialize() {
1561
+ const writer = new BufferWriter();
1562
+ writer.writeCompactSize(this.entries.length);
1563
+ for (const entry of this.entries) {
1564
+ writer.writeUint16LE(entry.vin);
1565
+ writer.writeCompactSlice(entry.script);
1566
+ writer.writeCompactSlice(entry.witness ?? new Uint8Array(0));
1567
+ }
1568
+ return writer.toBytes();
1569
+ }
1570
+ };
1571
+
1515
1572
  // src/extension/index.ts
1516
1573
  var ARKADE_MAGIC = new Uint8Array([65, 82, 75]);
1517
1574
  var ExtensionNotFoundError = class extends Error {
@@ -1531,10 +1588,14 @@ var Extension = class _Extension {
1531
1588
  }
1532
1589
  const seen = /* @__PURE__ */ new Set();
1533
1590
  for (const p of packets) {
1534
- if (seen.has(p.type())) {
1535
- throw new Error(`duplicate packet type ${p.type()}`);
1591
+ const type = p.type();
1592
+ if (!Number.isInteger(type) || type < 0 || type > 255) {
1593
+ throw new Error(`invalid packet type ${type}: must be an integer in [0, 255]`);
1536
1594
  }
1537
- seen.add(p.type());
1595
+ if (seen.has(type)) {
1596
+ throw new Error(`duplicate packet type ${type}`);
1597
+ }
1598
+ seen.add(type);
1538
1599
  }
1539
1600
  return new _Extension(packets);
1540
1601
  }
@@ -1548,7 +1609,7 @@ var Extension = class _Extension {
1548
1609
  if (decoded.length < 2 || decoded[0] !== "RETURN") return false;
1549
1610
  const data = decoded[1];
1550
1611
  if (!(data instanceof Uint8Array)) return false;
1551
- return data.length >= ARKADE_MAGIC.length && equalBytes$1(data.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC);
1612
+ return data.length >= ARKADE_MAGIC.length && equalBytes(data.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC);
1552
1613
  } catch {
1553
1614
  return false;
1554
1615
  }
@@ -1579,7 +1640,7 @@ var Extension = class _Extension {
1579
1640
  payload.set(d, offset);
1580
1641
  offset += d.length;
1581
1642
  }
1582
- if (payload.length < ARKADE_MAGIC.length || !equalBytes$1(payload.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC)) {
1643
+ if (payload.length < ARKADE_MAGIC.length || !equalBytes(payload.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC)) {
1583
1644
  throw new Error(
1584
1645
  `expected magic prefix ${hex.encode(ARKADE_MAGIC)}, got ${hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
1585
1646
  );
@@ -1664,12 +1725,45 @@ var Extension = class _Extension {
1664
1725
  }
1665
1726
  return null;
1666
1727
  }
1728
+ /**
1729
+ * getEmulatorPacket returns the embedded EmulatorPacket, or null if not present.
1730
+ */
1731
+ getEmulatorPacket() {
1732
+ for (const p of this.packets) {
1733
+ if (p instanceof EmulatorPacket) {
1734
+ return p;
1735
+ }
1736
+ }
1737
+ return null;
1738
+ }
1739
+ /**
1740
+ * getPacketByType returns the first packet matching the given type tag, or null.
1741
+ */
1742
+ getPacketByType(packetType) {
1743
+ for (const p of this.packets) {
1744
+ if (p.type() === packetType) {
1745
+ return p;
1746
+ }
1747
+ }
1748
+ return null;
1749
+ }
1750
+ /**
1751
+ * Returns all embedded packets in insertion order. Used when callers need
1752
+ * to rebuild an Extension from an existing one (e.g. appending a new packet).
1753
+ */
1754
+ getPackets() {
1755
+ return this.packets;
1756
+ }
1667
1757
  };
1668
1758
  function parsePacket(packetType, data) {
1669
- if (packetType === Packet.PACKET_TYPE) {
1670
- return Packet.fromBytes(data);
1759
+ switch (packetType) {
1760
+ case Packet.PACKET_TYPE:
1761
+ return Packet.fromBytes(data);
1762
+ case EmulatorPacket.PACKET_TYPE:
1763
+ return EmulatorPacket.fromBytes(data);
1764
+ default:
1765
+ return new UnknownPacket(packetType, data);
1671
1766
  }
1672
- return new UnknownPacket(packetType, data);
1673
1767
  }
1674
1768
  function encodeVarUint(value) {
1675
1769
  const bytes = [];
@@ -1750,7 +1844,7 @@ function validateOnchainRecipient(commitmentTx, recipient, network, usedOutputs)
1750
1844
  if (!output?.script || output.script.length === 0) {
1751
1845
  continue;
1752
1846
  }
1753
- if (equalBytes$1(output.script, expectedPkScript)) {
1847
+ if (equalBytes(output.script, expectedPkScript)) {
1754
1848
  if (output.amount !== BigInt(recipient.amount)) {
1755
1849
  continue;
1756
1850
  }
@@ -1774,7 +1868,7 @@ function validateOffchainRecipient(leaves, arkAddress, recipient, usedOutputs) {
1774
1868
  if (!output?.script || output.script.length === 0) {
1775
1869
  continue;
1776
1870
  }
1777
- if (!equalBytes$1(output.script, expectedPkScript)) {
1871
+ if (!equalBytes(output.script, expectedPkScript)) {
1778
1872
  continue;
1779
1873
  }
1780
1874
  if (output.amount !== expectedAmount) {
@@ -2303,6 +2397,55 @@ function isValidArkAddress(address) {
2303
2397
  return false;
2304
2398
  }
2305
2399
  }
2400
+ async function submitOffchainTx(provider, offchainTx, signer, hooks) {
2401
+ const { arkTx: signedArkTx, userSignedCheckpoints } = await signer.signArkTx(
2402
+ offchainTx.arkTx,
2403
+ offchainTx.checkpoints
2404
+ );
2405
+ if (userSignedCheckpoints && userSignedCheckpoints.length !== offchainTx.checkpoints.length) {
2406
+ throw new Error(
2407
+ `signer returned ${userSignedCheckpoints.length} signed checkpoints, expected ${offchainTx.checkpoints.length}`
2408
+ );
2409
+ }
2410
+ await hooks?.beforeSubmit?.();
2411
+ const { arkTxid, signedCheckpointTxs } = await provider.submitTx(
2412
+ base64.encode(signedArkTx.toPSBT()),
2413
+ offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
2414
+ );
2415
+ if (signedCheckpointTxs.length !== offchainTx.checkpoints.length) {
2416
+ throw new Error(
2417
+ `submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${offchainTx.checkpoints.length}`
2418
+ );
2419
+ }
2420
+ let finalCheckpoints;
2421
+ if (userSignedCheckpoints) {
2422
+ finalCheckpoints = signedCheckpointTxs.map((c, i) => {
2423
+ const serverSigned = Transaction.fromPSBT(base64.decode(c));
2424
+ combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
2425
+ return base64.encode(serverSigned.toPSBT());
2426
+ });
2427
+ } else {
2428
+ finalCheckpoints = await Promise.all(
2429
+ signedCheckpointTxs.map(async (c) => {
2430
+ const tx = Transaction.fromPSBT(base64.decode(c));
2431
+ const signed = await signer.signCheckpoint(tx);
2432
+ return base64.encode(signed.toPSBT());
2433
+ })
2434
+ );
2435
+ }
2436
+ await provider.finalizeTx(arkTxid, finalCheckpoints);
2437
+ await hooks?.afterFinalize?.();
2438
+ return { arkTxid, signedCheckpointTxs };
2439
+ }
2440
+ async function signAndSubmitOffchainTx(params) {
2441
+ const offchainTx = buildOffchainTx(params.inputs, params.outputs, params.serverUnrollScript);
2442
+ const signer = {
2443
+ signArkTx: async (arkTx) => ({ arkTx: await params.identity.sign(arkTx) }),
2444
+ signCheckpoint: (checkpoint) => params.identity.sign(checkpoint)
2445
+ };
2446
+ const { arkTxid } = await submitOffchainTx(params.provider, offchainTx, signer);
2447
+ return arkTxid;
2448
+ }
2306
2449
  var AmountVariableName = "amount";
2307
2450
  var ExpiryVariableName = "expiry";
2308
2451
  var BirthVariableName = "birth";
@@ -2458,6 +2601,14 @@ function parseProgram(text, env) {
2458
2601
  }
2459
2602
  return { program, text };
2460
2603
  }
2604
+
2605
+ // src/contracts/types.ts
2606
+ function isDiscoverable(handler) {
2607
+ return !!handler && typeof handler.discoverAt === "function";
2608
+ }
2609
+ function isTapscriptDeriving(handler) {
2610
+ return !!handler && typeof handler.deriveTapscripts === "function";
2611
+ }
2461
2612
  var DUST_AMOUNT = 546;
2462
2613
  var FALLBACK_WALLET_DUST_AMOUNT = 330n;
2463
2614
  function getDustAmount(wallet) {
@@ -2477,10 +2628,14 @@ function deriveContractTapscripts(contract) {
2477
2628
  throw new Error(`No handler for contract type '${contract.type}'`);
2478
2629
  }
2479
2630
  const script = handler.createScript(contract.params);
2631
+ if (isTapscriptDeriving(handler)) {
2632
+ return handler.deriveTapscripts(script, contract);
2633
+ }
2634
+ const legacy = script;
2480
2635
  return {
2481
- forfeitTapLeafScript: script.forfeit(),
2482
- intentTapLeafScript: script.forfeit(),
2483
- tapTree: script.encode()
2636
+ forfeitTapLeafScript: legacy.forfeit(),
2637
+ intentTapLeafScript: legacy.forfeit(),
2638
+ tapTree: legacy.encode()
2484
2639
  };
2485
2640
  }
2486
2641
  function cloneTapLeafScript([
@@ -2598,6 +2753,7 @@ async function runWithCrossInstanceLock(name, fn) {
2598
2753
  });
2599
2754
  }
2600
2755
  var MAX_VTXOS_PER_SETTLEMENT = 50;
2756
+ var MAX_INPUTS_PER_INTENT = 20;
2601
2757
  function byValueDescending(vtxos) {
2602
2758
  return [...vtxos].sort((a, b) => b.value - a.value);
2603
2759
  }
@@ -4187,6 +4343,96 @@ function readUInt32BE(array, offset) {
4187
4343
  function noteTapscript(preimageHash) {
4188
4344
  return Script.encode(["SHA256", preimageHash, "EQUAL"]);
4189
4345
  }
4346
+ var ArkCash = class _ArkCash {
4347
+ constructor(privateKey, serverPubKey, csvTimelock, hrp = _ArkCash.DefaultHRP) {
4348
+ this.csvTimelock = csvTimelock;
4349
+ this.hrp = hrp;
4350
+ if (privateKey.length !== 32) {
4351
+ throw new Error(
4352
+ `Invalid private key length: expected 32 bytes, got ${privateKey.length}`
4353
+ );
4354
+ }
4355
+ if (serverPubKey.length !== 32) {
4356
+ throw new Error(
4357
+ `Invalid server public key length: expected 32 bytes, got ${serverPubKey.length}`
4358
+ );
4359
+ }
4360
+ this._privateKey = privateKey.slice();
4361
+ this._serverPubKey = serverPubKey.slice();
4362
+ this._publicKey = pubSchnorr(this._privateKey);
4363
+ }
4364
+ csvTimelock;
4365
+ hrp;
4366
+ static DefaultHRP = "arkcash";
4367
+ static Version = 0;
4368
+ static PayloadLength = 1 + 32 + 32 + 4;
4369
+ // 69 bytes
4370
+ // Keys are held as private copies so a caller mutating the array it passed
4371
+ // in — or one it reads back from a getter — cannot alter this note's
4372
+ // identity or the outputs derived from it.
4373
+ _privateKey;
4374
+ _serverPubKey;
4375
+ _publicKey;
4376
+ /** The note's private key. Returns a fresh copy; mutating it is a no-op. */
4377
+ get privateKey() {
4378
+ return this._privateKey.slice();
4379
+ }
4380
+ /** The server's public key. Returns a fresh copy; mutating it is a no-op. */
4381
+ get serverPubKey() {
4382
+ return this._serverPubKey.slice();
4383
+ }
4384
+ /** The note's public key. Returns a fresh copy; mutating it is a no-op. */
4385
+ get publicKey() {
4386
+ return this._publicKey.slice();
4387
+ }
4388
+ static generate(serverPubKey, csvTimelock, hrp) {
4389
+ return new _ArkCash(randomPrivateKeyBytes(), serverPubKey, csvTimelock, hrp);
4390
+ }
4391
+ static fromString(encoded) {
4392
+ const decoded = bech32m.decodeUnsafe(encoded.trim().toLowerCase(), 1023);
4393
+ if (!decoded) {
4394
+ throw new Error("Invalid arkcash string: failed to decode bech32m");
4395
+ }
4396
+ const data = new Uint8Array(bech32m.fromWords(decoded.words));
4397
+ if (data.length !== _ArkCash.PayloadLength) {
4398
+ throw new Error(
4399
+ `Invalid arkcash data length: expected ${_ArkCash.PayloadLength} bytes, got ${data.length}`
4400
+ );
4401
+ }
4402
+ const version = data[0];
4403
+ if (version !== _ArkCash.Version) {
4404
+ throw new Error(`Unsupported arkcash version: ${version}`);
4405
+ }
4406
+ const privateKey = data.slice(1, 33);
4407
+ const serverPubKey = data.slice(33, 65);
4408
+ const sequence = new DataView(data.buffer, data.byteOffset + 65, 4).getUint32(0, false);
4409
+ const csvTimelock = sequenceToTimelock(sequence);
4410
+ return new _ArkCash(privateKey, serverPubKey, csvTimelock, decoded.prefix);
4411
+ }
4412
+ toString() {
4413
+ const data = new Uint8Array(_ArkCash.PayloadLength);
4414
+ data[0] = _ArkCash.Version;
4415
+ data.set(this._privateKey, 1);
4416
+ data.set(this._serverPubKey, 33);
4417
+ const sequence = timelockToSequence(this.csvTimelock);
4418
+ new DataView(data.buffer, data.byteOffset + 65, 4).setUint32(0, sequence, false);
4419
+ const words = bech32m.toWords(data);
4420
+ return bech32m.encode(this.hrp, words, 1023);
4421
+ }
4422
+ get identity() {
4423
+ return SingleKey.fromPrivateKey(this._privateKey);
4424
+ }
4425
+ get vtxoScript() {
4426
+ return new DefaultVtxo.Script({
4427
+ pubKey: this._publicKey,
4428
+ serverPubKey: this._serverPubKey,
4429
+ csvTimelock: this.csvTimelock
4430
+ });
4431
+ }
4432
+ address(addressHrp) {
4433
+ return this.vtxoScript.address(addressHrp, this._serverPubKey);
4434
+ }
4435
+ };
4190
4436
 
4191
4437
  // src/repositories/intentRepository.ts
4192
4438
  var ALL_INTENT_STATES = [
@@ -5989,7 +6235,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
5989
6235
  };
5990
6236
  }
5991
6237
  function findDestinationOutputIndex(outputs, destinationScript) {
5992
- return outputs.findIndex((o) => o.script && equalBytes$1(o.script, destinationScript));
6238
+ return outputs.findIndex((o) => o.script && equalBytes(o.script, destinationScript));
5993
6239
  }
5994
6240
  function getDayTimestamp(timestamp) {
5995
6241
  const date = new Date(timestamp);
@@ -7841,11 +8087,6 @@ var ContractRepositoryImpl = class {
7841
8087
  }
7842
8088
  };
7843
8089
 
7844
- // src/contracts/types.ts
7845
- function isDiscoverable(handler) {
7846
- return !!handler && typeof handler.discoverAt === "function";
7847
- }
7848
-
7849
8090
  // src/contracts/contractWatcher.ts
7850
8091
  function computeReconnectDelay(attempt, baseMs, maxMs) {
7851
8092
  return Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
@@ -9495,7 +9736,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
9495
9736
  throw e;
9496
9737
  }
9497
9738
  if (!boot) return void 0;
9498
- const offchainTapscript = equalBytes$1(
9739
+ const offchainTapscript = equalBytes(
9499
9740
  boot.receivePubkey,
9500
9741
  setup.offchainTapscript.options.pubKey
9501
9742
  ) ? setup.offchainTapscript : rebuildTapscript(setup.offchainTapscript, boot.receivePubkey);
@@ -9986,6 +10227,24 @@ async function spendableVtxosExcludingLocked(vtxos, intentRepository) {
9986
10227
  }
9987
10228
  return excludeLockedOutpoints(vtxos, locked);
9988
10229
  }
10230
+ var cashReport = (vtxo, reason) => ({
10231
+ txid: vtxo.txid,
10232
+ vout: vtxo.vout,
10233
+ value: vtxo.value,
10234
+ reason
10235
+ });
10236
+ var ArkCashCreateError = class extends Error {
10237
+ constructor(cash, cause) {
10238
+ super(
10239
+ `Failed to create ArkCash: send failed after the note may have been submitted. Recover with claimCash using the token on this error's \`cash\` field.`
10240
+ );
10241
+ this.cash = cash;
10242
+ this.cause = cause;
10243
+ this.name = "ArkCashCreateError";
10244
+ }
10245
+ cash;
10246
+ cause;
10247
+ };
9989
10248
  var ReadonlyWallet = class _ReadonlyWallet {
9990
10249
  constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegateProvider, watcherConfig, walletContractTimelocks) {
9991
10250
  this.identity = identity;
@@ -11342,7 +11601,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11342
11601
  } catch (e) {
11343
11602
  throw new Error("Invalid checkpointTapscript from server");
11344
11603
  }
11345
- if (equalBytes$1(xonly, this.arkServerPublicKey)) return;
11604
+ if (equalBytes(xonly, this.arkServerPublicKey)) return;
11346
11605
  if (this._receiveRotator) {
11347
11606
  await this._receiveRotator.runExclusive(
11348
11607
  () => this._doRotateServerSigner(xonly, newServerUnrollScript)
@@ -11357,7 +11616,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11357
11616
  return run;
11358
11617
  }
11359
11618
  async _doRotateServerSigner(xonly, newServerUnrollScript) {
11360
- if (equalBytes$1(xonly, this.arkServerPublicKey)) return;
11619
+ if (equalBytes(xonly, this.arkServerPublicKey)) return;
11361
11620
  const manager = await this.getContractManager();
11362
11621
  const [currentOffchainRow] = await manager.getContracts({
11363
11622
  script: this.defaultContractScript
@@ -12445,7 +12704,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12445
12704
  if (!hasPending) {
12446
12705
  return { finalized: [], pending: [] };
12447
12706
  }
12448
- const MAX_INPUTS_PER_INTENT = 20;
12449
12707
  if (!vtxos || vtxos.length === 0) {
12450
12708
  const scriptMap = await this.getScriptMap();
12451
12709
  const allExtended = [];
@@ -12555,6 +12813,265 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12555
12813
  settings: { ...state.settings, hasPendingTx: value }
12556
12814
  }));
12557
12815
  }
12816
+ /**
12817
+ * Create an ArkCash bearer instrument.
12818
+ *
12819
+ * Generates a fresh keypair, sends the specified amount to a DefaultVtxo
12820
+ * controlled by the new key, and returns the encoded arkcash string.
12821
+ * The receiver can claim the funds using `claimCash()` without ever
12822
+ * sharing their address.
12823
+ *
12824
+ * ArkCash is a short-lived instrument: it carries a private key, so it is
12825
+ * meant to be handed over and claimed promptly, not held. A note left
12826
+ * unclaimed past its batch expiry is swept by the server and `claimCash`
12827
+ * can only report it, not move it.
12828
+ *
12829
+ * @param amount - Amount in satoshis to send. Must be a whole number of
12830
+ * sats at or above the dust threshold — a below-dust amount would mint an
12831
+ * OP_RETURN output that is unspendable as cash.
12832
+ * @returns The encoded arkcash string (e.g., "arkcash1...")
12833
+ */
12834
+ async createCash(amount) {
12835
+ if (!Number.isSafeInteger(amount) || amount < Number(this.dustAmount)) {
12836
+ throw new Error(
12837
+ `Invalid ArkCash amount ${amount}: must be a whole number of sats >= dust (${this.dustAmount})`
12838
+ );
12839
+ }
12840
+ const cashHrp = this.network.hrp.replace(/ark$/, "arkcash");
12841
+ const cash = ArkCash.generate(
12842
+ this.arkServerPublicKey,
12843
+ this.offchainTapscript.options.csvTimelock,
12844
+ cashHrp
12845
+ );
12846
+ const address = cash.address(this.network.hrp).encode();
12847
+ const cashStr = cash.toString();
12848
+ try {
12849
+ await this.send({ address, amount });
12850
+ } catch (error) {
12851
+ throw new ArkCashCreateError(cashStr, error);
12852
+ }
12853
+ return cashStr;
12854
+ }
12855
+ /**
12856
+ * Claim an ArkCash bearer instrument: sweep what can be swept, report the
12857
+ * rest.
12858
+ *
12859
+ * Every spendable VTXO at the arkcash address is swept to this wallet in
12860
+ * its own offchain transaction, signed with the key carried by the arkcash
12861
+ * string. Nothing is ever persisted: no contract is imported, and the
12862
+ * arkcash key is not stored. Anything that cannot be swept — server-swept,
12863
+ * subdust, already claimed, or asset-bearing — is returned in `unclaimed`
12864
+ * with a per-VTXO reason and otherwise ignored.
12865
+ *
12866
+ * The arkcash string is the recovery token: a claim interrupted between
12867
+ * submit and finalize is completed by simply re-running `claimCash`, which
12868
+ * drains any pending arkcash transaction on the server before sweeping.
12869
+ *
12870
+ * @param cashStr - The encoded arkcash string (e.g., "arkcash1...")
12871
+ * @returns The swept total and the report of what was left behind. The
12872
+ * shape is open: further buckets may be added alongside `unclaimed`.
12873
+ */
12874
+ async claimCash(cashStr) {
12875
+ const cash = ArkCash.fromString(cashStr);
12876
+ const cashScript = cash.vtxoScript;
12877
+ const cashPkScript = hex.encode(cashScript.pkScript);
12878
+ const scripts = [cashPkScript];
12879
+ let vtxos = await this.fetchAllVtxos(scripts);
12880
+ if (vtxos.length === 0) {
12881
+ throw new Error("No VTXOs found for this arkcash");
12882
+ }
12883
+ const myPkScript = ArkAddress.decode(await this.getAddress()).pkScript;
12884
+ let { spendable, unclaimed } = this.classifyCashVtxos(vtxos);
12885
+ let swept = 0;
12886
+ const drainable = vtxos.filter((vtxo) => !isSubdust(vtxo, this.dustAmount));
12887
+ if (drainable.length > 0) {
12888
+ let drained = { count: 0, swept: 0, claimed: /* @__PURE__ */ new Set() };
12889
+ try {
12890
+ drained = await this.finalizePendingCashTxs(
12891
+ cash,
12892
+ drainable,
12893
+ cashScript,
12894
+ myPkScript
12895
+ );
12896
+ } catch (error) {
12897
+ console.error("Failed to drain pending arkcash txs:", error);
12898
+ }
12899
+ if (drained.count > 0) {
12900
+ vtxos = await this.fetchAllVtxos(scripts);
12901
+ ({ spendable, unclaimed } = this.classifyCashVtxos(vtxos));
12902
+ swept += drained.swept;
12903
+ unclaimed = unclaimed.filter(
12904
+ (vtxo) => !drained.claimed.has(`${vtxo.txid}:${vtxo.vout}`)
12905
+ );
12906
+ }
12907
+ }
12908
+ if (spendable.length > 0) {
12909
+ const info = await this.arkProvider.getInfo();
12910
+ const serverUnrollScript = CSVMultisigTapscript.decode(
12911
+ hex.decode(info.checkpointTapscript)
12912
+ );
12913
+ for (const vtxo of spendable) {
12914
+ try {
12915
+ await signAndSubmitOffchainTx({
12916
+ identity: cash.identity,
12917
+ provider: this.arkProvider,
12918
+ inputs: [
12919
+ {
12920
+ txid: vtxo.txid,
12921
+ vout: vtxo.vout,
12922
+ value: vtxo.value,
12923
+ tapLeafScript: cashScript.forfeit(),
12924
+ tapTree: cashScript.encode()
12925
+ }
12926
+ ],
12927
+ outputs: [{ script: myPkScript, amount: BigInt(vtxo.value) }],
12928
+ serverUnrollScript
12929
+ });
12930
+ swept += vtxo.value;
12931
+ } catch (error) {
12932
+ console.error(`Failed to sweep arkcash VTXO ${vtxo.txid}:${vtxo.vout}:`, error);
12933
+ unclaimed.push(cashReport(vtxo, "sweep-failed"));
12934
+ }
12935
+ }
12936
+ }
12937
+ return {
12938
+ swept,
12939
+ unclaimed: {
12940
+ amount: unclaimed.reduce((total, vtxo) => total + vtxo.value, 0),
12941
+ vtxos: unclaimed
12942
+ }
12943
+ };
12944
+ }
12945
+ /**
12946
+ * Split the VTXOs at an arkcash address into the ones the thin sweep can
12947
+ * move and the ones it can only report.
12948
+ */
12949
+ classifyCashVtxos(vtxos) {
12950
+ const spendable = [];
12951
+ const unclaimed = [];
12952
+ for (const vtxo of vtxos) {
12953
+ if (!isSpendable(vtxo)) {
12954
+ unclaimed.push(cashReport(vtxo, "already-spent"));
12955
+ } else if (isSubdust(vtxo, this.dustAmount)) {
12956
+ unclaimed.push(cashReport(vtxo, "subdust"));
12957
+ } else if (isRecoverable(vtxo)) {
12958
+ unclaimed.push(cashReport(vtxo, "swept"));
12959
+ } else if (vtxo.assets && vtxo.assets.length > 0) {
12960
+ unclaimed.push(cashReport(vtxo, "has-assets"));
12961
+ } else {
12962
+ spendable.push(vtxo);
12963
+ }
12964
+ }
12965
+ return { spendable, unclaimed };
12966
+ }
12967
+ /** Read every page of an unfiltered VTXO query for the given scripts. */
12968
+ async fetchAllVtxos(scripts) {
12969
+ const pageSize = 100;
12970
+ const all = [];
12971
+ const seen = /* @__PURE__ */ new Set();
12972
+ for (let pageIndex = 0; ; pageIndex++) {
12973
+ const { vtxos, page } = await this.indexerProvider.getVtxos({
12974
+ scripts,
12975
+ pageIndex,
12976
+ pageSize
12977
+ });
12978
+ for (const vtxo of vtxos) {
12979
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
12980
+ if (seen.has(outpoint)) continue;
12981
+ seen.add(outpoint);
12982
+ all.push(vtxo);
12983
+ }
12984
+ if (!page || vtxos.length < pageSize) return all;
12985
+ }
12986
+ }
12987
+ /**
12988
+ * Complete any sweep a previous `claimCash` registered but never finalized.
12989
+ * A thin variant of {@link finalizePendingTxs}, scoped to the arkcash key:
12990
+ * the intent is signed with it, and so are the returned checkpoints.
12991
+ *
12992
+ * A drained sweep is not automatically *ours*. Whoever registered it chose
12993
+ * the destination, so a tx a different claimer left half-finished pays that
12994
+ * claimer — finalizing it is still right (the input is spent either way, and
12995
+ * completing it unsticks the funds), but only the value it actually pays to
12996
+ * `myPkScript` may be counted as claimed here.
12997
+ *
12998
+ * @param vtxos - VTXOs at the arkcash contract's own pkScript, spent ones
12999
+ * included (a registered sweep marks its input spent). Any other outpoint —
13000
+ * subdust above all — would make the proof invalid, since it describes every
13001
+ * input by that pkScript.
13002
+ * @returns The number of pending txs finalized, the sats among them paid to
13003
+ * this wallet, and the outpoints those txs consumed to pay it.
13004
+ */
13005
+ async finalizePendingCashTxs(cash, vtxos, cashScript, myPkScript) {
13006
+ const result = { count: 0, swept: 0, claimed: /* @__PURE__ */ new Set() };
13007
+ const inputs = vtxos.map((vtxo) => ({
13008
+ ...vtxo,
13009
+ forfeitTapLeafScript: cashScript.forfeit(),
13010
+ intentTapLeafScript: cashScript.forfeit(),
13011
+ tapTree: cashScript.encode()
13012
+ }));
13013
+ if (inputs.length === 0) return result;
13014
+ const identity = cash.identity;
13015
+ const seenTxids = /* @__PURE__ */ new Set();
13016
+ const pendingTxs = [];
13017
+ for (let i = 0; i < inputs.length; i += MAX_INPUTS_PER_INTENT) {
13018
+ const batch = inputs.slice(i, i + MAX_INPUTS_PER_INTENT);
13019
+ const message = { type: "get-pending-tx", expire_at: 0 };
13020
+ const proof = Intent.create(message, batch, []);
13021
+ const signedProof = await identity.sign(
13022
+ proof,
13023
+ Array.from({ length: batch.length + 1 }, (_, j) => j)
13024
+ );
13025
+ const batchPending = await this.arkProvider.getPendingTxs({
13026
+ proof: base64.encode(signedProof.toPSBT()),
13027
+ message
13028
+ });
13029
+ for (const pendingTx of batchPending) {
13030
+ if (seenTxids.has(pendingTx.arkTxid)) continue;
13031
+ seenTxids.add(pendingTx.arkTxid);
13032
+ pendingTxs.push(pendingTx);
13033
+ }
13034
+ }
13035
+ const myPkScriptHex = hex.encode(myPkScript);
13036
+ for (const pendingTx of pendingTxs) {
13037
+ try {
13038
+ const checkpointTxs = pendingTx.signedCheckpointTxs.map(
13039
+ (checkpoint) => Transaction$2.fromPSBT(base64.decode(checkpoint))
13040
+ );
13041
+ const finalCheckpoints = await Promise.all(
13042
+ checkpointTxs.map(
13043
+ async (checkpoint) => base64.encode((await identity.sign(checkpoint)).toPSBT())
13044
+ )
13045
+ );
13046
+ await this.arkProvider.finalizeTx(pendingTx.arkTxid, finalCheckpoints);
13047
+ result.count++;
13048
+ const paidToMe = this.arkTxAmountPaidTo(pendingTx.finalArkTx, myPkScriptHex);
13049
+ if (paidToMe === 0) continue;
13050
+ result.swept += paidToMe;
13051
+ for (const checkpoint of checkpointTxs) {
13052
+ for (let i = 0; i < checkpoint.inputsLength; i++) {
13053
+ const input = checkpoint.getInput(i);
13054
+ if (!input.txid) continue;
13055
+ result.claimed.add(`${hex.encode(input.txid)}:${input.index}`);
13056
+ }
13057
+ }
13058
+ } catch (error) {
13059
+ console.error(`Failed to finalize pending arkcash tx ${pendingTx.arkTxid}:`, error);
13060
+ }
13061
+ }
13062
+ return result;
13063
+ }
13064
+ /** Sum the outputs of an ark tx PSBT that pay the given scriptPubKey. */
13065
+ arkTxAmountPaidTo(arkTxPsbt, pkScriptHex) {
13066
+ const arkTx = Transaction$2.fromPSBT(base64.decode(arkTxPsbt));
13067
+ let total = 0;
13068
+ for (let i = 0; i < arkTx.outputsLength; i++) {
13069
+ const output = arkTx.getOutput(i);
13070
+ if (!output.script || output.amount === void 0) continue;
13071
+ if (hex.encode(output.script) === pkScriptHex) total += Number(output.amount);
13072
+ }
13073
+ return total;
13074
+ }
12558
13075
  /**
12559
13076
  * Send BTC and/or assets to one or more recipients.
12560
13077
  *
@@ -12704,9 +13221,24 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12704
13221
  }
12705
13222
  const assetInputs = selectedCoinsToAssetInputs(selectedCoins);
12706
13223
  const hasAssets = assetInputs.size > 0 || recipients.some((r) => r.assets && r.assets.length > 0);
13224
+ const customExtPackets = [];
13225
+ for (const r of args) {
13226
+ if (r.extensions) {
13227
+ for (const ext of r.extensions) {
13228
+ customExtPackets.push({
13229
+ type: () => ext.type,
13230
+ serialize: () => ext.payload
13231
+ });
13232
+ }
13233
+ }
13234
+ }
13235
+ const allExtPackets = [];
12707
13236
  if (hasAssets) {
12708
- const assetPacket = createAssetPacket(assetInputs, recipients, changeReceiver);
12709
- outputs.push(Extension.create([assetPacket]).txOut());
13237
+ allExtPackets.push(createAssetPacket(assetInputs, recipients, changeReceiver));
13238
+ }
13239
+ allExtPackets.push(...customExtPackets);
13240
+ if (allExtPackets.length > 0) {
13241
+ outputs.push(Extension.create(allExtPackets).txOut());
12710
13242
  }
12711
13243
  const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
12712
13244
  return this._submitOffchainSpend(selectedCoins, outputs, {
@@ -12845,72 +13377,52 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12845
13377
  index,
12846
13378
  lookupScript: VtxoScript.decode(input.tapTree).pkScript
12847
13379
  }));
12848
- const checkpointJobs = offchainTx.checkpoints.map(
12849
- (c) => this.inputSigningJobsFromWitnessUtxos(c)
12850
- );
12851
- let signedVirtualTx;
12852
- let userSignedCheckpoints;
12853
13380
  const identity = this.identity;
12854
- const batchEligible = isBatchSignable(identity) && await this._signerRouter.canBatch(arkTxJobs, ...checkpointJobs);
12855
- if (batchEligible) {
12856
- const requests = [
12857
- {
12858
- tx: offchainTx.arkTx.clone(),
12859
- inputIndexes: arkTxJobs.map((j) => j.index)
12860
- },
12861
- ...offchainTx.checkpoints.map((c, i) => ({
12862
- tx: c.clone(),
12863
- inputIndexes: checkpointJobs[i].map((j) => j.index)
12864
- }))
12865
- ];
12866
- const signed = await identity.signMultiple(requests);
12867
- if (signed.length !== requests.length) {
12868
- throw new Error(
12869
- `signMultiple returned ${signed.length} transactions, expected ${requests.length}`
12870
- );
12871
- }
12872
- const [firstSignedTx, ...signedCheckpoints] = signed;
12873
- signedVirtualTx = firstSignedTx;
12874
- userSignedCheckpoints = signedCheckpoints;
12875
- } else {
12876
- signedVirtualTx = await this._signerRouter.sign(offchainTx.arkTx, arkTxJobs);
12877
- }
12878
- await this.setPendingTxFlag(true);
12879
- const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(
12880
- base64.encode(signedVirtualTx.toPSBT()),
12881
- offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
12882
- );
12883
- let finalCheckpoints;
12884
- if (userSignedCheckpoints) {
12885
- if (signedCheckpointTxs.length !== userSignedCheckpoints.length) {
12886
- throw new Error(
12887
- `submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${userSignedCheckpoints.length}`
13381
+ const signer = {
13382
+ signArkTx: async (arkTx, checkpoints) => {
13383
+ const checkpointJobs = checkpoints.map(
13384
+ (c) => this.inputSigningJobsFromWitnessUtxos(c)
12888
13385
  );
13386
+ const batchEligible = isBatchSignable(identity) && await this._signerRouter.canBatch(arkTxJobs, ...checkpointJobs);
13387
+ if (batchEligible) {
13388
+ const requests = [
13389
+ {
13390
+ tx: arkTx.clone(),
13391
+ inputIndexes: arkTxJobs.map((j) => j.index)
13392
+ },
13393
+ ...checkpoints.map((c, i) => ({
13394
+ tx: c.clone(),
13395
+ inputIndexes: checkpointJobs[i].map((j) => j.index)
13396
+ }))
13397
+ ];
13398
+ const signed = await identity.signMultiple(requests);
13399
+ if (signed.length !== requests.length) {
13400
+ throw new Error(
13401
+ `signMultiple returned ${signed.length} transactions, expected ${requests.length}`
13402
+ );
13403
+ }
13404
+ const [firstSignedTx, ...signedCheckpoints] = signed;
13405
+ return { arkTx: firstSignedTx, userSignedCheckpoints: signedCheckpoints };
13406
+ }
13407
+ return { arkTx: await this._signerRouter.sign(arkTx, arkTxJobs) };
13408
+ },
13409
+ signCheckpoint: (checkpoint) => this._signerRouter.sign(
13410
+ checkpoint,
13411
+ this.inputSigningJobsFromWitnessUtxos(checkpoint)
13412
+ )
13413
+ };
13414
+ return submitOffchainTx(this.arkProvider, offchainTx, signer, {
13415
+ // Mark pending before submitting — if we crash between submit and
13416
+ // finalize, the next init recovers via finalizePendingTxs.
13417
+ beforeSubmit: () => this.setPendingTxFlag(true),
13418
+ afterFinalize: async () => {
13419
+ try {
13420
+ await this.setPendingTxFlag(false);
13421
+ } catch (error) {
13422
+ console.error("Failed to clear pending tx flag:", error);
13423
+ }
12889
13424
  }
12890
- finalCheckpoints = signedCheckpointTxs.map((c, i) => {
12891
- const serverSigned = Transaction$2.fromPSBT(base64.decode(c));
12892
- combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
12893
- return base64.encode(serverSigned.toPSBT());
12894
- });
12895
- } else {
12896
- finalCheckpoints = await Promise.all(
12897
- signedCheckpointTxs.map(async (c) => {
12898
- const tx = Transaction$2.fromPSBT(base64.decode(c));
12899
- const signedCheckpoint = await this._signerRouter.sign(
12900
- tx,
12901
- this.inputSigningJobsFromWitnessUtxos(tx)
12902
- );
12903
- return base64.encode(signedCheckpoint.toPSBT());
12904
- })
12905
- );
12906
- }
12907
- await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
12908
- try {
12909
- await this.setPendingTxFlag(false);
12910
- } catch (error) {
12911
- console.error("Failed to clear pending tx flag:", error);
12912
- }
12913
- return { arkTxid, signedCheckpointTxs };
13425
+ });
12914
13426
  }
12915
13427
  // mark virtual outputs as spent, save change outputs if any.
12916
13428
  // `offchainTapscript` and `serverPubKey` are the epoch snapshot the
@@ -17316,7 +17828,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
17316
17828
  return false;
17317
17829
  }
17318
17830
  const derived = p2wpkh(pubkey);
17319
- if (!equalBytes(derived.hash, addressHash)) {
17831
+ if (!equalBytes$1(derived.hash, addressHash)) {
17320
17832
  return false;
17321
17833
  }
17322
17834
  const sighashType = sigWithHash[sigWithHash.length - 1];
@@ -17346,7 +17858,7 @@ function verifyLegacy(message, sigBytes, addressHash) {
17346
17858
  const sig = secp256k1.Signature.fromBytes(compactSig, "compact").addRecoveryBit(recoveryId);
17347
17859
  const point = sig.recoverPublicKey(msgHash);
17348
17860
  const pubkeyBytes = point.toBytes(compressed);
17349
- return equalBytes(hash160(pubkeyBytes), addressHash);
17861
+ return equalBytes$1(hash160(pubkeyBytes), addressHash);
17350
17862
  } catch {
17351
17863
  return false;
17352
17864
  }
@@ -17409,6 +17921,132 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
17409
17921
  });
17410
17922
  return tx;
17411
17923
  }
17924
+
17925
+ // src/providers/emulator.ts
17926
+ var RestEmulatorProvider = class {
17927
+ constructor(serverUrl) {
17928
+ this.serverUrl = serverUrl;
17929
+ }
17930
+ serverUrl;
17931
+ async getInfo() {
17932
+ const url = `${this.serverUrl}/v1/info`;
17933
+ const response = await fetch(url);
17934
+ if (!response.ok) {
17935
+ const errorText2 = await response.text();
17936
+ throw new Error(`Failed to get emulator info: ${errorText2}`);
17937
+ }
17938
+ const data = await response.json();
17939
+ const signerPubkey = data.signerPubkey;
17940
+ if (typeof signerPubkey !== "string" || !signerPubkey) {
17941
+ throw new Error("Invalid emulator info response: missing signerPubkey");
17942
+ }
17943
+ return { signerPubkey };
17944
+ }
17945
+ async submitTx(arkTx, checkpointTxs) {
17946
+ const url = `${this.serverUrl}/v1/tx`;
17947
+ const response = await fetch(url, {
17948
+ method: "POST",
17949
+ headers: { "Content-Type": "application/json" },
17950
+ body: JSON.stringify({
17951
+ arkTx,
17952
+ checkpointTxs
17953
+ })
17954
+ });
17955
+ if (!response.ok) {
17956
+ const errorText2 = await response.text();
17957
+ throw new Error(`Failed to submit tx to emulator: ${errorText2}`);
17958
+ }
17959
+ const data = await response.json();
17960
+ if (typeof data.signedArkTx !== "string" || !data.signedArkTx) {
17961
+ throw new Error("Invalid emulator submitTx response: missing signedArkTx");
17962
+ }
17963
+ if (!Array.isArray(data.signedCheckpointTxs) || !data.signedCheckpointTxs.every((item) => typeof item === "string")) {
17964
+ throw new Error(
17965
+ "Invalid emulator submitTx response: signedCheckpointTxs must be an array of strings"
17966
+ );
17967
+ }
17968
+ return {
17969
+ signedArkTx: data.signedArkTx,
17970
+ signedCheckpointTxs: data.signedCheckpointTxs
17971
+ };
17972
+ }
17973
+ async submitIntent(intent) {
17974
+ const url = `${this.serverUrl}/v1/intent`;
17975
+ const response = await fetch(url, {
17976
+ method: "POST",
17977
+ headers: { "Content-Type": "application/json" },
17978
+ body: JSON.stringify({
17979
+ intent: {
17980
+ proof: intent.proof,
17981
+ message: JSON.stringify(intent.message)
17982
+ }
17983
+ })
17984
+ });
17985
+ if (!response.ok) {
17986
+ const errorText2 = await response.text();
17987
+ throw new Error(`Failed to submit intent to emulator: ${errorText2}`);
17988
+ }
17989
+ const data = await response.json();
17990
+ if (typeof data.signedProof !== "string" || !data.signedProof) {
17991
+ throw new Error("Invalid emulator submitIntent response: missing signedProof");
17992
+ }
17993
+ return data.signedProof;
17994
+ }
17995
+ async submitFinalization(intent, forfeits, connectorTree, commitmentTx) {
17996
+ const url = `${this.serverUrl}/v1/finalization`;
17997
+ const response = await fetch(url, {
17998
+ method: "POST",
17999
+ headers: { "Content-Type": "application/json" },
18000
+ // Uses "signedIntent" (not "intent") because the proof was already
18001
+ // co-signed by the emulator via submitIntent in a prior step.
18002
+ body: JSON.stringify({
18003
+ signedIntent: {
18004
+ proof: intent.proof,
18005
+ message: JSON.stringify(intent.message)
18006
+ },
18007
+ forfeits,
18008
+ connectorTree,
18009
+ commitmentTx
18010
+ })
18011
+ });
18012
+ if (!response.ok) {
18013
+ const errorText2 = await response.text();
18014
+ throw new Error(`Failed to submit finalization to emulator: ${errorText2}`);
18015
+ }
18016
+ const data = await response.json();
18017
+ if (!Array.isArray(data.signedForfeits) || !data.signedForfeits.every((item) => typeof item === "string")) {
18018
+ throw new Error(
18019
+ "Invalid emulator submitFinalization response: signedForfeits must be an array of strings"
18020
+ );
18021
+ }
18022
+ if ("signedCommitmentTx" in data && typeof data.signedCommitmentTx !== "string") {
18023
+ throw new Error(
18024
+ "Invalid emulator submitFinalization response: invalid signedCommitmentTx"
18025
+ );
18026
+ }
18027
+ return {
18028
+ signedForfeits: data.signedForfeits,
18029
+ signedCommitmentTx: data.signedCommitmentTx
18030
+ };
18031
+ }
18032
+ async submitOnchainTx(tx) {
18033
+ const url = `${this.serverUrl}/v1/onchain-tx`;
18034
+ const response = await fetch(url, {
18035
+ method: "POST",
18036
+ headers: { "Content-Type": "application/json" },
18037
+ body: JSON.stringify({ tx })
18038
+ });
18039
+ if (!response.ok) {
18040
+ const errorText2 = await response.text();
18041
+ throw new Error(`Failed to submit onchain tx to emulator: ${errorText2}`);
18042
+ }
18043
+ const data = await response.json();
18044
+ if (typeof data.signedTx !== "string" || !data.signedTx) {
18045
+ throw new Error("Invalid emulator submitOnchainTx response: missing signedTx");
18046
+ }
18047
+ return { signedTx: data.signedTx };
18048
+ }
18049
+ };
17412
18050
  function finalizeVirtualTx(type, psbtBase64) {
17413
18051
  const tx = Transaction.fromPSBT(base64.decode(psbtBase64));
17414
18052
  if (type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
@@ -18441,6 +19079,656 @@ var UnilateralExit = {
18441
19079
  prepare,
18442
19080
  Executor
18443
19081
  };
19082
+
19083
+ // src/arkade/index.ts
19084
+ var arkade_exports = {};
19085
+ __export(arkade_exports, {
19086
+ ARKADE_OP: () => ARKADE_OP,
19087
+ ARKADE_OPCODES: () => ARKADE_OPCODES,
19088
+ ARKADE_OPCODE_NAMES: () => ARKADE_OPCODE_NAMES,
19089
+ ARKADE_OPCODE_VALUES: () => ARKADE_OPCODE_VALUES,
19090
+ ARKADE_OPS: () => ARKADE_OPS,
19091
+ Arkade: () => Arkade,
19092
+ ArkadeContract: () => ArkadeContract,
19093
+ ArkadeProgramScript: () => ArkadeProgramScript,
19094
+ ArkadeScript: () => ArkadeScript,
19095
+ ArkadeTransactionBuilder: () => ArkadeTransactionBuilder,
19096
+ BigNum: () => bignum_exports,
19097
+ OP: () => OP,
19098
+ OPCODE_NAMES: () => OPCODE_NAMES,
19099
+ OPCODE_VALUES: () => OPCODE_VALUES,
19100
+ SUPPORTED_PROGRAM_VERSION: () => SUPPORTED_PROGRAM_VERSION,
19101
+ Script: () => Script,
19102
+ arkadeScriptHash: () => arkadeScriptHash,
19103
+ arkadeWitnessHash: () => arkadeWitnessHash,
19104
+ asmToBytes: () => asmToBytes,
19105
+ bytesToASM: () => bytesToASM,
19106
+ computeArkadeScriptPublicKey: () => computeArkadeScriptPublicKey,
19107
+ createArkadeBatchHandler: () => createArkadeBatchHandler,
19108
+ deserializeArkadeContractParams: () => deserializeArkadeContractParams,
19109
+ fromASM: () => fromASM,
19110
+ getOpcodeName: () => getOpcodeName,
19111
+ getOpcodeValue: () => getOpcodeValue,
19112
+ parseArtifact: () => parseArtifact,
19113
+ resolveAsm: () => resolveAsm,
19114
+ serializeArkadeContractParams: () => serializeArkadeContractParams,
19115
+ stringifyArtifact: () => stringifyArtifact,
19116
+ toASM: () => toASM,
19117
+ validateProgram: () => validateProgram,
19118
+ witnessRefToBytes: () => witnessRefToBytes
19119
+ });
19120
+ var isVtxoCoin = (input) => "virtualStatus" in input;
19121
+ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message, session, arkProvider, emulator, network, recipients) {
19122
+ let batchId;
19123
+ let sweepTapTreeRoot;
19124
+ return {
19125
+ onBatchStarted: async (event) => {
19126
+ const utf8IntentId = new TextEncoder().encode(intentId);
19127
+ const intentIdHash = sha256(utf8IntentId);
19128
+ const intentIdHashStr = hex.encode(intentIdHash);
19129
+ if (!event.intentIdHashes.includes(intentIdHashStr)) return { skip: true };
19130
+ await arkProvider.confirmRegistration(intentId);
19131
+ batchId = event.id;
19132
+ const sweepTapscript = CSVMultisigTapscript.encode({
19133
+ timelock: {
19134
+ value: event.batchExpiry,
19135
+ // BIP-65: values >= 512 are interpreted as seconds, below as blocks
19136
+ type: event.batchExpiry >= 512n ? "seconds" : "blocks"
19137
+ },
19138
+ pubkeys: [hex.decode((await arkProvider.getInfo()).forfeitPubkey).subarray(1)]
19139
+ }).script;
19140
+ sweepTapTreeRoot = tapLeafHash(sweepTapscript);
19141
+ return { skip: false };
19142
+ },
19143
+ onTreeSigningStarted: async (event, vtxoTree) => {
19144
+ const signerPubKey = await session.getPublicKey();
19145
+ const xonlySignerPubKey = signerPubKey.subarray(1);
19146
+ const xOnlyPubkeys = event.cosignersPublicKeys.map((k) => k.slice(2));
19147
+ if (!xOnlyPubkeys.includes(hex.encode(xonlySignerPubKey))) {
19148
+ return { skip: true };
19149
+ }
19150
+ const commitmentTx = Transaction.fromPSBT(base64.decode(event.unsignedCommitmentTx));
19151
+ validateVtxoTxGraph(vtxoTree, commitmentTx, sweepTapTreeRoot);
19152
+ if (recipients && recipients.length > 0) {
19153
+ validateBatchRecipients(commitmentTx, vtxoTree.leaves(), recipients, network);
19154
+ }
19155
+ const sharedOutput = commitmentTx.getOutput(0);
19156
+ if (!sharedOutput?.amount) {
19157
+ throw new Error("Shared output not found");
19158
+ }
19159
+ await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
19160
+ const pubkey = hex.encode(await session.getPublicKey());
19161
+ const nonces = await session.getNonces();
19162
+ await arkProvider.submitTreeNonces(batchId, pubkey, nonces);
19163
+ return { skip: false };
19164
+ },
19165
+ onTreeNonces: async (event) => {
19166
+ const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
19167
+ if (!hasAllNonces) return { fullySigned: false };
19168
+ const signatures = await session.sign();
19169
+ const pubkey = hex.encode(await session.getPublicKey());
19170
+ await arkProvider.submitTreeSignatures(batchId, pubkey, signatures);
19171
+ return { fullySigned: true };
19172
+ },
19173
+ onBatchFinalization: async (event, _vtxoTree, connectorTree) => {
19174
+ const info = await arkProvider.getInfo();
19175
+ const forfeitOutputScript = OutScript.encode(
19176
+ Address(network).decode(info.forfeitAddress)
19177
+ );
19178
+ if (connectorTree) {
19179
+ validateConnectorsTxGraph(event.commitmentTx, connectorTree);
19180
+ }
19181
+ let commitmentPsbt = Transaction.fromPSBT(base64.decode(event.commitmentTx));
19182
+ const signedForfeits = [];
19183
+ let connectorIndex = 0;
19184
+ const connectorLeaves = connectorTree?.leaves() || [];
19185
+ const boardingIndices = [];
19186
+ for (const input of inputs) {
19187
+ if (!isVtxoCoin(input)) {
19188
+ let boardingIdx = null;
19189
+ for (let i = 0; i < commitmentPsbt.inputsLength; i++) {
19190
+ const psbtInput = commitmentPsbt.getInput(i);
19191
+ if (!psbtInput.txid) continue;
19192
+ if (hex.encode(psbtInput.txid) === input.txid && psbtInput.index === input.vout) {
19193
+ boardingIdx = i;
19194
+ break;
19195
+ }
19196
+ }
19197
+ if (boardingIdx === null) continue;
19198
+ commitmentPsbt.updateInput(boardingIdx, {
19199
+ tapLeafScript: [input.forfeitTapLeafScript]
19200
+ });
19201
+ boardingIndices.push(boardingIdx);
19202
+ continue;
19203
+ }
19204
+ if (isRecoverable(input) || isSubdust(input, info.dust)) {
19205
+ continue;
19206
+ }
19207
+ if (connectorIndex >= connectorLeaves.length) {
19208
+ throw new Error("not enough connectors received");
19209
+ }
19210
+ const connectorLeaf = connectorLeaves[connectorIndex++];
19211
+ const connectorTxId = connectorLeaf.id;
19212
+ const connectorOutput = connectorLeaf.getOutput(0);
19213
+ if (!connectorOutput?.amount || !connectorOutput?.script) {
19214
+ throw new Error(
19215
+ `Invalid connector output at index ${connectorIndex - 1}: missing amount or script`
19216
+ );
19217
+ }
19218
+ let forfeitTx = buildForfeitTx(
19219
+ [
19220
+ {
19221
+ txid: input.txid,
19222
+ index: input.vout,
19223
+ witnessUtxo: {
19224
+ amount: BigInt(input.value),
19225
+ script: VtxoScript.decode(input.tapTree).pkScript
19226
+ },
19227
+ sighashType: SigHash.DEFAULT,
19228
+ tapLeafScript: [input.forfeitTapLeafScript]
19229
+ },
19230
+ {
19231
+ txid: connectorTxId,
19232
+ index: 0,
19233
+ witnessUtxo: {
19234
+ amount: connectorOutput.amount,
19235
+ script: connectorOutput.script
19236
+ }
19237
+ }
19238
+ ],
19239
+ forfeitOutputScript
19240
+ );
19241
+ forfeitTx = await signer.sign(forfeitTx, [0]);
19242
+ signedForfeits.push(base64.encode(forfeitTx.toPSBT()));
19243
+ }
19244
+ if (boardingIndices.length > 0) {
19245
+ commitmentPsbt = await signer.sign(commitmentPsbt, boardingIndices);
19246
+ }
19247
+ let connectorTreeNodes = null;
19248
+ if (connectorTree) {
19249
+ connectorTreeNodes = [];
19250
+ for (const subtree of connectorTree.iterator()) {
19251
+ const children = {};
19252
+ for (const [outputIndex, child] of subtree.children) {
19253
+ children[String(outputIndex)] = child.txid;
19254
+ }
19255
+ connectorTreeNodes.push({
19256
+ txid: subtree.txid,
19257
+ tx: base64.encode(subtree.root.toPSBT()),
19258
+ children
19259
+ });
19260
+ }
19261
+ }
19262
+ const hasBoardingInputs = boardingIndices.length > 0;
19263
+ const commitmentB64 = hasBoardingInputs ? base64.encode(commitmentPsbt.toPSBT()) : event.commitmentTx;
19264
+ const emuResult = await emulator.submitFinalization(
19265
+ { proof: signedProof, message },
19266
+ signedForfeits,
19267
+ connectorTreeNodes,
19268
+ commitmentB64
19269
+ );
19270
+ await arkProvider.submitSignedForfeitTxs(
19271
+ emuResult.signedForfeits,
19272
+ emuResult.signedCommitmentTx || (hasBoardingInputs ? commitmentB64 : void 0)
19273
+ );
19274
+ }
19275
+ };
19276
+ }
19277
+ var Arkade = class _Arkade {
19278
+ arkProvider;
19279
+ /** The co-signing service, or undefined for emulator-less (pure tapscript) usage. */
19280
+ emulator;
19281
+ network;
19282
+ serverKey;
19283
+ /** The co-signer's x-only key, present only when an emulator is configured. */
19284
+ emulatorKey;
19285
+ checkpoint;
19286
+ indexer;
19287
+ identity;
19288
+ /** The signing identity's x-only public key, resolved at connect — identifies which inputs the wallet signs. */
19289
+ userKey;
19290
+ /** The wallet's contract manager, when contract persistence is wired up. */
19291
+ contractManager;
19292
+ constructor(fields) {
19293
+ this.arkProvider = fields.arkProvider;
19294
+ this.emulator = fields.emulator;
19295
+ this.network = fields.network;
19296
+ this.serverKey = fields.serverKey;
19297
+ this.emulatorKey = fields.emulatorKey;
19298
+ this.checkpoint = fields.checkpoint;
19299
+ this.indexer = fields.indexer;
19300
+ this.identity = fields.identity;
19301
+ this.userKey = fields.userKey;
19302
+ this.contractManager = fields.contractManager;
19303
+ }
19304
+ /** Connect and resolve the server key, checkpoint closure and (if present) the co-signer key. */
19305
+ static async connect(opts) {
19306
+ const info = await opts.arkade.getInfo();
19307
+ const serverKey = hex.decode(info.signerPubkey).slice(1);
19308
+ const checkpoint = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
19309
+ let emulatorKey;
19310
+ if (opts.emulator) {
19311
+ const eInfo = await opts.emulator.getInfo();
19312
+ emulatorKey = hex.decode(eInfo.signerPubkey);
19313
+ }
19314
+ let userKey;
19315
+ if (opts.identity) {
19316
+ const pub = await opts.identity.xOnlyPublicKey();
19317
+ userKey = pub.length === 33 ? pub.slice(1) : pub;
19318
+ }
19319
+ return new _Arkade({
19320
+ arkProvider: opts.arkade,
19321
+ emulator: opts.emulator,
19322
+ network: opts.network ?? DEFAULT_NETWORK,
19323
+ serverKey,
19324
+ emulatorKey,
19325
+ checkpoint,
19326
+ indexer: opts.indexer,
19327
+ identity: opts.identity,
19328
+ userKey,
19329
+ contractManager: opts.contractManager
19330
+ });
19331
+ }
19332
+ /**
19333
+ * Instantiate a contract from a program and its constructor arguments. The
19334
+ * `program`'s literal type is preserved (`const` inference) so the resulting
19335
+ * contract's `functions` map is strongly typed — `functions.<name>(...)`
19336
+ * knows each argument's type from the function's `inputs` descriptors.
19337
+ *
19338
+ * When the program declares a `server` or `user` param and the caller does
19339
+ * not bind it, it defaults to the client's server key or the identity's
19340
+ * key respectively; explicit args always win.
19341
+ */
19342
+ contract(program, args = {}) {
19343
+ const declared = (program.params ?? []).map(inputName);
19344
+ if (declared.includes("server") && args.server === void 0) {
19345
+ args = { ...args, server: this.serverKey };
19346
+ }
19347
+ if (declared.includes("user") && args.user === void 0 && this.userKey) {
19348
+ args = { ...args, user: this.userKey };
19349
+ }
19350
+ return new ArkadeContract(this, program, args);
19351
+ }
19352
+ };
19353
+ var ArkadeContract = class _ArkadeContract {
19354
+ constructor(client, program, args = {}, keys) {
19355
+ this.client = client;
19356
+ this.program = program;
19357
+ this.args = args;
19358
+ this.keys = keys ?? {
19359
+ serverKey: client.serverKey,
19360
+ userKey: client.userKey,
19361
+ emulatorKey: client.emulatorKey
19362
+ };
19363
+ this.vtxoScript = new ArkadeProgramScript(program, args, this.keys);
19364
+ this.tapTree = this.vtxoScript.encode();
19365
+ this.compiled = this.vtxoScript.compiled;
19366
+ }
19367
+ client;
19368
+ program;
19369
+ args;
19370
+ /** The compiled taproot tree of spending-path leaves. */
19371
+ vtxoScript;
19372
+ /** Encoded taproot tree (shared spend context for every path). */
19373
+ tapTree;
19374
+ /** The signer keys the program was compiled against. */
19375
+ keys;
19376
+ /** Spending paths in declaration order. */
19377
+ compiled;
19378
+ /**
19379
+ * Rebuild a callable contract from a persisted `"arkade"` contract row
19380
+ * (see {@link ArkadeContract.register}). The stored keys are used for
19381
+ * compilation — not the client's current ones — so the derived script and
19382
+ * address stay identical to the registered contract even after a server
19383
+ * signer rotation.
19384
+ */
19385
+ static fromContract(client, contract) {
19386
+ if (contract.type !== "arkade") {
19387
+ throw new Error(
19388
+ `ArkadeContract.fromContract: expected contract type 'arkade', got '${contract.type}'`
19389
+ );
19390
+ }
19391
+ const typed = deserializeArkadeContractParams(contract.params);
19392
+ return new _ArkadeContract(client, typed.program, typed.args, {
19393
+ serverKey: typed.serverKey,
19394
+ userKey: typed.userKey,
19395
+ emulatorKey: typed.emulatorKey
19396
+ });
19397
+ }
19398
+ /** Resolve the {@link TapLeafScript} for a spending path by its index. */
19399
+ leafScript(index) {
19400
+ const fn = this.compiled[index];
19401
+ if (!fn) throw new Error(`leaf index ${index} out of range`);
19402
+ return fn.tapLeafScript;
19403
+ }
19404
+ /** Arkade funding address. */
19405
+ get address() {
19406
+ return this.vtxoScript.address(this.client.network.hrp, this.keys.serverKey).encode();
19407
+ }
19408
+ /** Taproot output script. */
19409
+ get pkScript() {
19410
+ return this.vtxoScript.pkScript;
19411
+ }
19412
+ /**
19413
+ * Callable spending paths: `contract.functions.<name>(...args)`. Strongly
19414
+ * typed from the program's literal type — each function's argument types are
19415
+ * derived from its `inputs` descriptors (see {@link ContractFunctions}).
19416
+ */
19417
+ get functions() {
19418
+ const out = {};
19419
+ for (const fn of this.compiled) {
19420
+ out[fn.name] = (...callArgs) => new ArkadeTransactionBuilder(this, fn, bindInputs(fn, callArgs));
19421
+ }
19422
+ return out;
19423
+ }
19424
+ /**
19425
+ * The `createContract` payload for this contract — the serialized program,
19426
+ * args and keys plus the derived script/address. Useful when registering
19427
+ * through a manager the client does not hold.
19428
+ */
19429
+ toContractParams() {
19430
+ return {
19431
+ type: "arkade",
19432
+ params: serializeArkadeContractParams({
19433
+ program: this.program,
19434
+ args: this.args,
19435
+ serverKey: this.keys.serverKey,
19436
+ userKey: this.keys.userKey,
19437
+ emulatorKey: this.keys.emulatorKey
19438
+ }),
19439
+ script: hex.encode(this.pkScript),
19440
+ address: this.address
19441
+ };
19442
+ }
19443
+ /**
19444
+ * Persist this contract through the wallet's {@link IContractManager} so it
19445
+ * is tracked like any other contract type: stored in the contract
19446
+ * repository, watched for VTXO events, counted in repository-backed
19447
+ * balances, and re-derivable offline via the `"arkade"` contract handler.
19448
+ * Idempotent — re-registering the same script is a no-op.
19449
+ */
19450
+ async register(options) {
19451
+ const manager = this.client.contractManager;
19452
+ if (!manager) {
19453
+ throw new Error(
19454
+ "ArkadeContract.register requires a `contractManager` on the Arkade client \u2014 pass one to Arkade.connect"
19455
+ );
19456
+ }
19457
+ return manager.createContract({
19458
+ ...this.toContractParams(),
19459
+ label: options?.label,
19460
+ metadata: options?.metadata
19461
+ });
19462
+ }
19463
+ /**
19464
+ * Spendable VTXOs locked by this contract.
19465
+ *
19466
+ * When a `contractManager` is configured and this contract is registered,
19467
+ * reads the repository-backed state (offline-first, kept fresh by the
19468
+ * contract watcher). Otherwise falls back to a direct indexer query.
19469
+ */
19470
+ async getUtxos() {
19471
+ const manager = this.client.contractManager;
19472
+ const scriptHex = hex.encode(this.pkScript);
19473
+ if (manager) {
19474
+ const [registered] = await manager.getContracts({ script: scriptHex });
19475
+ if (registered) {
19476
+ const [withVtxos] = await manager.getContractsWithVtxos({ script: scriptHex });
19477
+ return (withVtxos?.vtxos ?? []).filter(isSpendable);
19478
+ }
19479
+ }
19480
+ if (!this.client.indexer) {
19481
+ throw new Error("ArkadeContract.getUtxos: an indexer is required");
19482
+ }
19483
+ const { vtxos } = await this.client.indexer.getVtxos({
19484
+ scripts: [scriptHex],
19485
+ spendableOnly: true
19486
+ });
19487
+ return vtxos;
19488
+ }
19489
+ /** Total spendable balance (requires an indexer). */
19490
+ async getBalance() {
19491
+ const utxos = await this.getUtxos();
19492
+ return utxos.reduce((sum, u) => sum + BigInt(u.value), 0n);
19493
+ }
19494
+ };
19495
+ var ArkadeTransactionBuilder = class {
19496
+ /** @internal */
19497
+ constructor(contract, fn, args) {
19498
+ this.contract = contract;
19499
+ this.fn = fn;
19500
+ this.args = args;
19501
+ }
19502
+ contract;
19503
+ fn;
19504
+ args;
19505
+ outputs = [];
19506
+ fundingCoins = [];
19507
+ assetSpecs = [];
19508
+ coin;
19509
+ changeScript;
19510
+ from(coin) {
19511
+ this.coin = coin;
19512
+ return this;
19513
+ }
19514
+ /**
19515
+ * Add extra inputs the caller funds (e.g. a taker's own coins in a swap).
19516
+ * These become inputs 1..n and are signed with the client identity.
19517
+ */
19518
+ fund(coins) {
19519
+ this.fundingCoins.push(...coins);
19520
+ return this;
19521
+ }
19522
+ /** Transfer an asset group: which inputs supply it and which outputs receive it. */
19523
+ withAsset(spec) {
19524
+ this.assetSpecs.push(spec);
19525
+ return this;
19526
+ }
19527
+ /** Destination for any surplus (inputs − outputs). Required when the spend is not exact. */
19528
+ change(script) {
19529
+ this.changeScript = script;
19530
+ return this;
19531
+ }
19532
+ to(scriptOrOutputs, amount) {
19533
+ if (Array.isArray(scriptOrOutputs)) {
19534
+ for (const [i, out] of scriptOrOutputs.entries()) {
19535
+ if (out.amount === void 0) {
19536
+ throw new Error(`to(outputs): output ${i} is missing an amount`);
19537
+ }
19538
+ }
19539
+ this.outputs.push(...scriptOrOutputs);
19540
+ } else {
19541
+ if (amount === void 0) throw new Error("to(script, amount): amount is required");
19542
+ this.outputs.push({ script: scriptOrOutputs, amount });
19543
+ }
19544
+ return this;
19545
+ }
19546
+ /** Assemble the unsigned ark transaction and its checkpoints. */
19547
+ async build() {
19548
+ if (this.outputs.length === 0) {
19549
+ throw new Error("ArkadeTransactionBuilder: at least one output is required");
19550
+ }
19551
+ const outputsSum = this.outputs.reduce((s, o) => s + (o.amount ?? 0n), 0n);
19552
+ const coin = this.coin ?? await this.selectCoin(outputsSum);
19553
+ const def = this.fn.def;
19554
+ const outputs = [...this.outputs];
19555
+ const fundingSum = this.fundingCoins.reduce((s, f) => s + BigInt(f.value), 0n);
19556
+ const surplus = BigInt(coin.value) + fundingSum - outputsSum;
19557
+ if (surplus < 0n) {
19558
+ throw new Error(
19559
+ `ArkadeTransactionBuilder: insufficient inputs \u2014 outputs ${outputsSum} exceed inputs ${BigInt(coin.value) + fundingSum}`
19560
+ );
19561
+ }
19562
+ if (surplus > 0n) {
19563
+ if (!this.changeScript) {
19564
+ throw new Error(
19565
+ `ArkadeTransactionBuilder: ${surplus} sats surplus with no change output \u2014 call .change(script)`
19566
+ );
19567
+ }
19568
+ outputs.push({ script: this.changeScript, amount: surplus });
19569
+ }
19570
+ const { arkTx, checkpoints } = buildOffchainTx(
19571
+ [
19572
+ {
19573
+ txid: coin.txid,
19574
+ vout: coin.vout,
19575
+ value: coin.value,
19576
+ tapLeafScript: this.fn.tapLeafScript,
19577
+ tapTree: this.contract.tapTree
19578
+ },
19579
+ ...this.fundingCoins
19580
+ ],
19581
+ outputs,
19582
+ this.contract.client.checkpoint
19583
+ );
19584
+ if (coin.sourceTx) {
19585
+ setArkPsbtField(arkTx, 0, PrevArkTxField, coin.sourceTx);
19586
+ }
19587
+ const condition = (def.tapscript.witness ?? []).map((w) => this.witnessBytes(w));
19588
+ if (condition.length > 0) {
19589
+ setArkPsbtField(arkTx, 0, ConditionWitness, condition);
19590
+ setArkPsbtField(checkpoints[0], 0, ConditionWitness, condition);
19591
+ }
19592
+ const packets = [];
19593
+ if (this.assetSpecs.length > 0) {
19594
+ packets.push(this.buildAssetPacket());
19595
+ }
19596
+ const arkadeScript = this.fn.arkadeScript;
19597
+ if (arkadeScript) {
19598
+ const stack = (def.arkadeScript?.witness ?? []).map((w) => this.witnessBytes(w));
19599
+ packets.push(
19600
+ EmulatorPacket.create([
19601
+ { vin: 0, script: arkadeScript, witness: RawWitness.encode(stack) }
19602
+ ])
19603
+ );
19604
+ }
19605
+ if (packets.length > 0) {
19606
+ attachExtension(arkTx, packets);
19607
+ }
19608
+ return { arkTx, checkpoints };
19609
+ }
19610
+ /** Build, submit and return the finalized transaction. */
19611
+ async send() {
19612
+ const { arkTx, checkpoints } = await this.build();
19613
+ const client = this.contract.client;
19614
+ const userInputs = this.userInputIndexes();
19615
+ if (this.fn.arkadeScript) {
19616
+ if (!client.emulator) {
19617
+ throw new Error("covenant spends require an `emulator` on the Arkade client");
19618
+ }
19619
+ const signedArk2 = await this.signArk(arkTx, userInputs);
19620
+ const signedCps = userInputs.length > 0 ? await Promise.all(
19621
+ checkpoints.map(
19622
+ (c, i) => userInputs.includes(i) ? client.identity.sign(c, [0]) : c
19623
+ )
19624
+ ) : checkpoints;
19625
+ const res2 = await client.emulator.submitTx(
19626
+ base64.encode(signedArk2.toPSBT()),
19627
+ signedCps.map((c) => base64.encode(c.toPSBT()))
19628
+ );
19629
+ const txid = Transaction.fromPSBT(base64.decode(res2.signedArkTx)).id;
19630
+ return {
19631
+ txid,
19632
+ signedArkTx: res2.signedArkTx,
19633
+ signedCheckpointTxs: res2.signedCheckpointTxs
19634
+ };
19635
+ }
19636
+ if (!client.identity) {
19637
+ throw new Error("a signing identity is required for non-covenant spends");
19638
+ }
19639
+ const signedArk = await this.signArk(arkTx, userInputs);
19640
+ const res = await client.arkProvider.submitTx(
19641
+ base64.encode(signedArk.toPSBT()),
19642
+ checkpoints.map((c) => base64.encode(c.toPSBT()))
19643
+ );
19644
+ const finalCps = await Promise.all(
19645
+ res.signedCheckpointTxs.map(
19646
+ async (b) => base64.encode(
19647
+ (await client.identity.sign(Transaction.fromPSBT(base64.decode(b)), [0])).toPSBT()
19648
+ )
19649
+ )
19650
+ );
19651
+ await client.arkProvider.finalizeTx(res.arkTxid, finalCps);
19652
+ return {
19653
+ txid: res.arkTxid,
19654
+ signedArkTx: res.finalArkTx,
19655
+ signedCheckpointTxs: res.signedCheckpointTxs
19656
+ };
19657
+ }
19658
+ /** Sign the client-owned inputs of the ark tx (no-op when there are none). */
19659
+ async signArk(arkTx, userInputs) {
19660
+ if (userInputs.length === 0) return arkTx;
19661
+ const { identity } = this.contract.client;
19662
+ if (!identity) {
19663
+ throw new Error("this spend requires an `identity` to sign its user/funding inputs");
19664
+ }
19665
+ return identity.sign(arkTx, userInputs);
19666
+ }
19667
+ /** Indexes of inputs the client owns and must sign (contract input + funded inputs). */
19668
+ userInputIndexes() {
19669
+ const idxs = [];
19670
+ const userKey = this.contract.keys.userKey;
19671
+ if (userKey && this.fn.signerKeys.some((k) => equalBytes(k, userKey))) {
19672
+ idxs.push(0);
19673
+ }
19674
+ for (let i = 0; i < this.fundingCoins.length; i++) {
19675
+ idxs.push(i + 1);
19676
+ }
19677
+ return idxs;
19678
+ }
19679
+ async selectCoin(amount) {
19680
+ const utxos = await this.contract.getUtxos();
19681
+ if (utxos.length === 0) throw new Error("no spendable coins for this contract");
19682
+ const covering = utxos.filter((u) => BigInt(u.value) >= amount).sort((a, b) => a.value - b.value);
19683
+ if (covering.length > 0) return covering[0];
19684
+ return [...utxos].sort((a, b) => b.value - a.value)[0];
19685
+ }
19686
+ buildAssetPacket() {
19687
+ const groups = this.assetSpecs.map((s) => {
19688
+ const id = typeof s.assetId === "string" ? AssetId.fromString(s.assetId) : AssetId.fromBytes(s.assetId);
19689
+ return AssetGroup.create(
19690
+ id,
19691
+ null,
19692
+ s.inputs.map((i) => AssetInput.create(i.vin, i.amount)),
19693
+ s.outputs.map((o) => AssetOutput.create(o.vout, o.amount)),
19694
+ (s.metadata ?? []).map((m) => Metadata.create(m.key, m.value))
19695
+ );
19696
+ });
19697
+ return Packet.create(groups);
19698
+ }
19699
+ witnessBytes(ref) {
19700
+ return witnessRefToBytes(ref, this.args, this.contract.args);
19701
+ }
19702
+ };
19703
+ function bindInputs(fn, callArgs) {
19704
+ const names = (fn.def.inputs ?? []).map(inputName);
19705
+ if (callArgs.length !== names.length) {
19706
+ throw new Error(`${fn.name}: expected ${names.length} argument(s), got ${callArgs.length}`);
19707
+ }
19708
+ const bound = {};
19709
+ names.forEach((n, i) => bound[n] = callArgs[i]);
19710
+ return bound;
19711
+ }
19712
+ function attachExtension(tx, newPackets) {
19713
+ for (let i = 0; i < tx.outputsLength; i++) {
19714
+ const out = tx.getOutput(i);
19715
+ if (!out?.script || !Extension.isExtension(out.script)) continue;
19716
+ const existing = Extension.fromBytes(out.script);
19717
+ const merged = Extension.create([...existing.getPackets(), ...newPackets]);
19718
+ tx.updateOutput(i, { script: merged.serialize(), amount: 0n });
19719
+ return;
19720
+ }
19721
+ const ext = Extension.create(newPackets);
19722
+ const newOut = ext.txOut();
19723
+ const lastIdx = tx.outputsLength - 1;
19724
+ const lastOut = tx.getOutput(lastIdx);
19725
+ if (lastOut?.script && lastOut.script.length === ANCHOR_PKSCRIPT.length && lastOut.script.every((b, j) => b === ANCHOR_PKSCRIPT[j])) {
19726
+ tx.updateOutput(lastIdx, { script: newOut.script, amount: newOut.amount });
19727
+ tx.addOutput({ script: lastOut.script, amount: lastOut.amount ?? 0n });
19728
+ return;
19729
+ }
19730
+ tx.addOutput({ script: newOut.script, amount: newOut.amount });
19731
+ }
18444
19732
  var ARKCONTRACT_PREFIX = "arkcontract";
18445
19733
  function encodeArkContract(contract) {
18446
19734
  const params = new URLSearchParams();
@@ -18500,6 +19788,6 @@ function isArkContract(str) {
18500
19788
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
18501
19789
  }
18502
19790
 
18503
- export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
18504
- //# sourceMappingURL=chunk-P6VBP3CT.js.map
18505
- //# sourceMappingURL=chunk-P6VBP3CT.js.map
19791
+ export { ARKADE_MAGIC, ActivityRegistry, ArkCash, ArkCashCreateError, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, arkade_exports, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
19792
+ //# sourceMappingURL=chunk-SV4PCGKM.js.map
19793
+ //# sourceMappingURL=chunk-SV4PCGKM.js.map