@arkade-os/sdk 0.4.74 → 0.4.76

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 (75) 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/{asyncStorageTaskQueue-D1AwLuqn.d.cts → asyncStorageTaskQueue-D7BU-BdP.d.cts} +2 -2
  8. package/dist/{asyncStorageTaskQueue-aCaOBGTS.d.ts → asyncStorageTaskQueue-DrKbfleJ.d.ts} +2 -2
  9. package/dist/{chunk-J4ZR4VEM.cjs → chunk-2KFXM3N5.cjs} +17 -17
  10. package/dist/{chunk-J4ZR4VEM.cjs.map → chunk-2KFXM3N5.cjs.map} +1 -1
  11. package/dist/{chunk-X5BPARRD.js → chunk-54W6QBXL.js} +3 -3
  12. package/dist/{chunk-X5BPARRD.js.map → chunk-54W6QBXL.js.map} +1 -1
  13. package/dist/{chunk-LGB65ZCO.js → chunk-7UJLIZQU.js} +12 -5
  14. package/dist/chunk-7UJLIZQU.js.map +1 -0
  15. package/dist/{chunk-DXLVCFVK.cjs → chunk-CQNX4BTA.cjs} +58 -51
  16. package/dist/chunk-CQNX4BTA.cjs.map +1 -0
  17. package/dist/{chunk-WJVOFYTQ.js → chunk-F6YOCYWU.js} +3 -3
  18. package/dist/{chunk-WJVOFYTQ.js.map → chunk-F6YOCYWU.js.map} +1 -1
  19. package/dist/{chunk-WIHSU3YS.cjs → chunk-LHSVPDFJ.cjs} +788 -588
  20. package/dist/chunk-LHSVPDFJ.cjs.map +1 -0
  21. package/dist/{chunk-QS6AZE4F.cjs → chunk-LQM6RHNT.cjs} +10 -10
  22. package/dist/{chunk-QS6AZE4F.cjs.map → chunk-LQM6RHNT.cjs.map} +1 -1
  23. package/dist/{chunk-J3XVGWMP.js → chunk-MUBPGYUE.js} +4 -4
  24. package/dist/{chunk-J3XVGWMP.js.map → chunk-MUBPGYUE.js.map} +1 -1
  25. package/dist/{chunk-KPLQN4M4.js → chunk-O2BIWLZC.js} +4 -4
  26. package/dist/{chunk-KPLQN4M4.js.map → chunk-O2BIWLZC.js.map} +1 -1
  27. package/dist/{chunk-KMPNH7NS.cjs → chunk-P53OHGXY.cjs} +3 -3
  28. package/dist/{chunk-KMPNH7NS.cjs.map → chunk-P53OHGXY.cjs.map} +1 -1
  29. package/dist/{chunk-AS3W34MG.cjs → chunk-T327OEMV.cjs} +17 -17
  30. package/dist/{chunk-AS3W34MG.cjs.map → chunk-T327OEMV.cjs.map} +1 -1
  31. package/dist/{chunk-ZTGTQP2W.js → chunk-WMHELOHH.js} +353 -154
  32. package/dist/chunk-WMHELOHH.js.map +1 -0
  33. package/dist/contracts/handlers/index.cjs +9 -9
  34. package/dist/contracts/handlers/index.d.cts +3 -3
  35. package/dist/contracts/handlers/index.d.ts +3 -3
  36. package/dist/contracts/handlers/index.js +2 -2
  37. package/dist/{delegate-z6veQfLI.d.ts → delegate-BAYpqkEx.d.ts} +1 -1
  38. package/dist/{delegate-6N0DKdnG.d.cts → delegate-CR7msIXK.d.cts} +1 -1
  39. package/dist/{hdWalletCapable-Bncp-eF0.d.ts → hdWalletCapable-BSF29IjK.d.ts} +3 -11
  40. package/dist/{hdWalletCapable-DJT8E8sN.d.cts → hdWalletCapable-BbZxCUeZ.d.cts} +3 -11
  41. package/dist/{index-BVqlZJwW.d.cts → index-4NF3srcg.d.cts} +2 -2
  42. package/dist/{index-Bxz3cKPA.d.ts → index-B_RYn9rg.d.cts} +13 -0
  43. package/dist/{index-Bxz3cKPA.d.cts → index-B_RYn9rg.d.ts} +13 -0
  44. package/dist/{index-CKQQ1oOg.d.ts → index-Dj2yOhQ2.d.ts} +2 -2
  45. package/dist/index.cjs +306 -302
  46. package/dist/index.d.cts +51 -14
  47. package/dist/index.d.ts +51 -14
  48. package/dist/index.js +4 -4
  49. package/dist/repositories/realm/index.cjs +19 -19
  50. package/dist/repositories/realm/index.d.cts +2 -2
  51. package/dist/repositories/realm/index.d.ts +2 -2
  52. package/dist/repositories/realm/index.js +5 -5
  53. package/dist/repositories/sqlite/index.cjs +18 -18
  54. package/dist/repositories/sqlite/index.d.cts +2 -2
  55. package/dist/repositories/sqlite/index.d.ts +2 -2
  56. package/dist/repositories/sqlite/index.js +5 -5
  57. package/dist/{taskRunner-C26IIg1F.d.cts → taskRunner-DGxde-YU.d.cts} +2 -2
  58. package/dist/{taskRunner-KxtIKYLv.d.ts → taskRunner-TzGUhxP-.d.ts} +2 -2
  59. package/dist/wallet/expo/background.cjs +14 -14
  60. package/dist/wallet/expo/background.d.cts +3 -3
  61. package/dist/wallet/expo/background.d.ts +3 -3
  62. package/dist/wallet/expo/background.js +6 -6
  63. package/dist/wallet/expo/index.cjs +14 -14
  64. package/dist/wallet/expo/index.d.cts +4 -4
  65. package/dist/wallet/expo/index.d.ts +4 -4
  66. package/dist/wallet/expo/index.js +5 -5
  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-DXLVCFVK.cjs.map +0 -1
  73. package/dist/chunk-LGB65ZCO.js.map +0 -1
  74. package/dist/chunk-WIHSU3YS.cjs.map +0 -1
  75. package/dist/chunk-ZTGTQP2W.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-X5BPARRD.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, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-LGB65ZCO.js';
