@arkade-os/sdk 0.4.53 → 0.4.55

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 (81) 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-B69ugeJw.d.cts → ark-Do9Lqfsp.d.cts} +36 -2
  8. package/dist/{ark-B69ugeJw.d.ts → ark-Do9Lqfsp.d.ts} +36 -2
  9. package/dist/{asyncStorageTaskQueue-B0SUQMXw.d.cts → asyncStorageTaskQueue-C3DYxvAT.d.cts} +1 -1
  10. package/dist/{asyncStorageTaskQueue-DWqxuIAs.d.ts → asyncStorageTaskQueue-DOnBOEDn.d.ts} +1 -1
  11. package/dist/{chunk-CZTQLA55.cjs → chunk-C6UYPHTQ.cjs} +9 -9
  12. package/dist/{chunk-CZTQLA55.cjs.map → chunk-C6UYPHTQ.cjs.map} +1 -1
  13. package/dist/{chunk-4GB73TRR.cjs → chunk-CSS74VCN.cjs} +17 -17
  14. package/dist/{chunk-4GB73TRR.cjs.map → chunk-CSS74VCN.cjs.map} +1 -1
  15. package/dist/{chunk-LHYNNDY6.js → chunk-ENWR2F2P.js} +8 -1327
  16. package/dist/chunk-ENWR2F2P.js.map +1 -0
  17. package/dist/{chunk-RNMLFY2V.cjs → chunk-G7DOBFQN.cjs} +19 -1367
  18. package/dist/chunk-G7DOBFQN.cjs.map +1 -0
  19. package/dist/chunk-JCJ5WFS3.js +2326 -0
  20. package/dist/chunk-JCJ5WFS3.js.map +1 -0
  21. package/dist/{chunk-B4WZOJAI.js → chunk-LAONNMM5.js} +4 -4
  22. package/dist/{chunk-B4WZOJAI.js.map → chunk-LAONNMM5.js.map} +1 -1
  23. package/dist/chunk-OWCPXRGP.cjs +2415 -0
  24. package/dist/chunk-OWCPXRGP.cjs.map +1 -0
  25. package/dist/{chunk-UZLQUWYP.js → chunk-QMPNPZ2T.js} +23 -4
  26. package/dist/chunk-QMPNPZ2T.js.map +1 -0
  27. package/dist/{chunk-3MMUKLCI.js → chunk-QO3IKBYL.js} +827 -130
  28. package/dist/chunk-QO3IKBYL.js.map +1 -0
  29. package/dist/{chunk-3XWDMLOI.js → chunk-SG4X7SEC.js} +4 -4
  30. package/dist/{chunk-3XWDMLOI.js.map → chunk-SG4X7SEC.js.map} +1 -1
  31. package/dist/{chunk-IEMPH7J6.cjs → chunk-V2TOHHPW.cjs} +60 -40
  32. package/dist/chunk-V2TOHHPW.cjs.map +1 -0
  33. package/dist/{chunk-3YC3LQ2E.cjs → chunk-ZPL34T2Q.cjs} +1191 -471
  34. package/dist/chunk-ZPL34T2Q.cjs.map +1 -0
  35. package/dist/contracts/handlers/index.cjs +8 -8
  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-C_Y2YrXC.d.cts → delegate-C0iIBxen.d.cts} +1 -1
  40. package/dist/{delegate-BU-Cpyjw.d.ts → delegate-DmhFrtdq.d.ts} +1 -1
  41. package/dist/{index-BWF-C9eh.d.ts → index-BNt4wcSh.d.ts} +2 -2
  42. package/dist/{index-tzAInyiX.d.cts → index-BnLM1XxA.d.cts} +2 -2
  43. package/dist/index.cjs +348 -240
  44. package/dist/index.d.cts +424 -14
  45. package/dist/index.d.ts +424 -14
  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-nzJFH8qb.d.ts → taskRunner-Bgs-0I3x.d.ts} +1 -1
  56. package/dist/{taskRunner-B7svzMhT.d.cts → taskRunner-RwUvgQ5M.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 +5 -5
  63. package/dist/wallet/expo/index.d.ts +5 -5
  64. package/dist/wallet/expo/index.js +5 -5
  65. package/dist/{wallet-B6Xiferv.d.cts → wallet-5Aou-V5n.d.ts} +83 -6
  66. package/dist/{wallet-CuHAI-eW.d.ts → wallet-Ck7BH8zR.d.cts} +83 -6
  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-3MMUKLCI.js.map +0 -1
  73. package/dist/chunk-3YC3LQ2E.cjs.map +0 -1
  74. package/dist/chunk-E7HB3GKK.cjs +0 -1030
  75. package/dist/chunk-E7HB3GKK.cjs.map +0 -1
  76. package/dist/chunk-EFNLTS6Q.js +0 -976
  77. package/dist/chunk-EFNLTS6Q.js.map +0 -1
  78. package/dist/chunk-IEMPH7J6.cjs.map +0 -1
  79. package/dist/chunk-LHYNNDY6.js.map +0 -1
  80. package/dist/chunk-RNMLFY2V.cjs.map +0 -1
  81. package/dist/chunk-UZLQUWYP.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { Transaction, setArkPsbtField, PrevArkTxField, ConditionWitness, AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, craftToSpendTx, getArkPsbtFields, CosignerPublicKey, BufferReader, BufferWriter, OP_RETURN_EMPTY_PKSCRIPT, VtxoTaprootTree, baseFetch, Intent, rateGate, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError } from './chunk-LHYNNDY6.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, speculativeReceiveMetadata, DelegateVtxo, deriveDescriptorLeafPubKey, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-UZLQUWYP.js';
3
- import { VtxoScript, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, hasTerminalSpend, getNormalizedVtxos, isVirtualCoin, ArkAddress, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, timelockToSequence, DEFAULT_NETWORK_NAME, normalizeVtxo, getSequence, toBatchExpiry, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, canSpendOffchain, toVirtualStatus, resolveTimeHeight, parseLegacyExpiry, networks as networks$1, isExpired, DEFAULT_ARKADE_SERVER_URL, isPastExpiry } from './chunk-EFNLTS6Q.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-ENWR2F2P.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, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-QMPNPZ2T.js';
3
+ import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isRetryableProviderError, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, RestArkProvider, canSpendOffchain, toVirtualStatus, ArkError, resolveTimeHeight, parseLegacyExpiry, isExpired, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1, isPastExpiry, fetchVtxoCreatedAtByTxid } from './chunk-JCJ5WFS3.js';
4
4
  import { __export } from './chunk-NSBPE2FW.js';
5
5
  import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
6
6
  import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
@@ -25,8 +25,8 @@ function aggregateNonces(pubNonces) {
25
25
  return musig2.nonceAggregate(pubNonces);
26
26
  }
