@arkade-os/sdk 0.4.73 → 0.4.75

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 (79) 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-DA2OdpJ0.d.cts → asyncStorageTaskQueue-5k0H_cDM.d.cts} +2 -2
  8. package/dist/{asyncStorageTaskQueue-C5IwA_Hs.d.ts → asyncStorageTaskQueue-DjwCbb07.d.ts} +2 -2
  9. package/dist/{chunk-L7IEXVXI.cjs → chunk-2Q2PYU3Y.cjs} +10 -10
  10. package/dist/{chunk-L7IEXVXI.cjs.map → chunk-2Q2PYU3Y.cjs.map} +1 -1
  11. package/dist/{chunk-QX2M67YI.js → chunk-2ULUCL6A.js} +21 -22
  12. package/dist/chunk-2ULUCL6A.js.map +1 -0
  13. package/dist/{chunk-G5RPUZJE.js → chunk-4GLSOP6L.js} +12 -5
  14. package/dist/chunk-4GLSOP6L.js.map +1 -0
  15. package/dist/{chunk-C4RKZY7D.cjs → chunk-EHJYR4E5.cjs} +827 -580
  16. package/dist/chunk-EHJYR4E5.cjs.map +1 -0
  17. package/dist/{chunk-YHJJSLE5.js → chunk-FTLPTF3R.js} +391 -147
  18. package/dist/chunk-FTLPTF3R.js.map +1 -0
  19. package/dist/{chunk-KJ5E646Y.cjs → chunk-J5EX3OMM.cjs} +8 -4
  20. package/dist/chunk-J5EX3OMM.cjs.map +1 -0
  21. package/dist/{chunk-M6HIWM5N.js → chunk-PS564TAU.js} +3 -3
  22. package/dist/{chunk-M6HIWM5N.js.map → chunk-PS564TAU.js.map} +1 -1
  23. package/dist/{chunk-D5DLGVN4.cjs → chunk-SH23SRZE.cjs} +17 -17
  24. package/dist/{chunk-D5DLGVN4.cjs.map → chunk-SH23SRZE.cjs.map} +1 -1
  25. package/dist/{chunk-5YCAC6XE.js → chunk-SKAL4MCW.js} +4 -4
  26. package/dist/{chunk-5YCAC6XE.js.map → chunk-SKAL4MCW.js.map} +1 -1
  27. package/dist/{chunk-OV4GLPZI.js → chunk-UKFA7NVE.js} +8 -4
  28. package/dist/chunk-UKFA7NVE.js.map +1 -0
  29. package/dist/{chunk-ZLDO4QDR.cjs → chunk-YA3L3WL4.cjs} +31 -32
  30. package/dist/chunk-YA3L3WL4.cjs.map +1 -0
  31. package/dist/{chunk-2D7F5IU3.cjs → chunk-YYWEBYFJ.cjs} +58 -51
  32. package/dist/chunk-YYWEBYFJ.cjs.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-BJp8f6LT.d.cts → delegate-D0v8yT-a.d.cts} +1 -1
  38. package/dist/{delegate-Ckz_TWzM.d.ts → delegate-D_sW61rj.d.ts} +1 -1
  39. package/dist/{hdWalletCapable-DABgAD-T.d.ts → hdWalletCapable-66hG5KQP.d.cts} +3 -11
  40. package/dist/{hdWalletCapable-DA9bJ5eB.d.cts → hdWalletCapable-DHCNbAgl.d.ts} +3 -11
  41. package/dist/{index-DSbDm0Io.d.cts → index-B2JSxDi1.d.cts} +2 -2
  42. package/dist/{index-Dg0-TvBQ.d.cts → index-TZBbMNqA.d.cts} +22 -7
  43. package/dist/{index-Dg0-TvBQ.d.ts → index-TZBbMNqA.d.ts} +22 -7
  44. package/dist/{index-B8xrELXY.d.ts → index-vUeTpifq.d.ts} +2 -2
  45. package/dist/index.cjs +312 -300
  46. package/dist/index.d.cts +89 -29
  47. package/dist/index.d.ts +89 -29
  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-C_uAJDsn.d.cts → taskRunner-CAAPpdyi.d.cts} +2 -2
  58. package/dist/{taskRunner-DARTFnZN.d.ts → taskRunner-CairkunF.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-2D7F5IU3.cjs.map +0 -1
  73. package/dist/chunk-C4RKZY7D.cjs.map +0 -1
  74. package/dist/chunk-G5RPUZJE.js.map +0 -1
  75. package/dist/chunk-KJ5E646Y.cjs.map +0 -1
  76. package/dist/chunk-OV4GLPZI.js.map +0 -1
  77. package/dist/chunk-QX2M67YI.js.map +0 -1
  78. package/dist/chunk-YHJJSLE5.js.map +0 -1
  79. package/dist/chunk-ZLDO4QDR.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-M6HIWM5N.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-G5RPUZJE.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-OV4GLPZI.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-PS564TAU.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-4GLSOP6L.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-UKFA7NVE.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 });