3
- import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, getAllNormalizedVtxos, isPastExpiry, isRetryableProviderError, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, PrevoutTxField, fetchVtxoCreatedAtByTxid, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1 } from './chunk-WJVOFYTQ.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-54W6QBXL.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, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-7UJLIZQU.js';
3
+ import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, networks, getNetwork, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, getAllNormalizedVtxos, isPastExpiry, isRetryableProviderError, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, PrevoutTxField, fetchVtxoCreatedAtByTxid, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError } from './chunk-F6YOCYWU.js';
4
4
  import { __export } from './chunk-NSBPE2FW.js';
5
5
  import { sha256 as sha256$1, 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';
@@ -13,7 +13,7 @@ import { Script as Script$1 } from '@scure/btc-signer/script.js';
13
13
  import { Transaction as Transaction$1, SigHash as SigHash$1 } from '@scure/btc-signer/transaction.js';
14
14
  import { mnemonicToSeedSync, validateMnemonic } from '@scure/bip39';
15
15
  import { wordlist } from '@scure/bip39/wordlists/english.js';
16
- import { networks, scriptExpressions, HDKey, expand } from '@bitcoinerlab/descriptors-scure';
16
+ import { networks as networks$1, scriptExpressions, HDKey, expand } from '@bitcoinerlab/descriptors-scure';
17
17
  import { tapLeafHash } from '@scure/btc-signer/payment.js';
18
18
  import { Environment } from '@marcbachmann/cel-js';
19
19
 
@@ -410,9 +410,9 @@ var SeedIdentity = class _SeedIdentity {
410
410
  let network;
411
411
  if ("descriptor" in opts && typeof opts.descriptor === "string") {
412
412
  descriptor = opts.descriptor;
413
- network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
413
+ network = isMainnetDescriptor(descriptor) ? networks$1.bitcoin : networks$1.testnet;
414
414
  } else {
415
- network = opts.isMainnet ?? true ? networks.bitcoin : networks.testnet;
415
+ network = opts.isMainnet ?? true ? networks$1.bitcoin : networks$1.testnet;
416
416
  descriptor = scriptExpressions.trBIP32({
417
417
  masterNode: HDKey.fromMasterSeed(seed, network.bip32),
418
418
  network,
@@ -557,7 +557,7 @@ var SeedIdentity = class _SeedIdentity {
557
557
  }
558
558
  // ── internal helpers ─────────────────────────────────────────────
559
559
  derivePrivateKeyForDescriptor(descriptor) {
560
- const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
560
+ const network = isMainnetDescriptor(descriptor) ? networks$1.bitcoin : networks$1.testnet;
561
561
  const expansion = expand({ descriptor, network });
562
562
  if (expansion.isRanged) {
563
563
  throw new Error(
@@ -644,7 +644,7 @@ var ReadonlyDescriptorIdentity = class _ReadonlyDescriptorIdentity {
644
644
  */
645
645
  descriptor;
646
646
  constructor(descriptor) {
647
- const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
647
+ const network = isMainnetDescriptor(descriptor) ? networks$1.bitcoin : networks$1.testnet;
648
648
  let expansion;
649
649
  try {
650
650
  expansion = expand({ descriptor, network, index: 0 });
@@ -3918,7 +3918,7 @@ function parseProgram(text, env) {
3918
3918
  var DUST_AMOUNT = 546;
3919
3919
  var FALLBACK_WALLET_DUST_AMOUNT = 330n;
3920
3920
  function getDustAmount(wallet) {
3921
- return "dustAmount" in wallet ? wallet.dustAmount : FALLBACK_WALLET_DUST_AMOUNT;
3921
+ return wallet && "dustAmount" in wallet ? wallet.dustAmount : FALLBACK_WALLET_DUST_AMOUNT;
3922
3922
  }
3923
3923
  function extendCoinWithTapscript(boardingTapscript, utxo) {
3924
3924
  return {
@@ -8687,15 +8687,74 @@ async function getVtxosForContract(repo, contract) {
8687
8687
  const vtxos = repo.getVtxosForScript ? await repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
8688
8688
  return vtxos.map(normalizeVtxo);
8689
8689
  }
8690
- async function saveVtxosForContract(repo, contract, vtxos) {
8691
- if (repo.saveVtxosForScript) {
8692
- return repo.saveVtxosForScript(
8693
- { script: contract.script, address: contract.address },
8694
- vtxos
8695
- );
8690
+ function hasRecordedSpend(vtxo) {
8691
+ return vtxo.isSpent === true && (!!vtxo.spentBy || !!vtxo.arkTxId || !!vtxo.settledBy);
8692
+ }
8693
+ var RECORDED_SPEND_TTL_MS = 6e4;
8694
+ var recordedSpends = /* @__PURE__ */ new WeakMap();
8695
+ function registryFor(repo) {
8696
+ const existing = recordedSpends.get(repo);
8697
+ if (existing) return existing;
8698
+ const created = /* @__PURE__ */ new Map();
8699
+ recordedSpends.set(repo, created);
8700
+ return created;
8701
+ }
8702
+ var writeChains = /* @__PURE__ */ new WeakMap();
8703
+ function inVtxoWriteOrder(repo, fn) {
8704
+ const prev = writeChains.get(repo) ?? Promise.resolve();
8705
+ const run = prev.then(fn, fn);
8706
+ writeChains.set(
8707
+ repo,
8708
+ run.catch(() => {
8709
+ })
8710
+ );
8711
+ return run;
8712
+ }
8713
+ function applyRecordedSpends(repo, vtxos) {
8714
+ const registry = registryFor(repo);
8715
+ const now = Date.now();
8716
+ for (const [key, spend] of registry) {
8717
+ if (now - spend.at >= RECORDED_SPEND_TTL_MS) registry.delete(key);
8718
+ }
8719
+ for (const incoming of vtxos) {
8720
+ const key = vtxoOutpoint(incoming);
8721
+ if (!hasRecordedSpend(incoming) || registry.has(key)) continue;
8722
+ registry.set(key, {
8723
+ at: now,
8724
+ spentBy: incoming.spentBy,
8725
+ arkTxId: incoming.arkTxId,
8726
+ settledBy: incoming.settledBy
8727
+ });
8696
8728
  }
8697
- validateVtxosForScript(vtxos, contract.script, "saveVtxosForContract");
8698
- return repo.saveVtxos(contract.address, vtxos);
8729
+ if (registry.size === 0) return vtxos;
8730
+ return vtxos.map((incoming) => {
8731
+ if (hasRecordedSpend(incoming)) return incoming;
8732
+ const spent = registry.get(vtxoOutpoint(incoming));
8733
+ if (!spent) return incoming;
8734
+ const spentBy = incoming.spentBy || spent.spentBy;
8735
+ const arkTxId = incoming.arkTxId || spent.arkTxId;
8736
+ const settledBy = incoming.settledBy || spent.settledBy;
8737
+ return {
8738
+ ...incoming,
8739
+ isSpent: true,
8740
+ ...spentBy ? { spentBy } : {},
8741
+ ...arkTxId ? { arkTxId } : {},
8742
+ ...settledBy ? { settledBy } : {}
8743
+ };
8744
+ });
8745
+ }
8746
+ async function saveVtxosForContract(repo, contract, vtxos) {
8747
+ return inVtxoWriteOrder(repo, async () => {
8748
+ const rows = applyRecordedSpends(repo, vtxos);
8749
+ if (repo.saveVtxosForScript) {
8750
+ return repo.saveVtxosForScript(
8751
+ { script: contract.script, address: contract.address },
8752
+ rows
8753
+ );
8754
+ }
8755
+ validateVtxosForScript(rows, contract.script, "saveVtxosForContract");
8756
+ return repo.saveVtxos(contract.address, rows);
8757
+ });
8699
8758
  }
8700
8759
 
8701
8760
  // src/repositories/inMemory/walletRepository.ts
@@ -9695,7 +9754,8 @@ function areCoalescibleContractTypes(a, b) {
9695
9754
  function toWatchOnlyContract(params) {
9696
9755
  return { ...params, state: params.state ?? "active", createdAt: Date.now() };
9697
9756
  }
9698
- function annotatableIn(scriptToContract, vtxos) {
9757
+ var TAPSCRIPT_MEMO_MAX_ENTRIES = 1024;
9758
+ function annotatableIn(scriptToContract, vtxos, memo) {
9699
9759
  const scripts = /* @__PURE__ */ new Set();
9700
9760
  const cache = /* @__PURE__ */ new Map();
9701
9761
  const failures = /* @__PURE__ */ new Map();
@@ -9703,7 +9763,25 @@ function annotatableIn(scriptToContract, vtxos) {
9703
9763
  const contract = scriptToContract.get(script);
9704
9764
  if (!contract) continue;
9705
9765
  try {
9706
- cache.set(script, deriveContractTapscripts(contract));
9766
+ const key = `${contract.type}\0${script}\0${JSON.stringify(contract.params)}`;
9767
+ const handler = contractHandlers.get(contract.type);
9768
+ const hit = memo?.get(script);
9769
+ let tapscripts;
9770
+ if (hit && hit.key === key && hit.handler === handler) {
9771
+ tapscripts = hit.tapscripts;
9772
+ memo.delete(script);
9773
+ memo.set(script, hit);
9774
+ } else {
9775
+ memo?.delete(script);
9776
+ tapscripts = deriveContractTapscripts(contract);
9777
+ if (handler && memo) {
9778
+ memo.set(script, { key, handler, tapscripts });
9779
+ if (memo.size > TAPSCRIPT_MEMO_MAX_ENTRIES) {
9780
+ memo.delete(memo.keys().next().value);
9781
+ }
9782
+ }
9783
+ }
9784
+ cache.set(script, tapscripts);
9707
9785
  scripts.add(script);
9708
9786
  } catch (err) {
9709
9787
  failures.set(
@@ -9721,6 +9799,8 @@ var CHAIN_TIP_TIMEOUT_MS = 5e3;
9721
9799
  var ContractManager = class _ContractManager {
9722
9800
  config;
9723
9801
  watcher;
9802
+ /** Per-sync tapscript caches start empty; this outlives them. Consumers only ever see clones. */
9803
+ tapscriptMemo = /* @__PURE__ */ new Map();
9724
9804
  initialized = false;
9725
9805
  eventCallbacks = /* @__PURE__ */ new Set();
9726
9806
  stopWatcherFn;
@@ -9788,7 +9868,21 @@ var ContractManager = class _ContractManager {
9788
9868
  }
9789
9869
  markSyncOnline() {
9790
9870
  this.lastSyncedAt = Date.now();
9791
- this.syncDegradedReason = this.annotationDegradedReason();
9871
+ this.syncDegradedReason = this.syncGapReason();
9872
+ }
9873
+ /**
9874
+ * Why a sync that just succeeded still has not covered everything it should
9875
+ * have. Both reasons outlive the sync that failed to cover them, so neither
9876
+ * is discharged by the next one succeeding — which is the point: a wallet
9877
+ * that reported itself current while the look-ahead band sat behind the
9878
+ * watermark, or while a contract could not be annotated, would be claiming
9879
+ * coverage it never had. `getSyncState()` is the channel apps already watch.
9880
+ */
9881
+ syncGapReason() {
9882
+ const annotations = this.annotationDegradedReason();
9883
+ const owedRefill = this.lookAheadRefillOwed ? "the look-ahead band is behind the allocation watermark, so a funded address inside it is not being watched yet" : void 0;
9884
+ const reasons = [annotations, owedRefill].filter((r) => r !== void 0);
9885
+ return reasons.length === 0 ? void 0 : reasons.join("; ");
9792
9886
  }
9793
9887
  /** Contracts a sync could not annotate, as `script → reason`. */
9794
9888
  annotationFailures = /* @__PURE__ */ new Map();
@@ -9822,7 +9916,13 @@ var ContractManager = class _ContractManager {
9822
9916
  for (const contract of contracts) {
9823
9917
  await this.watcher.addContract(contract);
9824
9918
  }
9825
- await this.scheduleLookAheadDrain();
9919
+ try {
9920
+ await this.scheduleLookAheadDrain();
9921
+ } catch (err) {
9922
+ if (!isRetryableProviderError(err)) throw err;
9923
+ this.lookAheadRefillOwed = true;
9924
+ this.markSyncDegraded(err);
9925
+ }
9826
9926
  try {
9827
9927
  await this.reconcileWatched();
9828
9928
  this.markSyncOnline();
@@ -10425,7 +10525,8 @@ var ContractManager = class _ContractManager {
10425
10525
  });
10426
10526
  const { scripts, failures } = annotatableIn(
10427
10527
  new Map(contracts.map((contract) => [contract.script, contract])),
10428
- vtxos
10528
+ vtxos,
10529
+ this.tapscriptMemo
10429
10530
  );
10430
10531
  const orphans = vtxos.filter(
10431
10532
  (vtxo) => !scripts.has(vtxo.script) && !failures.has(vtxo.script)
@@ -10576,6 +10677,7 @@ var ContractManager = class _ContractManager {
10576
10677
  */
10577
10678
  async deleteContract(script) {
10578
10679
  await this.config.contractRepository.deleteContract(script);
10680
+ this.tapscriptMemo.delete(script);
10579
10681
  await this.watcher.removeContract(script);
10580
10682
  }
10581
10683
  /**
@@ -10746,7 +10848,13 @@ var ContractManager = class _ContractManager {
10746
10848
  if (contract) {
10747
10849
  await saveVtxosForContract(this.config.walletRepository, contract, addressVtxos);
10748
10850
  } else {
10749
- await this.config.walletRepository.saveVtxos(address, addressVtxos);
10851
+ await inVtxoWriteOrder(
10852
+ this.config.walletRepository,
10853
+ async () => this.config.walletRepository.saveVtxos(
10854
+ address,
10855
+ applyRecordedSpends(this.config.walletRepository, addressVtxos)
10856
+ )
10857
+ );
10750
10858
  }
10751
10859
  }
10752
10860
  }
@@ -10915,7 +11023,11 @@ var ContractManager = class _ContractManager {
10915
11023
  for (const [script, contract] of await this.promoteLookAheadHits(vtxos)) {
10916
11024
  scriptToContract.set(script, contract);
10917
11025
  }
10918
- const { scripts: annotatable, cache, failures } = annotatableIn(scriptToContract, vtxos);
11026
+ const {
11027
+ scripts: annotatable,
11028
+ cache,
11029
+ failures
11030
+ } = annotatableIn(scriptToContract, vtxos, this.tapscriptMemo);
10919
11031
  this.recordAnnotationFailures(annotatable, failures);
10920
11032
  const owned = vtxos.filter((v) => annotatable.has(v.script));
10921
11033
  const annotated = await this.annotateVtxos(owned, cache);
@@ -11015,7 +11127,11 @@ var ContractManager = class _ContractManager {
11015
11127
  for (const [script, contract] of promoted) {
11016
11128
  scriptToContract.set(script, contract);
11017
11129
  }
11018
- const { scripts: annotatable, cache, failures } = annotatableIn(scriptToContract, vtxos);
11130
+ const {
11131
+ scripts: annotatable,
11132
+ cache,
11133
+ failures
11134
+ } = annotatableIn(scriptToContract, vtxos, this.tapscriptMemo);
11019
11135
  this.recordAnnotationFailures(annotatable, failures);
11020
11136
  const owned = vtxos.filter((v) => annotatable.has(v.script));
11021
11137
  const annotated = await this.annotateVtxos(owned, cache);
@@ -11974,7 +12090,7 @@ var HDDescriptorProvider = class _HDDescriptorProvider {
11974
12090
  */
11975
12091
  materializeDescriptorAt(index) {
11976
12092
  const descriptor = this.identity.descriptor;
11977
- const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
12093
+ const network = isMainnetDescriptor(descriptor) ? networks$1.bitcoin : networks$1.testnet;
11978
12094
  const expansion = expand({ descriptor, network, index });
11979
12095
  const keyInfo = expansion.expansionMap?.["@0"];
11980
12096
  if (!keyInfo?.keyExpression) {
@@ -12422,7 +12538,7 @@ async function resolveDescriptorProvider(config, walletRepository) {
12422
12538
 
12423
12539
  // src/wallet/balance.ts
12424
12540
  function computeOffchainBalance(vtxos, caps) {
12425
- const { now, isPendingRecovery, isGenericallySpendable, isUnlocked } = caps;
12541
+ const { now, isPendingRecovery, isGenericallySpendable, isUnlocked, dustCarrier } = caps;
12426
12542
  let settled = 0;
12427
12543
  let preconfirmed = 0;
12428
12544
  let available = 0;
@@ -12468,6 +12584,9 @@ function computeOffchainBalance(vtxos, caps) {
12468
12584
  addAssets(spendable, vtxo);
12469
12585
  }
12470
12586
  }
12587
+ if (available > 0 && spendable.size > 0) {
12588
+ available = Math.max(0, available - Number(dustCarrier));
12589
+ }
12471
12590
  const toAssets = (from) => Array.from(from.entries()).map(([assetId, amount]) => ({ assetId, amount }));
12472
12591
  return {
12473
12592
  settled,
@@ -12525,8 +12644,9 @@ var InputSignerRouter = class {
12525
12644
  async classify(jobs) {
12526
12645
  const identityIndexes = [];
12527
12646
  const descriptorGroups = /* @__PURE__ */ new Map();
12647
+ const unknownIndexes = [];
12528
12648
  if (jobs.length === 0) {
12529
- return { identityIndexes, descriptorGroups };
12649
+ return { identityIndexes, descriptorGroups, unknownIndexes };
12530
12650
  }
12531
12651
  const distinctScripts = Array.from(new Set(jobs.map((j) => hex.encode(j.lookupScript))));
12532
12652
  const contracts = await this.deps.contractRepository.getContracts({
@@ -12546,6 +12666,8 @@ var InputSignerRouter = class {
12546
12666
  if (!contract) {
12547
12667
  if (scriptHex === boardingScriptHex) {
12548
12668
  identityIndexes.push(job.index);
12669
+ } else {
12670
+ unknownIndexes.push(job.index);
12549
12671
  }
12550
12672
  continue;
12551
12673
  }
@@ -12572,7 +12694,7 @@ var InputSignerRouter = class {
12572
12694
  descriptorGroups.set(descriptor, [job.index]);
12573
12695
  }
12574
12696
  }
12575
- return { identityIndexes, descriptorGroups };
12697
+ return { identityIndexes, descriptorGroups, unknownIndexes };
12576
12698
  }
12577
12699
  /**
12578
12700
  * Returns `true` when every signable input across all `jobSets` resolves
@@ -12594,12 +12716,13 @@ var InputSignerRouter = class {
12594
12716
  const plan = await this.classify(jobSets.flat());
12595
12717
  return plan.descriptorGroups.size === 0;
12596
12718
  }
12597
- async sign(tx, jobs) {
12719
+ async sign(tx, jobs, opts) {
12598
12720
  if (jobs.length === 0) return tx;
12599
- const { identityIndexes, descriptorGroups } = await this.classify(jobs);
12721
+ const { identityIndexes, descriptorGroups, unknownIndexes } = await this.classify(jobs);
12722
+ const identitySignIndexes = opts?.onUnknownScript === "sign" ? [...identityIndexes, ...unknownIndexes] : identityIndexes;
12600
12723
  let signed = tx;
12601
- if (identityIndexes.length > 0) {
12602
- signed = await this.deps.identity.sign(signed, identityIndexes);
12724
+ if (identitySignIndexes.length > 0) {
12725
+ signed = await this.deps.identity.sign(signed, identitySignIndexes);
12603
12726
  }
12604
12727
  if (descriptorGroups.size > 0) {
12605
12728
  if (!this.deps.descriptorProvider) {
@@ -13390,7 +13513,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
13390
13513
  now: { timestamp: /* @__PURE__ */ new Date() },
13391
13514
  isPendingRecovery: (vtxo) => pendingRecovery.has(`${vtxo.txid}:${vtxo.vout}`),
13392
13515
  isGenericallySpendable: (vtxo) => !isGatedVtxo(vtxo, gated),
13393
- isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`)
13516
+ isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`),
13517
+ dustCarrier: getDustAmount(this)
13394
13518
  };
13395
13519
  }
13396
13520
  /**
@@ -13626,23 +13750,26 @@ var ReadonlyWallet = class _ReadonlyWallet {
13626
13750
  */
13627
13751
  async getBoardingUtxosForSigners(allowedSigners) {
13628
13752
  const tapscripts = await this.getBoardingTapscripts(allowedSigners);
13629
- const groups = [];
13630
- for (const tapscript of tapscripts) {
13631
- const address = tapscript.onchainAddress(this.network);
13632
- const coins = await this.onchainProvider.getCoins(address);
13633
- const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
13634
- await this.walletRepository.saveUtxos(address, utxos);
13635
- groups.push({
13636
- tapscript,
13637
- // Normalize so the group key matches the axis/contract x-only
13638
- // form regardless of how the tapscript's key was stored.
13639
- serverPubKey: toXOnlySignerHex(hex.encode(tapscript.options.serverPubKey)),
13640
- // Per-row CSV delay decoded from THIS tapscript's exit leaf —
13641
- // not the wallet's current boarding timelock, which a signer
13642
- // rotation may have changed.
13643
- csvTimelock: CSVMultisigTapscript.decode(hex.decode(tapscript.exitScript)).params.timelock,
13644
- coins: utxos
13645
- });
13753
+ const addresses = tapscripts.map((tapscript) => tapscript.onchainAddress(this.network));
13754
+ const groups = await Promise.all(
13755
+ tapscripts.map(async (tapscript, i) => {
13756
+ const coins = await this.onchainProvider.getCoins(addresses[i]);
13757
+ const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
13758
+ return {
13759
+ tapscript,
13760
+ // Normalize so the group key matches the axis/contract x-only
13761
+ // form regardless of how the tapscript's key was stored.
13762
+ serverPubKey: toXOnlySignerHex(hex.encode(tapscript.options.serverPubKey)),
13763
+ // Per-row CSV delay decoded from THIS tapscript's exit leaf —
13764
+ // not the wallet's current boarding timelock, which a signer
13765
+ // rotation may have changed.
13766
+ csvTimelock: CSVMultisigTapscript.decode(hex.decode(tapscript.exitScript)).params.timelock,
13767
+ coins: utxos
13768
+ };
13769
+ })
13770
+ );
13771
+ for (const [i, group] of groups.entries()) {
13772
+ await this.walletRepository.saveUtxos(addresses[i], group.coins);
13646
13773
  }
13647
13774
  return groups;
13648
13775
  }
@@ -15738,15 +15865,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15738
15865
  console.error(`Failed to persist intent ${intentTxId} (state=${state})`, e);
15739
15866
  }
15740
15867
  }
15741
- /**
15742
- * @internal Sign an on-chain boarding exit / sweep transaction, routing
15743
- * each input to the correct key by its `witnessUtxo.script`: the identity
15744
- * for index-0 / static boarding, the per-index descriptor for a rotated
15745
- * boarding UTXO (plan §6-III.3). Used by
15746
- * {@link VtxoManager.sweepExpiredBoardingUtxos}; without it, the
15747
- * unilateral exit of a rotated boarding UTXO would be signed with the
15748
- * wrong (index-0) key and rejected.
15749
- */
15868
+ async signInputsByWitnessScript(tx) {
15869
+ const signed = await this._signerRouter.sign(
15870
+ tx,
15871
+ this.inputSigningJobsFromWitnessUtxos(tx),
15872
+ {
15873
+ onUnknownScript: "sign"
15874
+ }
15875
+ );
15876
+ return signed;
15877
+ }
15750
15878
  async signOnchainBoardingTx(tx) {
15751
15879
  const signed = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
15752
15880
  return signed;
@@ -17596,6 +17724,18 @@ var DustChangeError = class extends Error {
17596
17724
  this.dustAmount = dustAmount;
17597
17725
  }
17598
17726
  };
17727
+ var OversizedChangeError = class extends Error {
17728
+ change;
17729
+ maxAmount;
17730
+ constructor(change, maxAmount) {
17731
+ super(
17732
+ `change ${change} sats is above the server's ${maxAmount} sat per-output ceiling; exit more of the balance, or split it into smaller VTXOs first`
17733
+ );
17734
+ this.name = "OversizedChangeError";
17735
+ this.change = change;
17736
+ this.maxAmount = maxAmount;
17737
+ }
17738
+ };
17599
17739
  var offboardNetworkNames = [
17600
17740
  "bitcoin",
17601
17741
  "regtest",
@@ -17606,7 +17746,7 @@ var offboardNetworkNames = [
17606
17746
  function offboardDestinationScript(destinationAddress) {
17607
17747
  for (const networkName of offboardNetworkNames) {
17608
17748
  try {
17609
- const network = networks$1[networkName];
17749
+ const network = networks[networkName];
17610
17750
  const addr = Address(network).decode(destinationAddress);
17611
17751
  return OutScript.encode(addr);
17612
17752
  } catch {
@@ -17627,11 +17767,50 @@ function settleChangeAgainstFee(change, feeOn) {
17627
17767
  `change fee does not settle after ${CHANGE_FEE_MAX_ROUNDS} rounds (${change} sats of change); settle the full balance instead`
17628
17768
  );
17629
17769
  }
17770
+ function changeAfterOutputFee(left, feeOn) {
17771
+ if (left <= 0n) return { change: 0n, fee: 0n };
17772
+ const change = settleChangeAgainstFee(left, feeOn);
17773
+ return { change, fee: left - change };
17774
+ }
17630
17775
  function reportUngatedInputs(wallet, inputs) {
17631
17776
  if (!("logUngatedInputs" in wallet)) return;
17632
17777
  const logger = wallet;
17633
17778
  void logger.logUngatedInputs("Ramps.offboard({ vtxos })", inputs);
17634
17779
  }
17780
+ function filterOffboardInputs(vtxos, feeInfo, named) {
17781
+ const estimator = new Estimator(feeInfo?.intentFee ?? {});
17782
+ const inputs = [];
17783
+ let subtotal = 0n;
17784
+ for (const vtxo of vtxos) {
17785
+ const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
17786
+ if (inputFee.satoshis >= vtxo.value) {
17787
+ if (named) {
17788
+ throw new Error(
17789
+ `selected vtxo ${vtxo.txid}:${vtxo.vout} costs ${inputFee.satoshis} sats to spend and is worth ${vtxo.value} \u2014 drop it from the selection`
17790
+ );
17791
+ }
17792
+ continue;
17793
+ }
17794
+ inputs.push(vtxo);
17795
+ subtotal += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
17796
+ }
17797
+ if (inputs.length === 0) {
17798
+ throw new Error("No vtxos available after deducting fees");
17799
+ }
17800
+ return { inputs, subtotal };
17801
+ }
17802
+ async function serverVtxoMaxAmount(wallet) {
17803
+ const provider = wallet.arkProvider;
17804
+ if (!provider) return void 0;
17805
+ return (await provider.getInfo()).vtxoMaxAmount;
17806
+ }
17807
+ function assertChangeSettleable(change, dustAmount, maxAmount) {
17808
+ if (change === 0n) return;
17809
+ if (change < dustAmount) throw new DustChangeError(change, dustAmount);
17810
+ if (maxAmount !== void 0 && maxAmount >= 0n && change > maxAmount) {
17811
+ throw new OversizedChangeError(change, maxAmount);
17812
+ }
17813
+ }
17635
17814
  var Ramps = class {
17636
17815
  /**
17637
17816
  * Create convenience wrappers for onboarding and offboarding flows.
@@ -17757,81 +17936,87 @@ var Ramps = class {
17757
17936
  async offboard(destinationAddress, feeInfo, amount, eventCallback, vtxos) {
17758
17937
  const named = vtxos !== void 0;
17759
17938
  if (vtxos) reportUngatedInputs(this.wallet, vtxos);
17760
- vtxos ??= await this.wallet.getSpendableVtxos({
17939
+ const spendable = vtxos ?? await this.wallet.getSpendableVtxos({
17761
17940
  withRecoverable: true,
17762
17941
  withUnrolled: false
17763
17942
  });
17764
17943
  const estimator = new Estimator(feeInfo?.intentFee ?? {});
17765
- const filteredVtxos = [];
17766
- let totalAmount = 0n;
17767
- for (const vtxo of vtxos) {
17768
- const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
17769
- if (inputFee.satoshis >= vtxo.value) {
17770
- if (named) {
17771
- throw new Error(
17772
- `selected vtxo ${vtxo.txid}:${vtxo.vout} costs ${inputFee.satoshis} sats to spend and is worth ${vtxo.value} \u2014 drop it from the selection`
17773
- );
17774
- }
17775
- continue;
17776
- }
17777
- filteredVtxos.push(vtxo);
17778
- totalAmount += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
17779
- }
17780
- if (filteredVtxos.length === 0) {
17781
- throw new Error("No vtxos available after deducting fees");
17782
- }
17783
- let change = 0n;
17784
- if (amount) {
17785
- if (amount > totalAmount) {
17786
- throw new Error("Amount is greater than total amount of vtxos after fees");
17787
- }
17788
- change = totalAmount - amount;
17789
- }
17790
- let changeAddress;
17791
- if (change > 0n) {
17792
- changeAddress = await this.wallet.getAddress();
17793
- const changeScript = hex.encode(ArkAddress.decode(changeAddress).pkScript);
17794
- change = settleChangeAgainstFee(
17795
- change,
17796
- (amount2) => BigInt(estimator.evalOffchainOutput({ amount: amount2, script: changeScript }).satoshis)
17797
- );
17798
- }
17799
- const dustAmount = getDustAmount(this.wallet);
17800
- if (change > 0n && change < dustAmount) {
17801
- throw new DustChangeError(change, dustAmount);
17802
- }
17803
- amount = amount ?? totalAmount;
17804
- const destinationScript = offboardDestinationScript(destinationAddress);
17944
+ const { inputs, subtotal } = filterOffboardInputs(spendable, feeInfo, named);
17945
+ if (amount && amount > subtotal) {
17946
+ throw new Error("Amount is greater than total amount of vtxos after fees");
17947
+ }
17948
+ const handed = amount || subtotal;
17949
+ const { change, changeAddress } = await this.fundChange(
17950
+ amount ? subtotal - handed : 0n,
17951
+ estimator
17952
+ );
17805
17953
  const outputFee = estimator.evalOnchainOutput({
17806
- amount,
17807
- script: hex.encode(destinationScript)
17954
+ amount: handed,
17955
+ script: hex.encode(offboardDestinationScript(destinationAddress))
17808
17956
  });
17809
- if (BigInt(outputFee.satoshis) > amount) {
17957
+ if (BigInt(outputFee.satoshis) > handed) {
17810
17958
  throw new Error(
17811
- `can't deduct fees from offboard amount (${outputFee.satoshis} > ${amount})`
17959
+ `can't deduct fees from offboard amount (${outputFee.satoshis} > ${handed})`
17812
17960
  );
17813
17961
  }
17814
- amount -= BigInt(outputFee.satoshis);
17815
17962
  const outputs = [
17816
- {
17817
- address: destinationAddress,
17818
- amount
17819
- }
17963
+ { address: destinationAddress, amount: handed - BigInt(outputFee.satoshis) }
17820
17964
  ];
17821
- if (change > 0n) {
17822
- const offchainAddress = changeAddress;
17823
- outputs.push({
17824
- address: offchainAddress,
17825
- amount: change
17826
- });
17965
+ if (change > 0n) outputs.push({ address: changeAddress, amount: change });
17966
+ return this.wallet.settle({ inputs, outputs }, eventCallback);
17967
+ }
17968
+ /**
17969
+ * Collaboratively exit with the DESTINATION paid exactly `amount`, pricing the
17970
+ * fee on that output rather than on a grossed-up figure. {@link offboard} keeps
17971
+ * the other anchor, which needs `g - fee(g) = amount` solved — impossible for
17972
+ * a program charging the whole output.
17973
+ */
17974
+ async offboardExact(params) {
17975
+ const { destinationAddress, feeInfo, amount, eventCallback, vtxos } = params;
17976
+ if (amount <= 0n) {
17977
+ throw new Error(`offboard amount must be positive, got ${amount}`);
17827
17978
  }
17828
- return this.wallet.settle(
17829
- {
17830
- inputs: filteredVtxos,
17831
- outputs
17832
- },
17833
- eventCallback
17979
+ const named = vtxos !== void 0;
17980
+ if (vtxos) reportUngatedInputs(this.wallet, vtxos);
17981
+ const spendable = vtxos ?? await this.wallet.getSpendableVtxos({
17982
+ withRecoverable: true,
17983
+ withUnrolled: false
17984
+ });
17985
+ const estimator = new Estimator(feeInfo?.intentFee ?? {});
17986
+ const { inputs, subtotal } = filterOffboardInputs(spendable, feeInfo, named);
17987
+ const outputFee = BigInt(
17988
+ estimator.evalOnchainOutput({
17989
+ amount,
17990
+ script: hex.encode(offboardDestinationScript(destinationAddress))
17991
+ }).satoshis
17992
+ );
17993
+ const needed = amount + outputFee;
17994
+ if (needed > subtotal) {
17995
+ throw new Error(
17996
+ `selected vtxos net ${subtotal} sats, ${needed} needed to deliver ${amount} exactly (${amount} + a ${outputFee} sat exit fee)`
17997
+ );
17998
+ }
17999
+ const { change, changeAddress } = await this.fundChange(subtotal - needed, estimator);
18000
+ const outputs = [{ address: destinationAddress, amount }];
18001
+ if (change > 0n) outputs.push({ address: changeAddress, amount: change });
18002
+ return this.wallet.settle({ inputs, outputs }, eventCallback);
18003
+ }
18004
+ /** Fund the change output: pay its own fee, then judge it against both
18005
+ * server-side bounds. `0n` in is `0n` out — no change output at all. */
18006
+ async fundChange(left, estimator) {
18007
+ if (left <= 0n) return { change: 0n };
18008
+ const changeAddress = await this.wallet.getAddress();
18009
+ const changeScript = hex.encode(ArkAddress.decode(changeAddress).pkScript);
18010
+ const { change } = changeAfterOutputFee(
18011
+ left,
18012
+ (amount) => BigInt(estimator.evalOffchainOutput({ amount, script: changeScript }).satoshis)
17834
18013
  );
18014
+ assertChangeSettleable(
18015
+ change,
18016
+ getDustAmount(this.wallet),
18017
+ await serverVtxoMaxAmount(this.wallet)
18018
+ );
18019
+ return { change, changeAddress };
17835
18020
  }
17836
18021
  };
17837
18022
 
@@ -18685,7 +18870,8 @@ var WalletMessageHandler = class {
18685
18870
  now: { timestamp: /* @__PURE__ */ new Date() },
18686
18871
  isPendingRecovery: (vtxo) => pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`),
18687
18872
  isGenericallySpendable: (vtxo) => !isGatedVtxo(vtxo, gated),
18688
- isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`)
18873
+ isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`),
18874
+ dustCarrier: getDustAmount(this.readonlyWallet)
18689
18875
  });
18690
18876
  return {
18691
18877
  boarding: {
@@ -22539,7 +22725,7 @@ function sweepFeeFor(path, outputAddress, network, feeRate) {
22539
22725
  return Number(estimator.vsize().fee(BigInt(Math.ceil(feeRate))));
22540
22726
  }
22541
22727
  async function buildSignedSweep(params) {
22542
- const { vtxo, path, outputAddress, feeRate, network, identity } = params;
22728
+ const { vtxo, path, outputAddress, feeRate, network, identity, signer } = params;
22543
22729
  const fee = sweepFeeFor(path, outputAddress, network, feeRate);
22544
22730
  const sendAmount = BigInt(vtxo.value) - BigInt(fee);
22545
22731
  if (sendAmount < BigInt(DUST_AMOUNT)) {
@@ -22557,7 +22743,7 @@ async function buildSignedSweep(params) {
22557
22743
  sighashType: SigHash.DEFAULT
22558
22744
  });
22559
22745
  tx.addOutputAddress(outputAddress, sendAmount, network);
22560
- const signed = await identity.sign(tx);
22746
+ const signed = signer ? await signer(tx) : await identity.sign(tx);
22561
22747
  if (!path.extraWitness || path.extraWitness.length === 0) {
22562
22748
  signed.finalize();
22563
22749
  return { tx: signed, fee };
@@ -23109,7 +23295,8 @@ async function prepare(opts) {
23109
23295
  outputAddress: opts.sweepAddress,
23110
23296
  feeRate,
23111
23297
  network: wallet.network,
23112
- identity: wallet.identity
23298
+ identity: wallet.identity,
23299
+ signer: (tx2) => wallet.signInputsByWitnessScript(tx2)
23113
23300
  });
23114
23301
  activeOutpoints.add(outpoint);
23115
23302
  sweepSteps.push({
@@ -23600,7 +23787,7 @@ var Arkade = class _Arkade {
23600
23787
  const info = await opts.arkade.getInfo();
23601
23788
  const serverKey = toXOnly(hex.decode(info.signerPubkey), "ark signer key");
23602
23789
  const checkpoint = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
23603
- const network = opts.network ?? DEFAULT_NETWORK;
23790
+ const network = opts.network ?? (Object.hasOwn(networks, info.network) ? getNetwork(info.network) : DEFAULT_NETWORK);
23604
23791
  let emulatorKey;
23605
23792
  if (opts.emulator) {
23606
23793
  emulatorKey = hex.decode(resolveEmulatorPubkey(network, opts.emulatorPubkey));
@@ -24048,6 +24235,7 @@ var BIP21Error = /* @__PURE__ */ ((BIP21Error2) => {
24048
24235
  BIP21Error2["INVALID_ADDRESS"] = "Invalid address";
24049
24236
  return BIP21Error2;
24050
24237
  })(BIP21Error || {});
24238
+ var formatAmount = (btc) => btc.toFixed(8).replace(/\.?0+$/, "");
24051
24239
  var BIP21 = class _BIP21 {
24052
24240
  /**
24053
24241
  * Create a BIP21 URI from the provided parameters.
@@ -24069,7 +24257,7 @@ var BIP21 = class _BIP21 {
24069
24257
  if (amount < 0) {
24070
24258
  continue;
24071
24259
  }
24072
- queryParams[key] = value;
24260
+ queryParams[key] = formatAmount(amount);
24073
24261
  } else if (key === "ark") {
24074
24262
  if (typeof value === "string" && (value.startsWith("ark") || value.startsWith("tark"))) {
24075
24263
  queryParams[key] = value;
@@ -24422,18 +24610,6 @@ function arkAssetRail() {
24422
24610
  };
24423
24611
  }
24424
24612
  var walletFeeSource = (wallet) => async () => (await wallet.arkProvider.getInfo()).fees;
24425
- var GROSS_UP_MAX_ROUNDS = 8;
24426
- function grossUpOffboard(net, feeInfo, script) {
24427
- const estimator = new Estimator(feeInfo?.intentFee ?? {});
24428
- const feeAt = (amount) => estimator.evalOnchainOutput({ amount: BigInt(amount), script }).satoshis;
24429
- let gross = net;
24430
- for (let i = 0; i < GROSS_UP_MAX_ROUNDS; i++) {
24431
- const next = net + feeAt(gross);
24432
- if (next === gross) break;
24433
- gross = next;
24434
- }
24435
- return { gross, fee: gross - net };
24436
- }
24437
24613
  function offchainInputFees(vtxos, fees) {
24438
24614
  const estimator = new Estimator(fees?.intentFee ?? {});
24439
24615
  return vtxos.reduce(
@@ -24441,6 +24617,18 @@ function offchainInputFees(vtxos, fees) {
24441
24617
  0
24442
24618
  );
24443
24619
  }
24620
+ async function changeOutputFee(wallet, inputs, needed, fees) {
24621
+ const estimator = new Estimator(fees?.intentFee ?? {});
24622
+ const net = inputs.reduce(
24623
+ (total, vtxo) => total + BigInt(vtxo.value) - BigInt(estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo)).satoshis),
24624
+ 0n
24625
+ );
24626
+ const script = hex.encode(ArkAddress.decode(await wallet.getAddress()).pkScript);
24627
+ return changeAfterOutputFee(
24628
+ net - needed,
24629
+ (amount) => BigInt(estimator.evalOffchainOutput({ amount, script }).satoshis)
24630
+ ).fee;
24631
+ }
24444
24632
  function onchainRail(deps) {
24445
24633
  if (typeof deps?.feeInfo !== "function") {
24446
24634
  throw new Error("onchain rail: a feeInfo source is required");
@@ -24456,23 +24644,34 @@ function onchainRail(deps) {
24456
24644
  const amt = resolveSendAmount("onchain", req.raw, req.amount);
24457
24645
  const fees = await deps.feeInfo();
24458
24646
  const script = hex.encode(offboardDestinationScript(address));
24459
- const { gross, fee } = grossUpOffboard(amt, fees, script);
24647
+ const exitFee = Number(
24648
+ new Estimator(fees?.intentFee ?? {}).evalOnchainOutput({
24649
+ amount: BigInt(amt),
24650
+ script
24651
+ }).satoshis
24652
+ );
24460
24653
  const selectedVtxos = selectionOf(req);
24461
24654
  const inputFee = selectedVtxos ? offchainInputFees(selectedVtxos, fees) : 0;
24655
+ const changeFee = selectedVtxos ? await changeOutputFee(
24656
+ ctx.wallet,
24657
+ selectedVtxos,
24658
+ BigInt(amt) + BigInt(exitFee),
24659
+ fees
24660
+ ) : 0n;
24462
24661
  return {
24463
24662
  railId: "onchain",
24464
24663
  amount: amt,
24465
- fee: fee + inputFee,
24466
- total: gross + inputFee,
24664
+ fee: exitFee + inputFee + Number(changeFee),
24665
+ total: amt + exitFee + inputFee + Number(changeFee),
24467
24666
  send: async () => makeHandle("onchain", async (emit) => {
24468
24667
  const ramps = new Ramps(ctx.wallet);
24469
- const txid = selectedVtxos ? await ramps.offboard(
24470
- address,
24471
- fees,
24472
- BigInt(gross),
24473
- void 0,
24474
- selectedVtxos
24475
- ) : await ramps.offboard(address, fees, BigInt(gross));
24668
+ const txid = await ramps.offboardExact({
24669
+ destinationAddress: address,
24670
+ feeInfo: fees,
24671
+ amount: BigInt(amt),
24672
+ // Omitted, not undefined: an empty set covers nothing.
24673
+ ...selectedVtxos ? { vtxos: selectedVtxos } : {}
24674
+ });
24476
24675
  const result = { railId: "onchain", txid };
24477
24676
  emit({ status: "settled", result });
24478
24677
  return result;
@@ -24495,6 +24694,6 @@ function createDefaultPaymentRouter(wallet) {
24495
24694
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
24496
24695
  */
24497
24696
 
24498
- export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertNoAssets, assertNoSelection, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, registerWalletRestoreHook, requiresMigration, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, selectionOf, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
24499
- //# sourceMappingURL=chunk-ZTGTQP2W.js.map
24500
- //# sourceMappingURL=chunk-ZTGTQP2W.js.map
24697
+ export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, OversizedChangeError, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertNoAssets, assertNoSelection, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, registerWalletRestoreHook, requiresMigration, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, selectionOf, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
24698
+ //# sourceMappingURL=chunk-WMHELOHH.js.map
24699
+ //# sourceMappingURL=chunk-WMHELOHH.js.map