27
27
  function aggregateKeys(publicKeys, sort, options = {}) {
28
- {
29
- publicKeys = musig2.sortKeys(publicKeys);
28
+ if (sort) {
29
+ publicKeys = musig2.sortKeys([...publicKeys]);
30
30
  }
31
31
  const { aggPublicKey: preTweakedKey } = musig2.keyAggregate(publicKeys);
32
32
  if (!options.taprootTweak) {
@@ -88,24 +88,27 @@ var PartialSig = class _PartialSig {
88
88
  return new _PartialSig(bytes, R);
89
89
  }
90
90
  };
91
- function sign(secNonce, privateKey, combinedNonce, publicKeys, message, options) {
91
+ function createSession(combinedNonce, publicKeys, message, options) {
92
+ const keys = musig2.sortKeys([...publicKeys]) ;
92
93
  let tweakBytes;
93
94
  if (options?.taprootTweak !== void 0) {
94
- const { preTweakedKey } = aggregateKeys(
95
- musig2.sortKeys(publicKeys) );
95
+ const { preTweakedKey } = aggregateKeys(keys, false);
96
96
  tweakBytes = schnorr.utils.taggedHash(
97
97
  "TapTweak",
98
98
  preTweakedKey.subarray(1),
99
99
  options.taprootTweak
100
100
  );
101
101
  }
102
- const session = new musig2.Session(
102
+ return new musig2.Session(
103
103
  combinedNonce,
104
- musig2.sortKeys(publicKeys) ,
104
+ keys,
105
105
  message,
106
106
  tweakBytes ? [tweakBytes] : void 0,
107
107
  tweakBytes ? [true] : void 0
108
108
  );
109
+ }
110
+ function sign(secNonce, privateKey, combinedNonce, publicKeys, message, options) {
111
+ const session = createSession(combinedNonce, publicKeys, message, options);
109
112
  const partialSig = session.sign(secNonce, privateKey);
110
113
  return PartialSig.decode(partialSig);
111
114
  }
@@ -514,6 +517,18 @@ var SeedIdentity = class _SeedIdentity {
514
517
  const key = this.derivePrivateKeyForDescriptor(descriptor);
515
518
  return this.signMessageWithKey(key, message, signatureType);
516
519
  }
520
+ /**
521
+ * BIP-340 sign `messageHash` with the key derived from `descriptor`,
522
+ * aux_rand = 0. Backs {@link DescriptorIdentity.signSchnorrDeterministic},
523
+ * which swap preimage derivation reads through.
524
+ */
525
+ async signSchnorrDeterministicWithDescriptor(descriptor, messageHash) {
526
+ if (!this.isOurs(descriptor)) {
527
+ throw new Error(`Descriptor ${descriptor} does not belong to this identity`);
528
+ }
529
+ const key = this.derivePrivateKeyForDescriptor(descriptor);
530
+ return schnorr$1.signAsync(messageHash, key, new Uint8Array(32));
531
+ }
517
532
  // ── internal helpers ─────────────────────────────────────────────
518
533
  derivePrivateKeyForDescriptor(descriptor) {
519
534
  const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
@@ -800,6 +815,59 @@ function assertValidSerializedIdentity(s) {
800
815
  }
801
816
  }
802
817
 
818
+ // src/identity/descriptorIdentity.ts
819
+ function isHDDeterministicSignCapable(value) {
820
+ return typeof value === "object" && value !== null && typeof value.signSchnorrDeterministicWithDescriptor === "function";
821
+ }
822
+ var DescriptorIdentity = class {
823
+ descriptor;
824
+ signer;
825
+ base;
826
+ constructor({ descriptor, signer, base }) {
827
+ this.descriptor = descriptor;
828
+ this.signer = signer;
829
+ this.base = base;
830
+ }
831
+ async xOnlyPublicKey() {
832
+ return deriveDescriptorLeafPubKey(this.descriptor);
833
+ }
834
+ async compressedPublicKey() {
835
+ return deriveDescriptorLeafCompressedPubKey(this.descriptor);
836
+ }
837
+ async sign(tx, inputIndexes) {
838
+ const [signed] = await this.signer.signWithDescriptor([
839
+ { descriptor: this.descriptor, tx, inputIndexes }
840
+ ]);
841
+ if (!signed) {
842
+ throw new Error(`Descriptor signer returned no transaction for ${this.descriptor}`);
843
+ }
844
+ return signed;
845
+ }
846
+ async signMessage(message, signatureType = "schnorr") {
847
+ return this.signer.signMessageWithDescriptor(this.descriptor, message, signatureType);
848
+ }
849
+ /**
850
+ * Delegated: `TreeSignerSession.random()` consults no key material, so the
851
+ * session is index-independent.
852
+ */
853
+ signerSession() {
854
+ return this.base.signerSession();
855
+ }
856
+ /**
857
+ * Throws rather than falling back to the baseline key: a preimage derived
858
+ * from the wrong index is unrecoverable, and callers probe this member
859
+ * through a structural guard that cannot tell the two apart.
860
+ */
861
+ async signSchnorrDeterministic(messageHash) {
862
+ if (!isHDDeterministicSignCapable(this.base)) {
863
+ throw new Error(
864
+ `Identity cannot sign deterministically for descriptor ${this.descriptor}`
865
+ );
866
+ }
867
+ return this.base.signSchnorrDeterministicWithDescriptor(this.descriptor, messageHash);
868
+ }
869
+ };
870
+
803
871
  // src/identity/hdCapableIdentity.ts
804
872
  function isHDCapableIdentity(value) {
805
873
  if (typeof value !== "object" || value === null) return false;
@@ -812,6 +880,13 @@ function isBatchSignable(identity) {
812
880
  return "signMultiple" in identity && typeof identity.signMultiple === "function";
813
881
  }
814
882
 
883
+ // src/wallet/hdWalletCapable.ts
884
+ function isHDWalletCapable(value) {
885
+ if (typeof value !== "object" || value === null) return false;
886
+ const v = value;
887
+ return typeof v.getCurrentSigningDescriptor === "function" && typeof v.getUsedSigningDescriptors === "function" && typeof v.signerForDescriptor === "function";
888
+ }
889
+
815
890
  // src/worker/browser/service-worker-manager.ts
816
891
  var registrations = /* @__PURE__ */ new Map();
817
892
  var handshakes = /* @__PURE__ */ new WeakSet();
@@ -1805,14 +1880,28 @@ function buildOpReturnScript(data) {
1805
1880
  }
1806
1881
  return script;
1807
1882
  }
1808
- var ErrOffchainOutputNotFound = (address) => new Error(`offchain send output not found: ${address}`);
1809
- var ErrInvalidAssetOutputAmount = (got, want, assetId) => new Error(`invalid asset output amount for ${assetId}: got ${got}, want ${want}`);
1810
- var ErrAssetGroupNotFound = (assetId) => new Error(`asset group not found in batch leaf: ${assetId}`);
1811
- var ErrAssetOutputNotFound = (assetId, outputIndex) => new Error(`asset output not found in asset group ${assetId} at index ${outputIndex}`);
1883
+ var ErrOffchainOutputNotFound = (address) => new ServerResponseMismatchError(`offchain send output not found: ${address}`);
1884
+ var ErrInvalidAssetOutputAmount = (got, want, assetId) => new ServerResponseMismatchError(
1885
+ `invalid asset output amount for ${assetId}: got ${got}, want ${want}`
1886
+ );
1887
+ var ErrAssetGroupNotFound = (assetId) => new ServerResponseMismatchError(`asset group not found in batch leaf: ${assetId}`);
1888
+ var ErrAssetOutputNotFound = (assetId, outputIndex) => new ServerResponseMismatchError(
1889
+ `asset output not found in asset group ${assetId} at index ${outputIndex}`
1890
+ );
1891
+ var ErrOnchainOutputNotFound = (address) => new ServerResponseMismatchError(`onchain output not found: ${address}`);
1892
+ var ErrUnvalidatedOffchainOutput = (address) => new ServerResponseMismatchError(
1893
+ `offchain output ${address} cannot be validated: virtual output tree signing did not run`
1894
+ );
1812
1895
  var ErrInvalidOnchainOutputAmount = (address) => new Error(`invalid onchain output amount: ${address}`);
1813
1896
  var ErrInvalidOnchainOutputAssets = (address) => new Error(`onchain output ${address} cannot have assets`);
1814
- var ErrOnchainOutputNotFound = (address) => new Error(`onchain output not found: ${address}`);
1815
1897
  var ErrInvalidOffchainOutputAmount = (address) => new Error(`invalid offchain output ${address}, missing amount`);
1898
+ function assertFinalCommitmentMatchesValidated(finalCommitmentTx, validatedCommitmentTxid, context) {
1899
+ if (!validatedCommitmentTxid) return;
1900
+ if (finalCommitmentTx.id === validatedCommitmentTxid) return;
1901
+ throw new ServerResponseMismatchError(
1902
+ `${context}: finalization commitment tx ${finalCommitmentTx.id} differs from the validated commitment tx ${validatedCommitmentTxid}`
1903
+ );
1904
+ }
1816
1905
  function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, network) {
1817
1906
  const usedOutputs = /* @__PURE__ */ new Set();
1818
1907
  const usedOnchainOutputs = /* @__PURE__ */ new Set();
@@ -1827,6 +1916,18 @@ function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, netwo
1827
1916
  validateOffchainRecipient(vtxoTreeLeaves, arkAddress, recipient, usedOutputs);
1828
1917
  }
1829
1918
  }
1919
+ function validateBatchRecipientsWithoutTree(commitmentTx, recipients, network) {
1920
+ const usedOnchainOutputs = /* @__PURE__ */ new Set();
1921
+ for (const recipient of recipients) {
1922
+ try {
1923
+ ArkAddress.decode(recipient.address);
1924
+ } catch {
1925
+ validateOnchainRecipient(commitmentTx, recipient, network, usedOnchainOutputs);
1926
+ continue;
1927
+ }
1928
+ throw ErrUnvalidatedOffchainOutput(recipient.address);
1929
+ }
1930
+ }
1830
1931
  function validateOnchainRecipient(commitmentTx, recipient, network, usedOutputs) {
1831
1932
  const addr = Address(network).decode(recipient.address);
1832
1933
  const expectedPkScript = OutScript.encode(addr);
@@ -1919,8 +2020,6 @@ function validateAssetGroupOutput(packet, outputIndex, assetId, expectedAmount)
1919
2020
  throw ErrInvalidAssetOutputAmount(assetOutput.amount, expectedAmount, assetId);
1920
2021
  }
1921
2022
  }
1922
-
1923
- // src/wallet/activity.ts
1924
2023
  async function buildActivities(txs, resolvers) {
1925
2024
  const preparedResolvers = (await Promise.all(
1926
2025
  resolvers.map(async (r) => {
@@ -2030,9 +2129,49 @@ function boardingResolver() {
2030
2129
  }
2031
2130
  };
2032
2131
  }
2132
+ function collabExitResolver() {
2133
+ return {
2134
+ id: "collab-exit",
2135
+ resolve(tx) {
2136
+ if (tx.tag !== "exit" || !tx.key.commitmentTxid) return void 0;
2137
+ return [
2138
+ {
2139
+ groupId: `exit:${tx.key.commitmentTxid}`,
2140
+ label: "Collaborative exit",
2141
+ kind: "exit"
2142
+ }
2143
+ ];
2144
+ }
2145
+ };
2146
+ }
2147
+ function assetMintResolver() {
2148
+ return {
2149
+ id: "asset-mint",
2150
+ resolve(tx) {
2151
+ if (!tx.assets?.length || !tx.key.arkTxid) return void 0;
2152
+ const minted = tx.assets.filter((a) => {
2153
+ try {
2154
+ return hex.encode(AssetId.fromString(a.assetId).txid) === tx.key.arkTxid;
2155
+ } catch {
2156
+ return false;
2157
+ }
2158
+ });
2159
+ if (minted.length === 0) return void 0;
2160
+ const received = tx.type === "RECEIVED" /* TxReceived */;
2161
+ return minted.map((a) => ({
2162
+ groupId: `mint:${a.assetId}`,
2163
+ label: received ? "Asset receive" : "Asset mint",
2164
+ kind: received ? "asset-receive" : "asset-mint",
2165
+ metadata: { assetId: a.assetId, amount: a.amount.toString() }
2166
+ }));
2167
+ }
2168
+ };
2169
+ }
2033
2170
  function createDefaultActivityRegistry() {
2034
2171
  const registry = new ActivityRegistry();
2035
2172
  registry.use(boardingResolver());
2173
+ registry.use(collabExitResolver());
2174
+ registry.use(assetMintResolver());
2036
2175
  return registry;
2037
2176
  }
2038
2177
 
@@ -2279,7 +2418,7 @@ function hasBoardingTxExpired(coin, boardingTimelock, chainTipHeight) {
2279
2418
  function formatSighash(type) {
2280
2419
  return `0x${type.toString(16).padStart(2, "0")}`;
2281
2420
  }
2282
- function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT]) {
2421
+ function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT], expectedLeafHash) {
2283
2422
  const input = tx.getInput(inputIndex);
2284
2423
  const prevoutScripts = [];
2285
2424
  const prevoutAmounts = [];
@@ -2294,12 +2433,21 @@ function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubke
2294
2433
  if (!input.tapScriptSig || input.tapScriptSig.length === 0) {
2295
2434
  throw new Error(`Input ${inputIndex} is missing tapScriptSig`);
2296
2435
  }
2436
+ const expectedLeafHashHex = expectedLeafHash ? hex.encode(expectedLeafHash) : void 0;
2297
2437
  for (const [tapScriptSigData, signature] of input.tapScriptSig) {
2298
2438
  const pubKey = tapScriptSigData.pubKey;
2299
2439
  const pubKeyHex = hex.encode(pubKey);
2300
2440
  if (excludePubkeys.includes(pubKeyHex)) {
2301
2441
  continue;
2302
2442
  }
2443
+ if (expectedLeafHashHex !== void 0) {
2444
+ const signedLeafHex = hex.encode(tapScriptSigData.leafHash);
2445
+ if (signedLeafHex !== expectedLeafHashHex) {
2446
+ throw new Error(
2447
+ `Input ${inputIndex}: signature from ${pubKeyHex} commits to leaf ${signedLeafHex}, expected ${expectedLeafHashHex}`
2448
+ );
2449
+ }
2450
+ }
2303
2451
  const sighashType = signature.length === 65 ? signature[64] : SigHash.DEFAULT;
2304
2452
  const sig = signature.subarray(0, 64);
2305
2453
  if (!allowedSighashTypes.includes(sighashType)) {
@@ -2383,6 +2531,70 @@ function isValidArkAddress(address) {
2383
2531
  return false;
2384
2532
  }
2385
2533
  }
2534
+ function matchServerCheckpoints(serverCheckpointTxs, expectedCheckpoints, context) {
2535
+ if (serverCheckpointTxs.length !== expectedCheckpoints.length) {
2536
+ throw new ServerResponseMismatchError(
2537
+ `${context} returned ${serverCheckpointTxs.length} checkpoints, expected ${expectedCheckpoints.length}`
2538
+ );
2539
+ }
2540
+ const byTxid = new Map(expectedCheckpoints.map((c) => [c.id, c]));
2541
+ return serverCheckpointTxs.map((encoded, index) => {
2542
+ const server = Transaction.fromPSBT(base64.decode(encoded));
2543
+ const local = byTxid.get(server.id);
2544
+ if (!local) {
2545
+ throw new ServerResponseMismatchError(
2546
+ `${context} checkpoint ${index} txid ${server.id} does not match any submitted checkpoint`
2547
+ );
2548
+ }
2549
+ byTxid.delete(server.id);
2550
+ return { server, local };
2551
+ });
2552
+ }
2553
+ function assertSubmittedArkTxid(response, signedArkTx, context) {
2554
+ if (response.arkTxid !== signedArkTx.id) {
2555
+ throw new ServerResponseMismatchError(
2556
+ `${context} returned ark txid ${response.arkTxid}, expected ${signedArkTx.id}`
2557
+ );
2558
+ }
2559
+ if (response.finalArkTx === void 0) return;
2560
+ const finalTxid = Transaction.fromPSBT(base64.decode(response.finalArkTx)).id;
2561
+ if (finalTxid !== signedArkTx.id) {
2562
+ throw new ServerResponseMismatchError(
2563
+ `${context} returned final ark tx ${finalTxid}, expected ${signedArkTx.id}`
2564
+ );
2565
+ }
2566
+ }
2567
+ function assertCheckpointsMatchInputs(checkpoints, inputs, unrollCandidates, context) {
2568
+ const byOutpoint = new Map(inputs.map((input) => [`${input.txid}:${input.vout}`, input]));
2569
+ for (const [index, checkpoint] of checkpoints.entries()) {
2570
+ if (checkpoint.inputsLength !== 1) {
2571
+ throw new ServerResponseMismatchError(
2572
+ `${context}: checkpoint ${index} spends ${checkpoint.inputsLength} inputs, expected 1`
2573
+ );
2574
+ }
2575
+ const spent = checkpoint.getInput(0);
2576
+ if (!spent.txid || spent.index === void 0) {
2577
+ throw new ServerResponseMismatchError(
2578
+ `${context}: checkpoint ${index} has no input outpoint`
2579
+ );
2580
+ }
2581
+ const outpoint = `${hex.encode(spent.txid)}:${spent.index}`;
2582
+ const input = byOutpoint.get(outpoint);
2583
+ if (!input) {
2584
+ throw new ServerResponseMismatchError(
2585
+ `${context}: checkpoint ${index} spends ${outpoint}, which is not one of the requested virtual outputs`
2586
+ );
2587
+ }
2588
+ const rebuilt = unrollCandidates.some(
2589
+ (unroll) => buildCheckpointTx(input, unroll).tx.id === checkpoint.id
2590
+ );
2591
+ if (!rebuilt) {
2592
+ throw new ServerResponseMismatchError(
2593
+ `${context}: checkpoint ${index} txid ${checkpoint.id} differs from the checkpoint built locally for ${outpoint}`
2594
+ );
2595
+ }
2596
+ }
2597
+ }
2386
2598
  async function submitOffchainTx(provider, offchainTx, signer, hooks) {
2387
2599
  const { arkTx: signedArkTx, userSignedCheckpoints } = await signer.signArkTx(
2388
2600
  offchainTx.arkTx,
@@ -2394,27 +2606,26 @@ async function submitOffchainTx(provider, offchainTx, signer, hooks) {
2394
2606
  );
2395
2607
  }
2396
2608
  await hooks?.beforeSubmit?.();
2397
- const { arkTxid, signedCheckpointTxs } = await provider.submitTx(
2609
+ const response = await provider.submitTx(
2398
2610
  base64.encode(signedArkTx.toPSBT()),
2399
2611
  offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
2400
2612
  );
2401
- if (signedCheckpointTxs.length !== offchainTx.checkpoints.length) {
2402
- throw new Error(
2403
- `submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${offchainTx.checkpoints.length}`
2404
- );
2405
- }
2613
+ assertSubmittedArkTxid(response, signedArkTx, "submitTx");
2614
+ const { arkTxid, signedCheckpointTxs } = response;
2615
+ const matched = matchServerCheckpoints(signedCheckpointTxs, offchainTx.checkpoints, "submitTx");
2406
2616
  let finalCheckpoints;
2407
2617
  if (userSignedCheckpoints) {
2408
- finalCheckpoints = signedCheckpointTxs.map((c, i) => {
2409
- const serverSigned = Transaction.fromPSBT(base64.decode(c));
2410
- combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
2411
- return base64.encode(serverSigned.toPSBT());
2618
+ const userByTxid = new Map(
2619
+ userSignedCheckpoints.map((c, i) => [offchainTx.checkpoints[i].id, c])
2620
+ );
2621
+ finalCheckpoints = matched.map(({ server, local }) => {
2622
+ combineTapscriptSigs(userByTxid.get(local.id), server);
2623
+ return base64.encode(server.toPSBT());
2412
2624
  });
2413
2625
  } else {
2414
2626
  finalCheckpoints = await Promise.all(
2415
- signedCheckpointTxs.map(async (c) => {
2416
- const tx = Transaction.fromPSBT(base64.decode(c));
2417
- const signed = await signer.signCheckpoint(tx);
2627
+ matched.map(async ({ server }) => {
2628
+ const signed = await signer.signCheckpoint(server);
2418
2629
  return base64.encode(signed.toPSBT());
2419
2630
  })
2420
2631
  );
@@ -2681,6 +2892,14 @@ function getRandomId() {
2681
2892
  const randomValue = crypto.getRandomValues(new Uint8Array(16));
2682
2893
  return hex.encode(randomValue);
2683
2894
  }
2895
+ function isValidArkAddress2(address) {
2896
+ try {
2897
+ ArkAddress.decode(address);
2898
+ return true;
2899
+ } catch (e) {
2900
+ return false;
2901
+ }
2902
+ }
2684
2903
  function validateRecipients(recipients, dustAmount) {
2685
2904
  const validatedRecipients = [];
2686
2905
  for (const recipient of recipients) {
@@ -4900,11 +5119,6 @@ function createExitChainResolver(params) {
4900
5119
  return new OrderedExitChainResolver(sources, params.repository);
4901
5120
  }
4902
5121
 
4903
- // src/providers/availability.ts
4904
- function isRetryableProviderError(error) {
4905
- return error instanceof ProviderUnavailableError || error instanceof FetchError || isFetchTimeoutError(error);
4906
- }
4907
-
4908
5122
  // src/utils/syncCursors.ts
4909
5123
  var SAFETY_LAG_MS = 3e4;
4910
5124
  var OVERLAP_MS = 24 * 60 * 60 * 1e3;
@@ -5527,8 +5741,20 @@ function subtractAssets(spent, change) {
5527
5741
  if (map.size === 0) return void 0;
5528
5742
  return Array.from(map, ([assetId, amount]) => ({ assetId, amount }));
5529
5743
  }
5530
- async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, getTxCreatedAt) {
5744
+ function collectArkTxidsNeedingCreatedAt(vtxos) {
5745
+ const ownTxids = new Set(vtxos.map((v) => v.txid));
5746
+ const txids = /* @__PURE__ */ new Set();
5747
+ for (const vtxo of vtxos) {
5748
+ if (vtxo.isSpent && vtxo.arkTxId && !ownTxids.has(vtxo.arkTxId)) {
5749
+ txids.add(vtxo.arkTxId);
5750
+ }
5751
+ }
5752
+ return [...txids];
5753
+ }
5754
+ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, resolveTxCreatedAt) {
5531
5755
  const fromOldestVtxo = vtxos.map(normalizeVtxo).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
5756
+ const txidsNeedingCreatedAt = resolveTxCreatedAt ? collectArkTxidsNeedingCreatedAt(fromOldestVtxo) : [];
5757
+ const resolvedCreatedAt = resolveTxCreatedAt && txidsNeedingCreatedAt.length > 0 ? await resolveTxCreatedAt(txidsNeedingCreatedAt) : /* @__PURE__ */ new Map();
5532
5758
  const unmatchedSettledBoardingTxs = allBoardingTxs.filter(isSettledBoardingReceive).sort((a, b) => a.createdAt - b.createdAt);
5533
5759
  const sent = [];
5534
5760
  let received = [];
@@ -5588,7 +5814,7 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
5588
5814
  txTime = changes[0].createdAt.getTime();
5589
5815
  } else {
5590
5816
  txAmount = spentAmount;
5591
- txTime = getTxCreatedAt ? await getTxCreatedAt(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1 : vtxo.createdAt.getTime() + 1;
5817
+ txTime = resolvedCreatedAt.get(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1;
5592
5818
  }
5593
5819
  const assets = subtractAssets(allSpent, changes);
5594
5820
  if (txAmount !== 0 || assets) {
@@ -11088,11 +11314,13 @@ var ReadonlyWallet = class _ReadonlyWallet {
11088
11314
  const response = await contractManager.getContractsWithVtxos();
11089
11315
  const allVtxos = response.flatMap((_) => _.vtxos);
11090
11316
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
11091
- const getTxCreatedAt = (txid) => getNormalizedVtxos(this.indexerProvider, { outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime()).catch((err) => {
11092
- if (isRetryableProviderError(err)) return void 0;
11093
- throw err;
11094
- });
11095
- return buildTransactionHistory(allVtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
11317
+ const resolveTxCreatedAt = (txids) => fetchVtxoCreatedAtByTxid(this.indexerProvider, txids);
11318
+ return buildTransactionHistory(
11319
+ allVtxos,
11320
+ boardingTxs,
11321
+ commitmentsToIgnore,
11322
+ resolveTxCreatedAt
11323
+ );
11096
11324
  }
11097
11325
  /**
11098
11326
  * Wallet history grouped by registered activity resolvers. With no resolver match,
@@ -11923,6 +12151,51 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11923
12151
  }
11924
12152
  return newBoarding.onchainAddress(this.network);
11925
12153
  }
12154
+ /**
12155
+ * @see HDWalletCapable.getCurrentSigningDescriptor
12156
+ */
12157
+ async getCurrentSigningDescriptor() {
12158
+ const provider = this._descriptorProvider;
12159
+ if (!(provider instanceof HDDescriptorProvider)) return void 0;
12160
+ return provider.getCurrentSigningDescriptor();
12161
+ }
12162
+ /**
12163
+ * @see HDWalletCapable.getUsedSigningDescriptors
12164
+ *
12165
+ * Union of the watermark band and the descriptors persisted on contracts:
12166
+ * the band alone would miss rows a restore scan wrote, and the contracts
12167
+ * alone would miss indices allocated for something the wallet never
12168
+ * persisted (a swap, an externally issued invoice).
12169
+ */
12170
+ async getUsedSigningDescriptors() {
12171
+ const provider = this._descriptorProvider;
12172
+ const descriptors = /* @__PURE__ */ new Set();
12173
+ if (provider instanceof HDDescriptorProvider) {
12174
+ const lastIndexUsed = await provider.getLastIndexUsed();
12175
+ for (let i = 0; i <= (lastIndexUsed ?? -1); i++) {
12176
+ descriptors.add(provider.materializeDescriptorAt(i));
12177
+ }
12178
+ }
12179
+ for (const contract of await this.contractRepository.getContracts()) {
12180
+ const descriptor = contract.metadata?.signingDescriptor;
12181
+ if (typeof descriptor === "string" && descriptor.length > 0) {
12182
+ descriptors.add(descriptor);
12183
+ }
12184
+ }
12185
+ return [...descriptors].sort(
12186
+ (a, b) => signingDescriptorIndex(a) - signingDescriptorIndex(b)
12187
+ );
12188
+ }
12189
+ /**
12190
+ * @see HDWalletCapable.signerForDescriptor
12191
+ */
12192
+ async signerForDescriptor(descriptor) {
12193
+ const provider = this._descriptorProvider;
12194
+ if (!(provider instanceof HDDescriptorProvider) || !provider.isOurs(descriptor)) {
12195
+ return this.identity;
12196
+ }
12197
+ return new DescriptorIdentity({ descriptor, signer: provider, base: this.identity });
12198
+ }
11926
12199
  /**
11927
12200
  * Mid-session server-signer rotation (plan §4). When arkd rotates its
11928
12201
  * active signer mid-session — the case the long-lived service worker and
@@ -12733,9 +13006,17 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12733
13006
  console.warn("Failed to rotate boarding address after board", e);
12734
13007
  }
12735
13008
  }
12736
- async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
13009
+ async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, expectedRecipients, connectorsGraph, validatedCommitmentTxid) {
12737
13010
  const signedForfeits = [];
12738
13011
  let settlementPsbt = Transaction$2.fromPSBT(base64.decode(event.commitmentTx));
13012
+ assertFinalCommitmentMatchesValidated(
13013
+ settlementPsbt,
13014
+ validatedCommitmentTxid,
13015
+ "settlement finalization"
13016
+ );
13017
+ if (!validatedCommitmentTxid && expectedRecipients.length > 0) {
13018
+ validateBatchRecipientsWithoutTree(settlementPsbt, expectedRecipients, this.network);
13019
+ }
12739
13020
  let hasBoardingUtxos = false;
12740
13021
  let connectorIndex = 0;
12741
13022
  const connectorsLeaves = connectorsGraph?.leaves() || [];
@@ -12858,6 +13139,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12858
13139
  */
12859
13140
  createBatchHandler(intentId, inputs, expectedRecipients, session) {
12860
13141
  let sweepTapTreeRoot;
13142
+ let validatedCommitmentTxid;
12861
13143
  return {
12862
13144
  onBatchStarted: async (event) => {
12863
13145
  const utf8IntentId = new TextEncoder().encode(intentId);
@@ -12915,6 +13197,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12915
13197
  if (!sharedOutput?.amount) {
12916
13198
  throw new Error("Shared output not found");
12917
13199
  }
13200
+ validatedCommitmentTxid = commitmentTx.id;
12918
13201
  await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
12919
13202
  const pubkey = hex.encode(await session.getPublicKey());
12920
13203
  const nonces = await session.getNonces();
@@ -12943,7 +13226,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12943
13226
  event,
12944
13227
  inputs,
12945
13228
  this.forfeitOutputScript,
12946
- connectorTree
13229
+ expectedRecipients,
13230
+ connectorTree,
13231
+ validatedCommitmentTxid
12947
13232
  );
12948
13233
  }
12949
13234
  };
@@ -13102,6 +13387,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13102
13387
  for (let i = 0; i < vtxos.length; i += MAX_INPUTS_PER_INTENT) {
13103
13388
  batches.push(vtxos.slice(i, i + MAX_INPUTS_PER_INTENT));
13104
13389
  }
13390
+ const unrollCandidates = this.checkpointUnrollCandidates(await this.arkProvider.getInfo());
13391
+ const checkpointInputs = vtxos.map((vtxo) => ({
13392
+ ...vtxo,
13393
+ tapLeafScript: vtxo.forfeitTapLeafScript
13394
+ }));
13105
13395
  const seen = /* @__PURE__ */ new Set();
13106
13396
  const results = await Promise.all(
13107
13397
  batches.map(async (batch) => {
@@ -13117,6 +13407,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13117
13407
  const checkpointTxs = pendingTx.signedCheckpointTxs.map(
13118
13408
  (c) => Transaction$2.fromPSBT(base64.decode(c))
13119
13409
  );
13410
+ assertCheckpointsMatchInputs(
13411
+ checkpointTxs,
13412
+ checkpointInputs,
13413
+ unrollCandidates,
13414
+ `pending tx ${pendingTx.arkTxid}`
13415
+ );
13120
13416
  const checkpointJobs = checkpointTxs.map(
13121
13417
  (tx) => this.inputSigningJobsFromWitnessUtxos(tx)
13122
13418
  );
@@ -13172,6 +13468,32 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13172
13468
  }
13173
13469
  return { finalized, pending };
13174
13470
  }
13471
+ /**
13472
+ * The server unroll scripts a pending checkpoint may legitimately have been
13473
+ * built under: this wallet's configured one, the server's current one, and
13474
+ * one per deprecated signer (same closure, deprecated key). A tx submitted
13475
+ * before a signer rotation was built under the old key, so rebuilding
13476
+ * against the current key alone would leave it pending forever.
13477
+ */
13478
+ checkpointUnrollCandidates(info) {
13479
+ const current = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
13480
+ const candidates = [this._serverUnrollScript, current];
13481
+ for (const deprecated of signerSetFromInfo(info).deprecated.keys()) {
13482
+ candidates.push(
13483
+ CSVMultisigTapscript.encode({
13484
+ ...current.params,
13485
+ pubkeys: [hex.decode(deprecated)]
13486
+ })
13487
+ );
13488
+ }
13489
+ const seen = /* @__PURE__ */ new Set();
13490
+ return candidates.filter((candidate) => {
13491
+ const script = hex.encode(candidate.script);
13492
+ if (seen.has(script)) return false;
13493
+ seen.add(script);
13494
+ return true;
13495
+ });
13496
+ }
13175
13497
  async hasPendingTxFlag() {
13176
13498
  const state = await this.walletRepository.getWalletState();
13177
13499
  return state?.settings?.hasPendingTx === true;
@@ -13404,12 +13726,23 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13404
13726
  pendingTxs.push(pendingTx);
13405
13727
  }
13406
13728
  }
13729
+ if (pendingTxs.length === 0) return result;
13407
13730
  const myPkScriptHex = hex.encode(myPkScript);
13731
+ const unrollCandidates = this.checkpointUnrollCandidates(await this.arkProvider.getInfo());
13408
13732
  for (const pendingTx of pendingTxs) {
13409
13733
  try {
13410
13734
  const checkpointTxs = pendingTx.signedCheckpointTxs.map(
13411
13735
  (checkpoint) => Transaction$2.fromPSBT(base64.decode(checkpoint))
13412
13736
  );
13737
+ assertCheckpointsMatchInputs(
13738
+ checkpointTxs,
13739
+ inputs.map((input) => ({
13740
+ ...input,
13741
+ tapLeafScript: input.forfeitTapLeafScript
13742
+ })),
13743
+ unrollCandidates,
13744
+ `pending arkadeCash tx ${pendingTx.arkTxid}`
13745
+ );
13413
13746
  const finalCheckpoints = await Promise.all(
13414
13747
  checkpointTxs.map(
13415
13748
  async (checkpoint) => base64.encode((await identity.sign(checkpoint)).toPSBT())
@@ -13819,28 +14152,29 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13819
14152
  `updateDbAfterOffchainTx: inputs length (${inputs.length}) differs from signedCheckpointTxs length (${signedCheckpointTxs.length})`
13820
14153
  );
13821
14154
  }
13822
- const safeLength = Math.min(inputs.length, signedCheckpointTxs.length);
14155
+ const checkpointIdByOutpoint = /* @__PURE__ */ new Map();
14156
+ for (const encoded of signedCheckpointTxs) {
14157
+ const checkpoint = Transaction$2.fromPSBT(base64.decode(encoded));
14158
+ for (let i = 0; i < checkpoint.inputsLength; i++) {
14159
+ const input = checkpoint.getInput(i);
14160
+ if (!input.txid) continue;
14161
+ checkpointIdByOutpoint.set(
14162
+ `${hex.encode(input.txid)}:${input.index}`,
14163
+ checkpoint.id
14164
+ );
14165
+ }
14166
+ }
13823
14167
  const cm = await this.getContractManager();
13824
14168
  const annotatedInputs = await cm.annotateVtxos(inputs);
13825
- for (const [inputIndex, vtxo] of annotatedInputs.entries()) {
14169
+ for (const vtxo of annotatedInputs) {
13826
14170
  const spentFacts = { ...vtxo, isSpent: true };
13827
- if (inputIndex < safeLength && signedCheckpointTxs[inputIndex]) {
13828
- const checkpoint = Transaction$2.fromPSBT(
13829
- base64.decode(signedCheckpointTxs[inputIndex])
13830
- );
13831
- spentVtxos.push({
13832
- ...spentFacts,
13833
- virtualStatus: toVirtualStatus(spentFacts),
13834
- spentBy: checkpoint.id,
13835
- arkTxId: arkTxid
13836
- });
13837
- } else {
13838
- spentVtxos.push({
13839
- ...spentFacts,
13840
- virtualStatus: toVirtualStatus(spentFacts),
13841
- arkTxId: arkTxid
13842
- });
13843
- }
14171
+ const spentBy = checkpointIdByOutpoint.get(`${vtxo.txid}:${vtxo.vout}`);
14172
+ spentVtxos.push({
14173
+ ...spentFacts,
14174
+ virtualStatus: toVirtualStatus(spentFacts),
14175
+ ...spentBy ? { spentBy } : {},
14176
+ arkTxId: arkTxid
14177
+ });
13844
14178
  for (const id of vtxo.commitmentTxIds) {
13845
14179
  commitmentTxIds.add(id);
13846
14180
  }
@@ -14607,6 +14941,25 @@ var DustChangeError = class extends Error {
14607
14941
  this.dustAmount = dustAmount;
14608
14942
  }
14609
14943
  };
14944
+ var offboardNetworkNames = [
14945
+ "bitcoin",
14946
+ "regtest",
14947
+ "testnet",
14948
+ "signet",
14949
+ "mutinynet"
14950
+ ];
14951
+ function offboardDestinationScript(destinationAddress) {
14952
+ for (const networkName of offboardNetworkNames) {
14953
+ try {
14954
+ const network = networks$1[networkName];
14955
+ const addr = Address(network).decode(destinationAddress);
14956
+ return OutScript.encode(addr);
14957
+ } catch {
14958
+ continue;
14959
+ }
14960
+ }
14961
+ throw new Error(`Failed to decode destination address: ${destinationAddress}`);
14962
+ }
14610
14963
  var Ramps = class {
14611
14964
  /**
14612
14965
  * Create convenience wrappers for onboarding and offboarding flows.
@@ -14744,27 +15097,7 @@ var Ramps = class {
14744
15097
  throw new DustChangeError(change, dustAmount);
14745
15098
  }
14746
15099
  amount = amount ?? totalAmount;
14747
- const networkNames = [
14748
- "bitcoin",
14749
- "regtest",
14750
- "testnet",
14751
- "signet",
14752
- "mutinynet"
14753
- ];
14754
- let destinationScript;
14755
- for (const networkName of networkNames) {
14756
- try {
14757
- const network = networks$1[networkName];
14758
- const addr = Address(network).decode(destinationAddress);
14759
- destinationScript = OutScript.encode(addr);
14760
- break;
14761
- } catch {
14762
- continue;
14763
- }
14764
- }
14765
- if (!destinationScript) {
14766
- throw new Error(`Failed to decode destination address: ${destinationAddress}`);
14767
- }
15100
+ const destinationScript = offboardDestinationScript(destinationAddress);
14768
15101
  const outputFee = estimator.evalOnchainOutput({
14769
15102
  amount,
14770
15103
  script: hex.encode(destinationScript)
@@ -15796,47 +16129,15 @@ var WalletMessageHandler = class {
15796
16129
  return allVtxos;
15797
16130
  }
15798
16131
  /**
15799
- * Build transaction history from cached virtual outputs without hitting the indexer.
15800
- * Falls back to indexer only for uncached transaction timestamps.
16132
+ * Build transaction history from cached virtual outputs, hitting the indexer only for
16133
+ * uncached timestamps. Best-effort, like the plain Wallet path.
15801
16134
  */
15802
16135
  async buildTransactionHistoryFromCache(vtxos) {
15803
16136
  if (!this.readonlyWallet) return null;
15804
16137
  const { boardingTxs, commitmentsToIgnore } = await this.readonlyWallet.getBoardingTxs();
15805
- const vtxoCreatedAt = /* @__PURE__ */ new Map();
15806
- for (const vtxo of vtxos) {
15807
- const existing = vtxoCreatedAt.get(vtxo.txid);
15808
- const ts = vtxo.createdAt.getTime();
15809
- if (existing === void 0 || ts < existing) {
15810
- vtxoCreatedAt.set(vtxo.txid, ts);
15811
- }
15812
- }
15813
- if (this.indexerProvider) {
15814
- const uncachedTxids = /* @__PURE__ */ new Set();
15815
- for (const vtxo of vtxos) {
15816
- if (vtxo.isSpent && vtxo.arkTxId && !vtxoCreatedAt.has(vtxo.arkTxId) && !vtxos.some((v) => v.txid === vtxo.arkTxId)) {
15817
- uncachedTxids.add(vtxo.arkTxId);
15818
- }
15819
- }
15820
- if (uncachedTxids.size > 0) {
15821
- const outpoints = [...uncachedTxids].map((txid) => ({
15822
- txid,
15823
- vout: 0
15824
- }));
15825
- const BATCH_SIZE = 100;
15826
- for (let i = 0; i < outpoints.length; i += BATCH_SIZE) {
15827
- const res = await getNormalizedVtxos(this.indexerProvider, {
15828
- outpoints: outpoints.slice(i, i + BATCH_SIZE)
15829
- });
15830
- for (const v of res.vtxos) {
15831
- vtxoCreatedAt.set(v.txid, v.createdAt.getTime());
15832
- }
15833
- }
15834
- }
15835
- }
15836
- const getTxCreatedAt = async (txid) => {
15837
- return vtxoCreatedAt.get(txid);
15838
- };
15839
- return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
16138
+ const indexerProvider = this.indexerProvider;
16139
+ const resolveTxCreatedAt = indexerProvider ? (txids) => fetchVtxoCreatedAtByTxid(indexerProvider, txids) : void 0;
16140
+ return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, resolveTxCreatedAt);
15840
16141
  }
15841
16142
  async ensureContractEventBroadcasting() {
15842
16143
  if (!this.readonlyWallet) return;
@@ -19013,19 +19314,48 @@ async function estimate(opts) {
19013
19314
  }
19014
19315
 
19015
19316
  // src/wallet/exit/executor.ts
19317
+ function abortErrorFor(signal) {
19318
+ if (signal.reason !== void 0) return signal.reason;
19319
+ const e = new Error("The operation was aborted");
19320
+ e.name = "AbortError";
19321
+ return e;
19322
+ }
19323
+ function throwIfAborted(signal) {
19324
+ if (signal?.aborted) throw abortErrorFor(signal);
19325
+ }
19016
19326
  var Executor = class {
19017
19327
  constructor(pkg, provider, opts) {
19018
19328
  this.pkg = pkg;
19019
19329
  this.provider = provider;
19020
19330
  this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
19021
19331
  this.feeWallet = opts?.feeWallet;
19332
+ this.signal = opts?.signal;
19022
19333
  }
19023
19334
  pkg;
19024
19335
  provider;
19025
19336
  pollIntervalMs;
19026
19337
  feeWallet;
19338
+ signal;
19339
+ /**
19340
+ * Poll delay. Nearly all wall-clock time is spent here, so it must be the
19341
+ * thing that reacts to abort — checking only between polls would leave
19342
+ * cancellation up to a full interval late and the timer still pending.
19343
+ */
19027
19344
  sleep() {
19028
- return new Promise((r) => setTimeout(r, this.pollIntervalMs));
19345
+ const signal = this.signal;
19346
+ if (!signal) return new Promise((r) => setTimeout(r, this.pollIntervalMs));
19347
+ return new Promise((resolve, reject) => {
19348
+ if (signal.aborted) return reject(abortErrorFor(signal));
19349
+ const onAbort = () => {
19350
+ clearTimeout(timer);
19351
+ reject(abortErrorFor(signal));
19352
+ };
19353
+ const timer = setTimeout(() => {
19354
+ signal.removeEventListener("abort", onAbort);
19355
+ resolve();
19356
+ }, this.pollIntervalMs);
19357
+ signal.addEventListener("abort", onAbort, { once: true });
19358
+ });
19029
19359
  }
19030
19360
  async status(txid) {
19031
19361
  try {
@@ -19038,10 +19368,12 @@ var Executor = class {
19038
19368
  for (; ; ) {
19039
19369
  const s = await this.status(txid);
19040
19370
  if (s?.confirmed) return s;
19371
+ throwIfAborted(this.signal);
19041
19372
  await this.sleep();
19042
19373
  }
19043
19374
  }
19044
19375
  async *[Symbol.asyncIterator]() {
19376
+ throwIfAborted(this.signal);
19045
19377
  const dead = /* @__PURE__ */ new Set();
19046
19378
  if (this.pkg.validUntil && Date.now() / 1e3 > this.pkg.validUntil) {
19047
19379
  yield {
@@ -19153,6 +19485,7 @@ var Executor = class {
19153
19485
  while (done.size < pending.length) {
19154
19486
  for (const { index, step } of pending) {
19155
19487
  if (done.has(index)) continue;
19488
+ throwIfAborted(this.signal);
19156
19489
  const swept = await this.status(step.txid);
19157
19490
  if (swept?.confirmed) {
19158
19491
  done.add(index);
@@ -19500,6 +19833,7 @@ var isVtxoCoin = (input) => isVirtualCoin(input);
19500
19833
  function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message, session, arkProvider, emulator, network, recipients) {
19501
19834
  let batchId;
19502
19835
  let sweepTapTreeRoot;
19836
+ let validatedCommitmentTxid;
19503
19837
  return {
19504
19838
  onBatchStarted: async (event) => {
19505
19839
  const utf8IntentId = new TextEncoder().encode(intentId);
@@ -19535,6 +19869,7 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
19535
19869
  if (!sharedOutput?.amount) {
19536
19870
  throw new Error("Shared output not found");
19537
19871
  }
19872
+ validatedCommitmentTxid = commitmentTx.id;
19538
19873
  await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
19539
19874
  const pubkey = hex.encode(await session.getPublicKey());
19540
19875
  const nonces = await session.getNonces();
@@ -19558,6 +19893,14 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
19558
19893
  validateConnectorsTxGraph(event.commitmentTx, connectorTree);
19559
19894
  }
19560
19895
  let commitmentPsbt = Transaction.fromPSBT(base64.decode(event.commitmentTx));
19896
+ assertFinalCommitmentMatchesValidated(
19897
+ commitmentPsbt,
19898
+ validatedCommitmentTxid,
19899
+ "arkade batch finalization"
19900
+ );
19901
+ if (!validatedCommitmentTxid && recipients && recipients.length > 0) {
19902
+ validateBatchRecipientsWithoutTree(commitmentPsbt, recipients, network);
19903
+ }
19561
19904
  const signedForfeits = [];
19562
19905
  let connectorIndex = 0;
19563
19906
  const connectorLeaves = connectorTree?.leaves() || [];
@@ -20024,11 +20367,11 @@ var ArkadeTransactionBuilder = class {
20024
20367
  base64.encode(signedArk.toPSBT()),
20025
20368
  checkpoints.map((c) => base64.encode(c.toPSBT()))
20026
20369
  );
20370
+ assertSubmittedArkTxid(res, signedArk, "submitTx");
20371
+ const matched = matchServerCheckpoints(res.signedCheckpointTxs, checkpoints, "submitTx");
20027
20372
  const finalCps = await Promise.all(
20028
- res.signedCheckpointTxs.map(
20029
- async (b) => base64.encode(
20030
- (await client.identity.sign(Transaction.fromPSBT(base64.decode(b)), [0])).toPSBT()
20031
- )
20373
+ matched.map(
20374
+ async ({ server }) => base64.encode((await client.identity.sign(server, [0])).toPSBT())
20032
20375
  )
20033
20376
  );
20034
20377
  await client.arkProvider.finalizeTx(res.arkTxid, finalCps);
@@ -20112,6 +20455,360 @@ function attachExtension(tx, newPackets) {
20112
20455
  }
20113
20456
  tx.addOutput({ script: newOut.script, amount: newOut.amount });
20114
20457
  }
20458
+
20459
+ // src/utils/bip21.ts
20460
+ var BIP21Error = /* @__PURE__ */ ((BIP21Error2) => {
20461
+ BIP21Error2["INVALID_URI"] = "Invalid BIP21 URI";
20462
+ BIP21Error2["INVALID_ADDRESS"] = "Invalid address";
20463
+ return BIP21Error2;
20464
+ })(BIP21Error || {});
20465
+ var BIP21 = class _BIP21 {
20466
+ /**
20467
+ * Create a BIP21 URI from the provided parameters.
20468
+ *
20469
+ * @param params - BIP21 parameters to encode
20470
+ * @returns Encoded BIP21 URI
20471
+ */
20472
+ static create(params) {
20473
+ const { address, ...options } = params;
20474
+ const queryParams = {};
20475
+ for (const [key, value] of Object.entries(options)) {
20476
+ if (value === void 0) continue;
20477
+ if (key === "amount") {
20478
+ const amount = value;
20479
+ if (!isFinite(amount) || !Number.isSafeInteger(Math.trunc(amount))) {
20480
+ console.warn("Invalid amount");
20481
+ continue;
20482
+ }
20483
+ if (amount < 0) {
20484
+ continue;
20485
+ }
20486
+ queryParams[key] = value;
20487
+ } else if (key === "ark") {
20488
+ if (typeof value === "string" && (value.startsWith("ark") || value.startsWith("tark"))) {
20489
+ queryParams[key] = value;
20490
+ } else {
20491
+ console.warn("Invalid ARK address format");
20492
+ }
20493
+ } else if (key === "sp") {
20494
+ if (typeof value === "string" && value.startsWith("sp")) {
20495
+ queryParams[key] = value;
20496
+ } else {
20497
+ console.warn("Invalid Silent Payment address format");
20498
+ }
20499
+ } else if (typeof value === "string" || typeof value === "number") {
20500
+ queryParams[key] = value;
20501
+ }
20502
+ }
20503
+ const query = Object.keys(queryParams).length > 0 ? "?" + new URLSearchParams(
20504
+ Object.fromEntries(
20505
+ Object.entries(queryParams).map(([k, v]) => [k, String(v)])
20506
+ )
20507
+ ).toString() : "";
20508
+ return `bitcoin:${address ? address.toLowerCase() : ""}${query}`;
20509
+ }
20510
+ /**
20511
+ * Integer-sats amount encoded in a BIP21 URI (`amount=` is BTC), or
20512
+ * `undefined` when absent or the URI is unparseable. Shared by the payment
20513
+ * rails, which accept either a bare address or a BIP21 URI.
20514
+ *
20515
+ * @param uri - BIP21 URI (or bare address) to read the amount from
20516
+ * @returns The amount in satoshis, or `undefined`
20517
+ */
20518
+ static amountSats(uri) {
20519
+ try {
20520
+ const btc = _BIP21.parse(uri).params.amount;
20521
+ return typeof btc === "number" ? Math.round(btc * 1e8) : void 0;
20522
+ } catch {
20523
+ return void 0;
20524
+ }
20525
+ }
20526
+ /**
20527
+ * Parse a BIP21 URI and return its decoded parameters.
20528
+ *
20529
+ * @param uri - BIP21 URI to parse
20530
+ * @returns Parsed BIP21 URI data
20531
+ * @throws Error if the URI does not start with the `bitcoin:` scheme
20532
+ */
20533
+ static parse(uri) {
20534
+ if (!uri.toLowerCase().startsWith("bitcoin:")) {
20535
+ throw new Error("Invalid BIP21 URI" /* INVALID_URI */);
20536
+ }
20537
+ const withoutPrefix = uri.slice(uri.toLowerCase().indexOf("bitcoin:") + 8);
20538
+ const [address, query] = withoutPrefix.split("?");
20539
+ const params = {};
20540
+ if (address) {
20541
+ params.address = address.toLowerCase();
20542
+ }
20543
+ if (query) {
20544
+ const queryParams = new URLSearchParams(query);
20545
+ for (const [key, value] of queryParams.entries()) {
20546
+ if (!value) continue;
20547
+ if (key === "amount") {
20548
+ if (!/^(?:\d+\.?\d*|\.\d+)$/.test(value)) {
20549
+ continue;
20550
+ }
20551
+ const amount = Number(value);
20552
+ if (!isFinite(amount) || !Number.isSafeInteger(Math.trunc(amount))) {
20553
+ continue;
20554
+ }
20555
+ params[key] = amount;
20556
+ } else if (key === "ark") {
20557
+ if (value.startsWith("ark") || value.startsWith("tark")) {
20558
+ params[key] = value;
20559
+ } else {
20560
+ console.warn("Invalid ARK address format");
20561
+ }
20562
+ } else if (key === "sp") {
20563
+ if (value.startsWith("sp")) {
20564
+ params[key] = value;
20565
+ } else {
20566
+ console.warn("Invalid Silent Payment address format");
20567
+ }
20568
+ } else {
20569
+ params[key] = value;
20570
+ }
20571
+ }
20572
+ }
20573
+ return {
20574
+ originalString: uri,
20575
+ params
20576
+ };
20577
+ }
20578
+ };
20579
+
20580
+ // src/payment/predicates.ts
20581
+ var isLightningInvoice = (raw) => /^ln(bcrt|bc|tbs|tb|sb)[0-9a-z]+$/i.test(raw.replace(/^lightning:/i, ""));
20582
+ var isLnurl = (raw) => /^lnurl/i.test(raw) || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw);
20583
+ var BECH32_BTC = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,90}$/;
20584
+ var isBech32Btc = (raw) => (raw === raw.toLowerCase() || raw === raw.toUpperCase()) && BECH32_BTC.test(raw.toLowerCase());
20585
+ var isBtcAddress = (raw) => isBech32Btc(raw) || /^[13mn2][a-km-zA-HJ-NP-Z1-9]{25,39}$/.test(raw);
20586
+
20587
+ // src/payment/targets.ts
20588
+ var fromBip21 = (raw, key, accept) => {
20589
+ try {
20590
+ const v = BIP21.parse(raw).params[key];
20591
+ return typeof v === "string" && accept(v) ? v : void 0;
20592
+ } catch {
20593
+ return void 0;
20594
+ }
20595
+ };
20596
+ var arkTarget = (raw) => isValidArkAddress2(raw) ? raw : fromBip21(raw, "ark", isValidArkAddress2);
20597
+ var btcTarget = (raw) => isBtcAddress(raw) ? raw : fromBip21(raw, "address", isBtcAddress);
20598
+ var invoiceTarget = (raw) => {
20599
+ const invoice = isLightningInvoice(raw) ? raw : fromBip21(raw, "lightning", isLightningInvoice);
20600
+ return invoice?.replace(/^lightning:/i, "");
20601
+ };
20602
+
20603
+ // src/payment/amount.ts
20604
+ function assertSendableAmount(railId, amt) {
20605
+ if (!Number.isInteger(amt) || amt <= 0) {
20606
+ throw new Error(`${railId}: invalid amount ${amt} sats (expected a positive integer)`);
20607
+ }
20608
+ }
20609
+ function resolveSendAmount(railId, raw, amount) {
20610
+ const amt = amount ?? BIP21.amountSats(raw);
20611
+ if (amt === void 0) {
20612
+ throw new Error(
20613
+ `${railId}: an amount is required (none provided or encoded in the request)`
20614
+ );
20615
+ }
20616
+ assertSendableAmount(railId, amt);
20617
+ return amt;
20618
+ }
20619
+ function tryResolveSendAmount(raw, amount) {
20620
+ const amt = amount ?? BIP21.amountSats(raw);
20621
+ return amt !== void 0 && Number.isInteger(amt) && amt > 0 ? amt : void 0;
20622
+ }
20623
+
20624
+ // src/payment/handle.ts
20625
+ function makeHandle(id, run) {
20626
+ let status = "pending";
20627
+ let last = { status };
20628
+ const subs = /* @__PURE__ */ new Set();
20629
+ const notify = (f, u) => {
20630
+ try {
20631
+ f(u);
20632
+ } catch {
20633
+ }
20634
+ };
20635
+ const isTerminal = (s) => s === "settled" || s === "failed";
20636
+ const emit = (u) => {
20637
+ status = u.status;
20638
+ last = u;
20639
+ for (const f of subs) notify(f, u);
20640
+ if (isTerminal(u.status)) subs.clear();
20641
+ };
20642
+ const done = run(emit);
20643
+ done.catch((error) => {
20644
+ if (status !== "settled") emit({ status: "failed", error });
20645
+ });
20646
+ return {
20647
+ id,
20648
+ get status() {
20649
+ return status;
20650
+ },
20651
+ subscribe(fn) {
20652
+ notify(fn, last);
20653
+ if (isTerminal(status)) return () => {
20654
+ };
20655
+ subs.add(fn);
20656
+ return () => subs.delete(fn);
20657
+ },
20658
+ settled(opts) {
20659
+ if (!opts?.timeoutMs) return done;
20660
+ let timer;
20661
+ return Promise.race([
20662
+ done,
20663
+ new Promise((_, rej) => {
20664
+ timer = setTimeout(() => rej(new Error("settle timeout")), opts.timeoutMs);
20665
+ })
20666
+ ]).finally(() => clearTimeout(timer));
20667
+ }
20668
+ };
20669
+ }
20670
+
20671
+ // src/payment/router.ts
20672
+ var AmbiguousRouteError = class extends Error {
20673
+ constructor(railIds) {
20674
+ super(`ambiguous route: ${railIds.join(", ")}`);
20675
+ this.railIds = railIds;
20676
+ this.name = "AmbiguousRouteError";
20677
+ }
20678
+ railIds;
20679
+ };
20680
+ var PaymentRouter = class {
20681
+ constructor(ctx) {
20682
+ this.ctx = ctx;
20683
+ }
20684
+ ctx;
20685
+ rails = /* @__PURE__ */ new Map();
20686
+ /** Register a rail (or overwrite one with the same id). */
20687
+ use(rail) {
20688
+ this.rails.set(rail.id, rail);
20689
+ return this;
20690
+ }
20691
+ /** Remove a rail by id. */
20692
+ remove(id) {
20693
+ this.rails.delete(id);
20694
+ return this;
20695
+ }
20696
+ /** Rails matching `req`, filtered by `disabled`/`available()`, ranked by
20697
+ * `priority` (unlisted rails keep insertion order, after listed ones). */
20698
+ async options(req, prefs) {
20699
+ const merged = { ...this.ctx.prefs, ...prefs };
20700
+ const ctx = { ...this.ctx, prefs: merged };
20701
+ const matched = [...this.rails.values()].filter(
20702
+ (rail) => !merged.disabled?.includes(rail.id) && rail.match(req, ctx)
20703
+ );
20704
+ const available = (await Promise.all(
20705
+ matched.map(async (rail) => {
20706
+ try {
20707
+ return await rail.available?.(req, ctx) ?? true ? rail : null;
20708
+ } catch (e) {
20709
+ console.warn(`payment rail "${rail.id}": available() threw`, e);
20710
+ return null;
20711
+ }
20712
+ })
20713
+ )).filter((rail) => rail !== null);
20714
+ const rank = (id) => {
20715
+ const i = merged.priority?.indexOf(id) ?? -1;
20716
+ return i < 0 ? Number.MAX_SAFE_INTEGER : i;
20717
+ };
20718
+ available.sort((a, b) => rank(a.id) - rank(b.id));
20719
+ return available.map((rail) => ({
20720
+ railId: rail.id,
20721
+ quote: () => rail.quote(req, ctx)
20722
+ }));
20723
+ }
20724
+ /** Top-ranked option's quote. Throws when nothing routes, or
20725
+ * {@link AmbiguousRouteError} when `tieBreak` is `"require-choice"` and
20726
+ * more than one option survives. */
20727
+ async route(req, prefs) {
20728
+ const options = await this.options(req, prefs);
20729
+ if (options.length === 0) {
20730
+ throw new Error(`no rail for: ${req.raw}`);
20731
+ }
20732
+ const tieBreak = { ...this.ctx.prefs, ...prefs }.tieBreak ?? "first";
20733
+ if (tieBreak === "require-choice" && options.length > 1) {
20734
+ throw new AmbiguousRouteError(options.map((o) => o.railId));
20735
+ }
20736
+ return options[0].quote();
20737
+ }
20738
+ };
20739
+
20740
+ // src/payment/rails/ark.ts
20741
+ function arkRail() {
20742
+ return {
20743
+ id: "ark",
20744
+ match: (req) => arkTarget(req.raw) !== void 0,
20745
+ quote: async (req, ctx) => {
20746
+ const address = arkTarget(req.raw);
20747
+ const amt = resolveSendAmount("ark", req.raw, req.amount);
20748
+ return {
20749
+ railId: "ark",
20750
+ amount: amt,
20751
+ fee: 0,
20752
+ total: amt,
20753
+ send: async () => makeHandle("ark", async (emit) => {
20754
+ const txid = await ctx.wallet.send({ address, amount: amt });
20755
+ const result = { railId: "ark", txid };
20756
+ emit({ status: "settled", result });
20757
+ return result;
20758
+ })
20759
+ };
20760
+ }
20761
+ };
20762
+ }
20763
+ var GROSS_UP_MAX_ROUNDS = 8;
20764
+ function grossUpOffboard(net, feeInfo, script) {
20765
+ const estimator = new Estimator(feeInfo?.intentFee ?? {});
20766
+ const feeAt = (amount) => estimator.evalOnchainOutput({ amount: BigInt(amount), script }).satoshis;
20767
+ let gross = net;
20768
+ for (let i = 0; i < GROSS_UP_MAX_ROUNDS; i++) {
20769
+ const next = net + feeAt(gross);
20770
+ if (next === gross) break;
20771
+ gross = next;
20772
+ }
20773
+ return { gross, fee: gross - net };
20774
+ }
20775
+ function onchainRail() {
20776
+ return {
20777
+ id: "onchain",
20778
+ match: (req) => btcTarget(req.raw) !== void 0,
20779
+ quote: async (req, ctx) => {
20780
+ const address = btcTarget(req.raw);
20781
+ const amt = resolveSendAmount("onchain", req.raw, req.amount);
20782
+ const { fees } = await ctx.wallet.arkProvider.getInfo();
20783
+ const script = hex.encode(offboardDestinationScript(address));
20784
+ const { gross, fee } = grossUpOffboard(amt, fees, script);
20785
+ return {
20786
+ railId: "onchain",
20787
+ amount: amt,
20788
+ fee,
20789
+ total: gross,
20790
+ send: async () => makeHandle("onchain", async (emit) => {
20791
+ const txid = await new Ramps(ctx.wallet).offboard(
20792
+ address,
20793
+ fees,
20794
+ BigInt(gross)
20795
+ );
20796
+ const result = { railId: "onchain", txid };
20797
+ emit({ status: "settled", result });
20798
+ return result;
20799
+ })
20800
+ };
20801
+ }
20802
+ };
20803
+ }
20804
+
20805
+ // src/payment/index.ts
20806
+ function createDefaultPaymentRouter(wallet) {
20807
+ return new PaymentRouter({
20808
+ wallet,
20809
+ prefs: { priority: ["ark", "lightning", "onchain"] }
20810
+ }).use(arkRail()).use(onchainRail());
20811
+ }
20115
20812
  var ARKCONTRACT_PREFIX = "arkcontract";
20116
20813
  function encodeArkContract(contract) {
20117
20814
  const params = new URLSearchParams();
@@ -20171,6 +20868,6 @@ function isArkContract(str) {
20171
20868
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
20172
20869
  }
20173
20870
 
20174
- export { ARKADE_MAGIC, ActivityRegistry, ArkNote, ArkadeCash, ArkadeCashCreateError, 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, hasCandidates, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isMessageBusInitializingError, isMessageBusNotInitializedError, isRetryableProviderError, 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 };
20175
- //# sourceMappingURL=chunk-3MMUKLCI.js.map
20176
- //# sourceMappingURL=chunk-3MMUKLCI.js.map
20871
+ export { ARKADE_MAGIC, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, 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, PaymentRouter, 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, arkRail, arkTarget, arkade_exports, assertIntentIdUnique, assertSendableAmount, assertSubmittedArkTxid, assetMintResolver, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isArkContract, isBatchSignable, isBtcAddress, isCooperativelyMigratable, isDiscoverable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, onchainRail, openDatabase, requiresMigration, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, tryResolveSendAmount, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
20872
+ //# sourceMappingURL=chunk-QO3IKBYL.js.map
20873
+ //# sourceMappingURL=chunk-QO3IKBYL.js.map