@@ -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
@@ -9010,23 +9069,32 @@ var ContractWatcher = class {
9010
9069
  }
9011
9070
  /** @see IContractManager.watchScript for the delivery and filter contract. */
9012
9071
  async addWatchedScript(script, options) {
9013
- const existing = this.watchedScripts.get(script);
9014
- if (existing) {
9015
- if (options?.label !== void 0) existing.label = options.label;
9016
- return;
9072
+ const added = [];
9073
+ for (const one of Array.isArray(script) ? script : [script]) {
9074
+ const existing = this.watchedScripts.get(one);
9075
+ if (existing) {
9076
+ if (options?.label !== void 0) existing.label = options.label;
9077
+ continue;
9078
+ }
9079
+ this.watchedScripts.set(one, {
9080
+ label: options?.label,
9081
+ lastKnownVtxos: /* @__PURE__ */ new Map()
9082
+ });
9083
+ added.push(one);
9017
9084
  }
9018
- this.watchedScripts.set(script, {
9019
- label: options?.label,
9020
- lastKnownVtxos: /* @__PURE__ */ new Map()
9021
- });
9022
- if (this.isWatching) {
9023
- await this.pollWatchedScripts([script]);
9085
+ if (added.length === 0 || !this.isWatching) return;
9086
+ await this.withCoalescedSubscription(async () => {
9087
+ await this.pollWatchedScripts(added);
9024
9088
  await this.tryUpdateSubscription();
9025
- }
9089
+ });
9026
9090
  }
9027
9091
  /** Stop watching a script added by {@link addWatchedScript}. */
9028
9092
  async removeWatchedScript(script) {
9029
- if (!this.watchedScripts.delete(script)) return;
9093
+ let removed = false;
9094
+ for (const one of Array.isArray(script) ? script : [script]) {
9095
+ if (this.watchedScripts.delete(one)) removed = true;
9096
+ }
9097
+ if (!removed) return;
9030
9098
  if (this.isWatching) {
9031
9099
  await this.tryUpdateSubscription();
9032
9100
  }
@@ -9779,7 +9847,21 @@ var ContractManager = class _ContractManager {
9779
9847
  }
9780
9848
  markSyncOnline() {
9781
9849
  this.lastSyncedAt = Date.now();
9782
- this.syncDegradedReason = this.annotationDegradedReason();
9850
+ this.syncDegradedReason = this.syncGapReason();
9851
+ }
9852
+ /**
9853
+ * Why a sync that just succeeded still has not covered everything it should
9854
+ * have. Both reasons outlive the sync that failed to cover them, so neither
9855
+ * is discharged by the next one succeeding — which is the point: a wallet
9856
+ * that reported itself current while the look-ahead band sat behind the
9857
+ * watermark, or while a contract could not be annotated, would be claiming
9858
+ * coverage it never had. `getSyncState()` is the channel apps already watch.
9859
+ */
9860
+ syncGapReason() {
9861
+ const annotations = this.annotationDegradedReason();
9862
+ 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;
9863
+ const reasons = [annotations, owedRefill].filter((r) => r !== void 0);
9864
+ return reasons.length === 0 ? void 0 : reasons.join("; ");
9783
9865
  }
9784
9866
  /** Contracts a sync could not annotate, as `script → reason`. */
9785
9867
  annotationFailures = /* @__PURE__ */ new Map();
@@ -9813,7 +9895,13 @@ var ContractManager = class _ContractManager {
9813
9895
  for (const contract of contracts) {
9814
9896
  await this.watcher.addContract(contract);
9815
9897
  }
9816
- await this.scheduleLookAheadDrain();
9898
+ try {
9899
+ await this.scheduleLookAheadDrain();
9900
+ } catch (err) {
9901
+ if (!isRetryableProviderError(err)) throw err;
9902
+ this.lookAheadRefillOwed = true;
9903
+ this.markSyncDegraded(err);
9904
+ }
9817
9905
  try {
9818
9906
  await this.reconcileWatched();
9819
9907
  this.markSyncOnline();
@@ -10737,7 +10825,13 @@ var ContractManager = class _ContractManager {
10737
10825
  if (contract) {
10738
10826
  await saveVtxosForContract(this.config.walletRepository, contract, addressVtxos);
10739
10827
  } else {
10740
- await this.config.walletRepository.saveVtxos(address, addressVtxos);
10828
+ await inVtxoWriteOrder(
10829
+ this.config.walletRepository,
10830
+ async () => this.config.walletRepository.saveVtxos(
10831
+ address,
10832
+ applyRecordedSpends(this.config.walletRepository, addressVtxos)
10833
+ )
10834
+ );
10741
10835
  }
10742
10836
  }
10743
10837
  }
@@ -11965,7 +12059,7 @@ var HDDescriptorProvider = class _HDDescriptorProvider {
11965
12059
  */
11966
12060
  materializeDescriptorAt(index) {
11967
12061
  const descriptor = this.identity.descriptor;
11968
- const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
12062
+ const network = isMainnetDescriptor(descriptor) ? networks$1.bitcoin : networks$1.testnet;
11969
12063
  const expansion = expand({ descriptor, network, index });
11970
12064
  const keyInfo = expansion.expansionMap?.["@0"];
11971
12065
  if (!keyInfo?.keyExpression) {
@@ -12516,8 +12610,9 @@ var InputSignerRouter = class {
12516
12610
  async classify(jobs) {
12517
12611
  const identityIndexes = [];
12518
12612
  const descriptorGroups = /* @__PURE__ */ new Map();
12613
+ const unknownIndexes = [];
12519
12614
  if (jobs.length === 0) {
12520
- return { identityIndexes, descriptorGroups };
12615
+ return { identityIndexes, descriptorGroups, unknownIndexes };
12521
12616
  }
12522
12617
  const distinctScripts = Array.from(new Set(jobs.map((j) => hex.encode(j.lookupScript))));
12523
12618
  const contracts = await this.deps.contractRepository.getContracts({
@@ -12537,6 +12632,8 @@ var InputSignerRouter = class {
12537
12632
  if (!contract) {
12538
12633
  if (scriptHex === boardingScriptHex) {
12539
12634
  identityIndexes.push(job.index);
12635
+ } else {
12636
+ unknownIndexes.push(job.index);
12540
12637
  }
12541
12638
  continue;
12542
12639
  }
@@ -12563,7 +12660,7 @@ var InputSignerRouter = class {
12563
12660
  descriptorGroups.set(descriptor, [job.index]);
12564
12661
  }
12565
12662
  }
12566
- return { identityIndexes, descriptorGroups };
12663
+ return { identityIndexes, descriptorGroups, unknownIndexes };
12567
12664
  }
12568
12665
  /**
12569
12666
  * Returns `true` when every signable input across all `jobSets` resolves
@@ -12585,12 +12682,13 @@ var InputSignerRouter = class {
12585
12682
  const plan = await this.classify(jobSets.flat());
12586
12683
  return plan.descriptorGroups.size === 0;
12587
12684
  }
12588
- async sign(tx, jobs) {
12685
+ async sign(tx, jobs, opts) {
12589
12686
  if (jobs.length === 0) return tx;
12590
- const { identityIndexes, descriptorGroups } = await this.classify(jobs);
12687
+ const { identityIndexes, descriptorGroups, unknownIndexes } = await this.classify(jobs);
12688
+ const identitySignIndexes = opts?.onUnknownScript === "sign" ? [...identityIndexes, ...unknownIndexes] : identityIndexes;
12591
12689
  let signed = tx;
12592
- if (identityIndexes.length > 0) {
12593
- signed = await this.deps.identity.sign(signed, identityIndexes);
12690
+ if (identitySignIndexes.length > 0) {
12691
+ signed = await this.deps.identity.sign(signed, identitySignIndexes);
12594
12692
  }
12595
12693
  if (descriptorGroups.size > 0) {
12596
12694
  if (!this.deps.descriptorProvider) {
@@ -13617,23 +13715,26 @@ var ReadonlyWallet = class _ReadonlyWallet {
13617
13715
  */
13618
13716
  async getBoardingUtxosForSigners(allowedSigners) {
13619
13717
  const tapscripts = await this.getBoardingTapscripts(allowedSigners);
13620
- const groups = [];
13621
- for (const tapscript of tapscripts) {
13622
- const address = tapscript.onchainAddress(this.network);
13623
- const coins = await this.onchainProvider.getCoins(address);
13624
- const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
13625
- await this.walletRepository.saveUtxos(address, utxos);
13626
- groups.push({
13627
- tapscript,
13628
- // Normalize so the group key matches the axis/contract x-only
13629
- // form regardless of how the tapscript's key was stored.
13630
- serverPubKey: toXOnlySignerHex(hex.encode(tapscript.options.serverPubKey)),
13631
- // Per-row CSV delay decoded from THIS tapscript's exit leaf —
13632
- // not the wallet's current boarding timelock, which a signer
13633
- // rotation may have changed.
13634
- csvTimelock: CSVMultisigTapscript.decode(hex.decode(tapscript.exitScript)).params.timelock,
13635
- coins: utxos
13636
- });
13718
+ const addresses = tapscripts.map((tapscript) => tapscript.onchainAddress(this.network));
13719
+ const groups = await Promise.all(
13720
+ tapscripts.map(async (tapscript, i) => {
13721
+ const coins = await this.onchainProvider.getCoins(addresses[i]);
13722
+ const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
13723
+ return {
13724
+ tapscript,
13725
+ // Normalize so the group key matches the axis/contract x-only
13726
+ // form regardless of how the tapscript's key was stored.
13727
+ serverPubKey: toXOnlySignerHex(hex.encode(tapscript.options.serverPubKey)),
13728
+ // Per-row CSV delay decoded from THIS tapscript's exit leaf —
13729
+ // not the wallet's current boarding timelock, which a signer
13730
+ // rotation may have changed.
13731
+ csvTimelock: CSVMultisigTapscript.decode(hex.decode(tapscript.exitScript)).params.timelock,
13732
+ coins: utxos
13733
+ };
13734
+ })
13735
+ );
13736
+ for (const [i, group] of groups.entries()) {
13737
+ await this.walletRepository.saveUtxos(addresses[i], group.coins);
13637
13738
  }
13638
13739
  return groups;
13639
13740
  }
@@ -15729,15 +15830,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15729
15830
  console.error(`Failed to persist intent ${intentTxId} (state=${state})`, e);
15730
15831
  }
15731
15832
  }
15732
- /**
15733
- * @internal Sign an on-chain boarding exit / sweep transaction, routing
15734
- * each input to the correct key by its `witnessUtxo.script`: the identity
15735
- * for index-0 / static boarding, the per-index descriptor for a rotated
15736
- * boarding UTXO (plan §6-III.3). Used by
15737
- * {@link VtxoManager.sweepExpiredBoardingUtxos}; without it, the
15738
- * unilateral exit of a rotated boarding UTXO would be signed with the
15739
- * wrong (index-0) key and rejected.
15740
- */
15833
+ async signInputsByWitnessScript(tx) {
15834
+ const signed = await this._signerRouter.sign(
15835
+ tx,
15836
+ this.inputSigningJobsFromWitnessUtxos(tx),
15837
+ {
15838
+ onUnknownScript: "sign"
15839
+ }
15840
+ );
15841
+ return signed;
15842
+ }
15741
15843
  async signOnchainBoardingTx(tx) {
15742
15844
  const signed = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
15743
15845
  return signed;
@@ -17587,6 +17689,18 @@ var DustChangeError = class extends Error {
17587
17689
  this.dustAmount = dustAmount;
17588
17690
  }
17589
17691
  };
17692
+ var OversizedChangeError = class extends Error {
17693
+ change;
17694
+ maxAmount;
17695
+ constructor(change, maxAmount) {
17696
+ super(
17697
+ `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`
17698
+ );
17699
+ this.name = "OversizedChangeError";
17700
+ this.change = change;
17701
+ this.maxAmount = maxAmount;
17702
+ }
17703
+ };
17590
17704
  var offboardNetworkNames = [
17591
17705
  "bitcoin",
17592
17706
  "regtest",
@@ -17597,7 +17711,7 @@ var offboardNetworkNames = [
17597
17711
  function offboardDestinationScript(destinationAddress) {
17598
17712
  for (const networkName of offboardNetworkNames) {
17599
17713
  try {
17600
- const network = networks$1[networkName];
17714
+ const network = networks[networkName];
17601
17715
  const addr = Address(network).decode(destinationAddress);
17602
17716
  return OutScript.encode(addr);
17603
17717
  } catch {
@@ -17606,6 +17720,62 @@ function offboardDestinationScript(destinationAddress) {
17606
17720
  }
17607
17721
  throw new Error(`Failed to decode destination address: ${destinationAddress}`);
17608
17722
  }
17723
+ var CHANGE_FEE_MAX_ROUNDS = 8;
17724
+ function settleChangeAgainstFee(change, feeOn) {
17725
+ let net = change;
17726
+ for (let i = 0; i < CHANGE_FEE_MAX_ROUNDS; i++) {
17727
+ const next = change - feeOn(net > 0n ? net : 0n);
17728
+ if (next === net) return net > 0n ? net : 0n;
17729
+ net = next;
17730
+ }
17731
+ throw new Error(
17732
+ `change fee does not settle after ${CHANGE_FEE_MAX_ROUNDS} rounds (${change} sats of change); settle the full balance instead`
17733
+ );
17734
+ }
17735
+ function changeAfterOutputFee(left, feeOn) {
17736
+ if (left <= 0n) return { change: 0n, fee: 0n };
17737
+ const change = settleChangeAgainstFee(left, feeOn);
17738
+ return { change, fee: left - change };
17739
+ }
17740
+ function reportUngatedInputs(wallet, inputs) {
17741
+ if (!("logUngatedInputs" in wallet)) return;
17742
+ const logger = wallet;
17743
+ void logger.logUngatedInputs("Ramps.offboard({ vtxos })", inputs);
17744
+ }
17745
+ function filterOffboardInputs(vtxos, feeInfo, named) {
17746
+ const estimator = new Estimator(feeInfo?.intentFee ?? {});
17747
+ const inputs = [];
17748
+ let subtotal = 0n;
17749
+ for (const vtxo of vtxos) {
17750
+ const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
17751
+ if (inputFee.satoshis >= vtxo.value) {
17752
+ if (named) {
17753
+ throw new Error(
17754
+ `selected vtxo ${vtxo.txid}:${vtxo.vout} costs ${inputFee.satoshis} sats to spend and is worth ${vtxo.value} \u2014 drop it from the selection`
17755
+ );
17756
+ }
17757
+ continue;
17758
+ }
17759
+ inputs.push(vtxo);
17760
+ subtotal += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
17761
+ }
17762
+ if (inputs.length === 0) {
17763
+ throw new Error("No vtxos available after deducting fees");
17764
+ }
17765
+ return { inputs, subtotal };
17766
+ }
17767
+ async function serverVtxoMaxAmount(wallet) {
17768
+ const provider = wallet.arkProvider;
17769
+ if (!provider) return void 0;
17770
+ return (await provider.getInfo()).vtxoMaxAmount;
17771
+ }
17772
+ function assertChangeSettleable(change, dustAmount, maxAmount) {
17773
+ if (change === 0n) return;
17774
+ if (change < dustAmount) throw new DustChangeError(change, dustAmount);
17775
+ if (maxAmount !== void 0 && maxAmount >= 0n && change > maxAmount) {
17776
+ throw new OversizedChangeError(change, maxAmount);
17777
+ }
17778
+ }
17609
17779
  var Ramps = class {
17610
17780
  /**
17611
17781
  * Create convenience wrappers for onboarding and offboarding flows.
@@ -17659,6 +17829,19 @@ var Ramps = class {
17659
17829
  }
17660
17830
  change = totalAmount - amount;
17661
17831
  }
17832
+ let boardingAddress;
17833
+ if (change > 0n) {
17834
+ boardingAddress = await this.wallet.getBoardingAddress();
17835
+ const changeScript = hex.encode(offboardDestinationScript(boardingAddress));
17836
+ change = settleChangeAgainstFee(
17837
+ change,
17838
+ (amt) => BigInt(estimator.evalOnchainOutput({ amount: amt, script: changeScript }).satoshis)
17839
+ );
17840
+ const dustAmount = getDustAmount(this.wallet);
17841
+ if (change > 0n && change < dustAmount) {
17842
+ throw new DustChangeError(change, dustAmount);
17843
+ }
17844
+ }
17662
17845
  amount = amount ?? totalAmount;
17663
17846
  const offchainAddress = await this.wallet.getAddress();
17664
17847
  const offchainAddr = ArkAddress.decode(offchainAddress);
@@ -17680,7 +17863,6 @@ var Ramps = class {
17680
17863
  }
17681
17864
  ];
17682
17865
  if (change > 0n) {
17683
- const boardingAddress = await this.wallet.getBoardingAddress();
17684
17866
  outputs.push({
17685
17867
  address: boardingAddress,
17686
17868
  amount: change
@@ -17701,6 +17883,10 @@ var Ramps = class {
17701
17883
  * @param feeInfo - The fee info to deduct from the offboard amount.
17702
17884
  * @param amount - The amount to offboard. If not provided, the total amount of virtual outputs will be offboarded.
17703
17885
  * @param eventCallback - Optional callback that receives settlement events
17886
+ * @param vtxos - Specific virtual outputs to spend, mirroring {@link onboard}'s
17887
+ * `boardingUtxos`. Omitted, every spendable one is spent — merging the whole
17888
+ * off-chain balance into a single change output. Taken as given like
17889
+ * `settle({ inputs })`: an `amount` these cannot cover is an error, not a top-up.
17704
17890
  * @returns The Arkade transaction id created by settlement
17705
17891
  * @throws Error if no virtual outputs remain after fee deduction or the destination address cannot be decoded
17706
17892
  * @see IWallet.getSpendableVtxos
@@ -17712,68 +17898,90 @@ var Ramps = class {
17712
17898
  * await ramps.offboard('bc1q...', feeInfo);
17713
17899
  * ```
17714
17900
  */
17715
- async offboard(destinationAddress, feeInfo, amount, eventCallback) {
17716
- const vtxos = await this.wallet.getSpendableVtxos({
17901
+ async offboard(destinationAddress, feeInfo, amount, eventCallback, vtxos) {
17902
+ const named = vtxos !== void 0;
17903
+ if (vtxos) reportUngatedInputs(this.wallet, vtxos);
17904
+ const spendable = vtxos ?? await this.wallet.getSpendableVtxos({
17717
17905
  withRecoverable: true,
17718
17906
  withUnrolled: false
17719
17907
  });
17720
17908
  const estimator = new Estimator(feeInfo?.intentFee ?? {});
17721
- const filteredVtxos = [];
17722
- let totalAmount = 0n;
17723
- for (const vtxo of vtxos) {
17724
- const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
17725
- if (inputFee.satoshis >= vtxo.value) {
17726
- continue;
17727
- }
17728
- filteredVtxos.push(vtxo);
17729
- totalAmount += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
17730
- }
17731
- if (filteredVtxos.length === 0) {
17732
- throw new Error("No vtxos available after deducting fees");
17733
- }
17734
- let change = 0n;
17735
- if (amount) {
17736
- if (amount > totalAmount) {
17737
- throw new Error("Amount is greater than total amount of vtxos after fees");
17738
- }
17739
- change = totalAmount - amount;
17740
- }
17741
- const dustAmount = getDustAmount(this.wallet);
17742
- if (change > 0n && change < dustAmount) {
17743
- throw new DustChangeError(change, dustAmount);
17744
- }
17745
- amount = amount ?? totalAmount;
17746
- const destinationScript = offboardDestinationScript(destinationAddress);
17909
+ const { inputs, subtotal } = filterOffboardInputs(spendable, feeInfo, named);
17910
+ if (amount && amount > subtotal) {
17911
+ throw new Error("Amount is greater than total amount of vtxos after fees");
17912
+ }
17913
+ const handed = amount || subtotal;
17914
+ const { change, changeAddress } = await this.fundChange(
17915
+ amount ? subtotal - handed : 0n,
17916
+ estimator
17917
+ );
17747
17918
  const outputFee = estimator.evalOnchainOutput({
17748
- amount,
17749
- script: hex.encode(destinationScript)
17919
+ amount: handed,
17920
+ script: hex.encode(offboardDestinationScript(destinationAddress))
17750
17921
  });
17751
- if (BigInt(outputFee.satoshis) > amount) {
17922
+ if (BigInt(outputFee.satoshis) > handed) {
17752
17923
  throw new Error(
17753
- `can't deduct fees from offboard amount (${outputFee.satoshis} > ${amount})`
17924
+ `can't deduct fees from offboard amount (${outputFee.satoshis} > ${handed})`
17754
17925
  );
17755
17926
  }
17756
- amount -= BigInt(outputFee.satoshis);
17757
17927
  const outputs = [
17758
- {
17759
- address: destinationAddress,
17760
- amount
17761
- }
17928
+ { address: destinationAddress, amount: handed - BigInt(outputFee.satoshis) }
17762
17929
  ];
17763
- if (change > 0n) {
17764
- const offchainAddress = await this.wallet.getAddress();
17765
- outputs.push({
17766
- address: offchainAddress,
17767
- amount: change
17768
- });
17930
+ if (change > 0n) outputs.push({ address: changeAddress, amount: change });
17931
+ return this.wallet.settle({ inputs, outputs }, eventCallback);
17932
+ }
17933
+ /**
17934
+ * Collaboratively exit with the DESTINATION paid exactly `amount`, pricing the
17935
+ * fee on that output rather than on a grossed-up figure. {@link offboard} keeps
17936
+ * the other anchor, which needs `g - fee(g) = amount` solved — impossible for
17937
+ * a program charging the whole output.
17938
+ */
17939
+ async offboardExact(params) {
17940
+ const { destinationAddress, feeInfo, amount, eventCallback, vtxos } = params;
17941
+ if (amount <= 0n) {
17942
+ throw new Error(`offboard amount must be positive, got ${amount}`);
17769
17943
  }
17770
- return this.wallet.settle(
17771
- {
17772
- inputs: filteredVtxos,
17773
- outputs
17774
- },
17775
- eventCallback
17944
+ const named = vtxos !== void 0;
17945
+ if (vtxos) reportUngatedInputs(this.wallet, vtxos);
17946
+ const spendable = vtxos ?? await this.wallet.getSpendableVtxos({
17947
+ withRecoverable: true,
17948
+ withUnrolled: false
17949
+ });
17950
+ const estimator = new Estimator(feeInfo?.intentFee ?? {});
17951
+ const { inputs, subtotal } = filterOffboardInputs(spendable, feeInfo, named);
17952
+ const outputFee = BigInt(
17953
+ estimator.evalOnchainOutput({
17954
+ amount,
17955
+ script: hex.encode(offboardDestinationScript(destinationAddress))
17956
+ }).satoshis
17957
+ );
17958
+ const needed = amount + outputFee;
17959
+ if (needed > subtotal) {
17960
+ throw new Error(
17961
+ `selected vtxos net ${subtotal} sats, ${needed} needed to deliver ${amount} exactly (${amount} + a ${outputFee} sat exit fee)`
17962
+ );
17963
+ }
17964
+ const { change, changeAddress } = await this.fundChange(subtotal - needed, estimator);
17965
+ const outputs = [{ address: destinationAddress, amount }];
17966
+ if (change > 0n) outputs.push({ address: changeAddress, amount: change });
17967
+ return this.wallet.settle({ inputs, outputs }, eventCallback);
17968
+ }
17969
+ /** Fund the change output: pay its own fee, then judge it against both
17970
+ * server-side bounds. `0n` in is `0n` out — no change output at all. */
17971
+ async fundChange(left, estimator) {
17972
+ if (left <= 0n) return { change: 0n };
17973
+ const changeAddress = await this.wallet.getAddress();
17974
+ const changeScript = hex.encode(ArkAddress.decode(changeAddress).pkScript);
17975
+ const { change } = changeAfterOutputFee(
17976
+ left,
17977
+ (amount) => BigInt(estimator.evalOffchainOutput({ amount, script: changeScript }).satoshis)
17978
+ );
17979
+ assertChangeSettleable(
17980
+ change,
17981
+ getDustAmount(this.wallet),
17982
+ await serverVtxoMaxAmount(this.wallet)
17776
17983
  );
17984
+ return { change, changeAddress };
17777
17985
  }
17778
17986
  };
17779
17987
 
@@ -22481,7 +22689,7 @@ function sweepFeeFor(path, outputAddress, network, feeRate) {
22481
22689
  return Number(estimator.vsize().fee(BigInt(Math.ceil(feeRate))));
22482
22690
  }
22483
22691
  async function buildSignedSweep(params) {
22484
- const { vtxo, path, outputAddress, feeRate, network, identity } = params;
22692
+ const { vtxo, path, outputAddress, feeRate, network, identity, signer } = params;
22485
22693
  const fee = sweepFeeFor(path, outputAddress, network, feeRate);
22486
22694
  const sendAmount = BigInt(vtxo.value) - BigInt(fee);
22487
22695
  if (sendAmount < BigInt(DUST_AMOUNT)) {
@@ -22499,7 +22707,7 @@ async function buildSignedSweep(params) {
22499
22707
  sighashType: SigHash.DEFAULT
22500
22708
  });
22501
22709
  tx.addOutputAddress(outputAddress, sendAmount, network);
22502
- const signed = await identity.sign(tx);
22710
+ const signed = signer ? await signer(tx) : await identity.sign(tx);
22503
22711
  if (!path.extraWitness || path.extraWitness.length === 0) {
22504
22712
  signed.finalize();
22505
22713
  return { tx: signed, fee };
@@ -23051,7 +23259,8 @@ async function prepare(opts) {
23051
23259
  outputAddress: opts.sweepAddress,
23052
23260
  feeRate,
23053
23261
  network: wallet.network,
23054
- identity: wallet.identity
23262
+ identity: wallet.identity,
23263
+ signer: (tx2) => wallet.signInputsByWitnessScript(tx2)
23055
23264
  });
23056
23265
  activeOutpoints.add(outpoint);
23057
23266
  sweepSteps.push({
@@ -23542,7 +23751,7 @@ var Arkade = class _Arkade {
23542
23751
  const info = await opts.arkade.getInfo();
23543
23752
  const serverKey = toXOnly(hex.decode(info.signerPubkey), "ark signer key");
23544
23753
  const checkpoint = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
23545
- const network = opts.network ?? DEFAULT_NETWORK;
23754
+ const network = opts.network ?? (Object.hasOwn(networks, info.network) ? getNetwork(info.network) : DEFAULT_NETWORK);
23546
23755
  let emulatorKey;
23547
23756
  if (opts.emulator) {
23548
23757
  emulatorKey = hex.decode(resolveEmulatorPubkey(network, opts.emulatorPubkey));
@@ -24159,6 +24368,16 @@ function assertNoAssets(railId, req) {
24159
24368
  );
24160
24369
  }
24161
24370
  }
24371
+ function selectionOf(req) {
24372
+ return req.selectedVtxos;
24373
+ }
24374
+ function assertNoSelection(railId, req) {
24375
+ if (selectionOf(req) !== void 0) {
24376
+ throw new Error(
24377
+ `${railId}: cannot spend a caller-chosen input set (selectedVtxos) \u2014 this rail does not choose the coins it spends`
24378
+ );
24379
+ }
24380
+ }
24162
24381
  function resolveAssetAmount(railId, req) {
24163
24382
  const assets = assetsOf(req);
24164
24383
  if (assets.length !== 1) {
@@ -24302,13 +24521,17 @@ function arkRail() {
24302
24521
  const address = arkTarget(req.raw);
24303
24522
  assertNoAssets("ark", req);
24304
24523
  const amt = resolveSendAmount("ark", req.raw, req.amount);
24524
+ const selectedVtxos = selectionOf(req);
24305
24525
  return {
24306
24526
  railId: "ark",
24307
24527
  amount: amt,
24308
24528
  fee: 0,
24309
24529
  total: amt,
24310
24530
  send: async () => makeHandle("ark", async (emit) => {
24311
- const txid = await ctx.wallet.send({ address, amount: amt });
24531
+ const txid = selectedVtxos ? await ctx.wallet.send({
24532
+ recipients: [{ address, amount: amt }],
24533
+ selectedVtxos
24534
+ }) : await ctx.wallet.send({ address, amount: amt });
24312
24535
  const result = { railId: "ark", txid };
24313
24536
  emit({ status: "settled", result });
24314
24537
  return result;
@@ -24331,6 +24554,7 @@ function arkAssetRail() {
24331
24554
  const asset = resolveAssetAmount("ark-asset", req);
24332
24555
  if (req.amount !== void 0) assertSendableAmount("ark-asset", req.amount);
24333
24556
  const carrier = tryResolveSendAmount(req.raw, req.amount) ?? ASSET_CARRIER_SATS;
24557
+ const selectedVtxos = selectionOf(req);
24334
24558
  return {
24335
24559
  railId: "ark-asset",
24336
24560
  amount: carrier,
@@ -24338,11 +24562,8 @@ function arkAssetRail() {
24338
24562
  total: carrier,
24339
24563
  assets: { delivered: asset, spent: asset },
24340
24564
  send: async () => makeHandle("ark-asset", async (emit) => {
24341
- const txid = await ctx.wallet.send({
24342
- address,
24343
- amount: carrier,
24344
- assets: [asset]
24345
- });
24565
+ const recipient = { address, amount: carrier, assets: [asset] };
24566
+ const txid = selectedVtxos ? await ctx.wallet.send({ recipients: [recipient], selectedVtxos }) : await ctx.wallet.send(recipient);
24346
24567
  const result = { railId: "ark-asset", txid };
24347
24568
  emit({ status: "settled", result });
24348
24569
  return result;
@@ -24352,17 +24573,24 @@ function arkAssetRail() {
24352
24573
  };
24353
24574
  }
24354
24575
  var walletFeeSource = (wallet) => async () => (await wallet.arkProvider.getInfo()).fees;
24355
- var GROSS_UP_MAX_ROUNDS = 8;
24356
- function grossUpOffboard(net, feeInfo, script) {
24357
- const estimator = new Estimator(feeInfo?.intentFee ?? {});
24358
- const feeAt = (amount) => estimator.evalOnchainOutput({ amount: BigInt(amount), script }).satoshis;
24359
- let gross = net;
24360
- for (let i = 0; i < GROSS_UP_MAX_ROUNDS; i++) {
24361
- const next = net + feeAt(gross);
24362
- if (next === gross) break;
24363
- gross = next;
24364
- }
24365
- return { gross, fee: gross - net };
24576
+ function offchainInputFees(vtxos, fees) {
24577
+ const estimator = new Estimator(fees?.intentFee ?? {});
24578
+ return vtxos.reduce(
24579
+ (total, vtxo) => total + estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo)).satoshis,
24580
+ 0
24581
+ );
24582
+ }
24583
+ async function changeOutputFee(wallet, inputs, needed, fees) {
24584
+ const estimator = new Estimator(fees?.intentFee ?? {});
24585
+ const net = inputs.reduce(
24586
+ (total, vtxo) => total + BigInt(vtxo.value) - BigInt(estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo)).satoshis),
24587
+ 0n
24588
+ );
24589
+ const script = hex.encode(ArkAddress.decode(await wallet.getAddress()).pkScript);
24590
+ return changeAfterOutputFee(
24591
+ net - needed,
24592
+ (amount) => BigInt(estimator.evalOffchainOutput({ amount, script }).satoshis)
24593
+ ).fee;
24366
24594
  }
24367
24595
  function onchainRail(deps) {
24368
24596
  if (typeof deps?.feeInfo !== "function") {
@@ -24379,18 +24607,34 @@ function onchainRail(deps) {
24379
24607
  const amt = resolveSendAmount("onchain", req.raw, req.amount);
24380
24608
  const fees = await deps.feeInfo();
24381
24609
  const script = hex.encode(offboardDestinationScript(address));
24382
- const { gross, fee } = grossUpOffboard(amt, fees, script);
24610
+ const exitFee = Number(
24611
+ new Estimator(fees?.intentFee ?? {}).evalOnchainOutput({
24612
+ amount: BigInt(amt),
24613
+ script
24614
+ }).satoshis
24615
+ );
24616
+ const selectedVtxos = selectionOf(req);
24617
+ const inputFee = selectedVtxos ? offchainInputFees(selectedVtxos, fees) : 0;
24618
+ const changeFee = selectedVtxos ? await changeOutputFee(
24619
+ ctx.wallet,
24620
+ selectedVtxos,
24621
+ BigInt(amt) + BigInt(exitFee),
24622
+ fees
24623
+ ) : 0n;
24383
24624
  return {
24384
24625
  railId: "onchain",
24385
24626
  amount: amt,
24386
- fee,
24387
- total: gross,
24627
+ fee: exitFee + inputFee + Number(changeFee),
24628
+ total: amt + exitFee + inputFee + Number(changeFee),
24388
24629
  send: async () => makeHandle("onchain", async (emit) => {
24389
- const txid = await new Ramps(ctx.wallet).offboard(
24390
- address,
24391
- fees,
24392
- BigInt(gross)
24393
- );
24630
+ const ramps = new Ramps(ctx.wallet);
24631
+ const txid = await ramps.offboardExact({
24632
+ destinationAddress: address,
24633
+ feeInfo: fees,
24634
+ amount: BigInt(amt),
24635
+ // Omitted, not undefined: an empty set covers nothing.
24636
+ ...selectedVtxos ? { vtxos: selectedVtxos } : {}
24637
+ });
24394
24638
  const result = { railId: "onchain", txid };
24395
24639
  emit({ status: "settled", result });
24396
24640
  return result;
@@ -24413,6 +24657,6 @@ function createDefaultPaymentRouter(wallet) {
24413
24657
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
24414
24658
  */
24415
24659
 
24416
- 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, 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, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
24417
- //# sourceMappingURL=chunk-YHJJSLE5.js.map
24418
- //# sourceMappingURL=chunk-YHJJSLE5.js.map
24660
+ 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 };
24661
+ //# sourceMappingURL=chunk-FTLPTF3R.js.map
24662
+ //# sourceMappingURL=chunk-FTLPTF3R.js.map