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

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 (85) hide show
  1. package/dist/adapters/expo.cjs +5 -5
  2. package/dist/adapters/expo.d.cts +2 -2
  3. package/dist/adapters/expo.d.ts +2 -2
  4. package/dist/adapters/expo.js +3 -3
  5. package/dist/adapters/indexedDB.cjs +5 -5
  6. package/dist/adapters/indexedDB.js +4 -4
  7. package/dist/{ark-CoJgwTi_.d.cts → ark-CjE0KkGp.d.cts} +405 -250
  8. package/dist/{ark-CoJgwTi_.d.ts → ark-CjE0KkGp.d.ts} +405 -250
  9. package/dist/{asyncStorageTaskQueue-CwknfM_j.d.cts → asyncStorageTaskQueue-CeLZhZFU.d.cts} +1 -1
  10. package/dist/{asyncStorageTaskQueue-D2Zl7yAB.d.ts → asyncStorageTaskQueue-D9SOPMPC.d.ts} +1 -1
  11. package/dist/{chunk-XN3554FT.js → chunk-4VVTRPH3.js} +4 -4
  12. package/dist/chunk-4VVTRPH3.js.map +1 -0
  13. package/dist/{chunk-RQSA7ADZ.cjs → chunk-5XMVTK5S.cjs} +37 -37
  14. package/dist/chunk-5XMVTK5S.cjs.map +1 -0
  15. package/dist/{chunk-L5ND2FJJ.cjs → chunk-7TXSPRN5.cjs} +161 -2
  16. package/dist/chunk-7TXSPRN5.cjs.map +1 -0
  17. package/dist/{chunk-GM6JRQP5.js → chunk-BHHLRZCS.js} +169 -42
  18. package/dist/chunk-BHHLRZCS.js.map +1 -0
  19. package/dist/{chunk-CFQO5MCM.cjs → chunk-EB3ALGTM.cjs} +178 -50
  20. package/dist/chunk-EB3ALGTM.cjs.map +1 -0
  21. package/dist/{chunk-3HUZAIKI.cjs → chunk-ENDNQPZA.cjs} +17 -44
  22. package/dist/chunk-ENDNQPZA.cjs.map +1 -0
  23. package/dist/{chunk-RJMFTTRJ.cjs → chunk-ENZ4YPR2.cjs} +614 -592
  24. package/dist/chunk-ENZ4YPR2.cjs.map +1 -0
  25. package/dist/{chunk-4AIHKC65.js → chunk-G6J6AEMH.js} +5 -4
  26. package/dist/chunk-G6J6AEMH.js.map +1 -0
  27. package/dist/{chunk-SV4PCGKM.js → chunk-NYR5H6GR.js} +348 -323
  28. package/dist/chunk-NYR5H6GR.js.map +1 -0
  29. package/dist/{chunk-EA3IWZ4W.cjs → chunk-SYDANBJI.cjs} +9 -8
  30. package/dist/chunk-SYDANBJI.cjs.map +1 -0
  31. package/dist/{chunk-ZRY2KSK5.js → chunk-XFMGIFBL.js} +4 -31
  32. package/dist/chunk-XFMGIFBL.js.map +1 -0
  33. package/dist/{chunk-C7TXXTMS.js → chunk-ZDDBTFVO.js} +146 -3
  34. package/dist/chunk-ZDDBTFVO.js.map +1 -0
  35. package/dist/contracts/handlers/index.cjs +8 -8
  36. package/dist/contracts/handlers/index.d.cts +3 -3
  37. package/dist/contracts/handlers/index.d.ts +3 -3
  38. package/dist/contracts/handlers/index.js +2 -2
  39. package/dist/{delegate-Dl87_zMA.d.cts → delegate-D3dY-2vS.d.cts} +1 -1
  40. package/dist/{delegate-CuaDqqQi.d.ts → delegate-DhGZk1r2.d.ts} +1 -1
  41. package/dist/{index-BC_G0VTh.d.cts → index-D6RQROds.d.cts} +2 -2
  42. package/dist/{index-CKwpZEpw.d.ts → index-DK4_iWYy.d.ts} +2 -2
  43. package/dist/index.cjs +208 -188
  44. package/dist/index.d.cts +35 -18
  45. package/dist/index.d.ts +35 -18
  46. package/dist/index.js +4 -4
  47. package/dist/repositories/realm/index.cjs +19 -19
  48. package/dist/repositories/realm/index.d.cts +2 -2
  49. package/dist/repositories/realm/index.d.ts +2 -2
  50. package/dist/repositories/realm/index.js +5 -5
  51. package/dist/repositories/sqlite/index.cjs +18 -18
  52. package/dist/repositories/sqlite/index.d.cts +2 -2
  53. package/dist/repositories/sqlite/index.d.ts +2 -2
  54. package/dist/repositories/sqlite/index.js +5 -5
  55. package/dist/{taskRunner-Df8YQpn1.d.ts → taskRunner-B-2MHCYG.d.ts} +1 -1
  56. package/dist/{taskRunner-RYihSj9E.d.cts → taskRunner-Brnak0Rc.d.cts} +1 -1
  57. package/dist/wallet/expo/background.cjs +14 -14
  58. package/dist/wallet/expo/background.d.cts +3 -3
  59. package/dist/wallet/expo/background.d.ts +3 -3
  60. package/dist/wallet/expo/background.js +6 -6
  61. package/dist/wallet/expo/index.cjs +14 -14
  62. package/dist/wallet/expo/index.cjs.map +1 -1
  63. package/dist/wallet/expo/index.d.cts +5 -5
  64. package/dist/wallet/expo/index.d.ts +5 -5
  65. package/dist/wallet/expo/index.js +5 -5
  66. package/dist/wallet/expo/index.js.map +1 -1
  67. package/dist/{wallet-B6s7_5bJ.d.cts → wallet-B5bwixQa.d.ts} +31 -31
  68. package/dist/{wallet-C61huH8i.d.ts → wallet-DiOrtYic.d.cts} +31 -31
  69. package/dist/worker/expo/index.cjs +9 -9
  70. package/dist/worker/expo/index.d.cts +4 -4
  71. package/dist/worker/expo/index.d.ts +4 -4
  72. package/dist/worker/expo/index.js +5 -5
  73. package/package.json +3 -2
  74. package/dist/chunk-3HUZAIKI.cjs.map +0 -1
  75. package/dist/chunk-4AIHKC65.js.map +0 -1
  76. package/dist/chunk-C7TXXTMS.js.map +0 -1
  77. package/dist/chunk-CFQO5MCM.cjs.map +0 -1
  78. package/dist/chunk-EA3IWZ4W.cjs.map +0 -1
  79. package/dist/chunk-GM6JRQP5.js.map +0 -1
  80. package/dist/chunk-L5ND2FJJ.cjs.map +0 -1
  81. package/dist/chunk-RJMFTTRJ.cjs.map +0 -1
  82. package/dist/chunk-RQSA7ADZ.cjs.map +0 -1
  83. package/dist/chunk-SV4PCGKM.js.map +0 -1
  84. package/dist/chunk-XN3554FT.js.map +0 -1
  85. package/dist/chunk-ZRY2KSK5.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { Transaction, setArkPsbtField, PrevArkTxField, ConditionWitness, AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, craftToSpendTx, getArkPsbtFields, CosignerPublicKey, BufferReader, BufferWriter, OP_RETURN_EMPTY_PKSCRIPT, VtxoTaprootTree, baseFetch, Intent, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError } from './chunk-GM6JRQP5.js';
2
- import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, DefaultVtxo, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, BoardingContractHandler } from './chunk-XN3554FT.js';
3
- import { VtxoScript, CSVMultisigTapscript, DEFAULT_NETWORK, ArkAddress, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, timelockToSequence, DEFAULT_NETWORK_NAME, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-C7TXXTMS.js';
1
+ import { Transaction, setArkPsbtField, PrevArkTxField, ConditionWitness, AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, craftToSpendTx, getArkPsbtFields, CosignerPublicKey, BufferReader, BufferWriter, OP_RETURN_EMPTY_PKSCRIPT, VtxoTaprootTree, baseFetch, Intent, rateGate, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError } from './chunk-BHHLRZCS.js';
2
+ import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, DefaultVtxo, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, BoardingContractHandler } from './chunk-4VVTRPH3.js';
3
+ import { VtxoScript, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, hasTerminalSpend, getNormalizedVtxos, isVirtualCoin, ArkAddress, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, timelockToSequence, DEFAULT_NETWORK_NAME, normalizeVtxo, getSequence, toBatchExpiry, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, canSpendOffchain, toVirtualStatus, resolveTimeHeight, parseLegacyExpiry, networks as networks$1, isExpired, DEFAULT_ARKADE_SERVER_URL, isPastExpiry } from './chunk-ZDDBTFVO.js';
4
4
  import { __export } from './chunk-NSBPE2FW.js';
5
5
  import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
6
6
  import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
@@ -991,7 +991,7 @@ var RestDelegateProvider = class {
991
991
  */
992
992
  async getDelegateInfo() {
993
993
  const url = `${this.url}/v1/delegator/info`;
994
- const response = await baseFetch(url);
994
+ const response = await rateGate.runHttp(url, () => baseFetch(url));
995
995
  if (!response.ok) {
996
996
  const errorText2 = await response.text();
997
997
  throw new Error(`Failed to get delegate info: ${errorText2}`);
@@ -2042,20 +2042,6 @@ var TxType = /* @__PURE__ */ ((TxType2) => {
2042
2042
  TxType2["TxReceived"] = "RECEIVED";
2043
2043
  return TxType2;
2044
2044
  })(TxType || {});
2045
- function isSpendable(vtxo) {
2046
- return !vtxo.isSpent;
2047
- }
2048
- function isRecoverable(vtxo) {
2049
- return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
2050
- }
2051
- function isExpired(vtxo) {
2052
- if (vtxo.virtualStatus.state === "swept") return true;
2053
- const expiry = vtxo.virtualStatus.batchExpiry;
2054
- if (!expiry) return false;
2055
- const expireAt = new Date(expiry);
2056
- if (expireAt.getFullYear() < 2025) return false;
2057
- return expiry <= Date.now();
2058
- }
2059
2045
  function isSubdust(vtxo, dust) {
2060
2046
  if (typeof vtxo === "bigint") return vtxo < dust;
2061
2047
  return vtxo.value < dust;
@@ -2725,7 +2711,7 @@ function selectPendingRecoveryOutpoints(contractsWithVtxos, signerSet, nowSecond
2725
2711
  continue;
2726
2712
  }
2727
2713
  for (const v of vtxos) {
2728
- if (isSpendable(v) && !isRecoverable(v)) out.add(`${v.txid}:${v.vout}`);
2714
+ if (!hasTerminalSpend(v) && !v.isSwept) out.add(`${v.txid}:${v.vout}`);
2729
2715
  }
2730
2716
  }
2731
2717
  return out;
@@ -2757,16 +2743,28 @@ var MAX_INPUTS_PER_INTENT = 20;
2757
2743
  function byValueDescending(vtxos) {
2758
2744
  return [...vtxos].sort((a, b) => b.value - a.value);
2759
2745
  }
2760
- function byExpiryAscending(vtxos) {
2746
+ async function fetchTimeHeight(wallet) {
2747
+ return resolveTimeHeight(wallet.onchainProvider);
2748
+ }
2749
+ function canMigrateBySend(vtxo, now) {
2750
+ if (!canSpendOffchain(vtxo, now)) return false;
2751
+ return vtxo.expiresAt !== void 0 || vtxo.expiresAtHeight !== void 0;
2752
+ }
2753
+ function byExpiryAscending(vtxos, now) {
2761
2754
  const expiryKey = (vtxo) => {
2762
- if (isRecoverable(vtxo)) return -Infinity;
2763
- const batchExpiry = vtxo.virtualStatus.batchExpiry;
2764
- if (isExpired(vtxo)) return batchExpiry ?? -Infinity;
2765
- if (!batchExpiry) return Infinity;
2766
- if (new Date(batchExpiry).getFullYear() < 2025) return Infinity;
2767
- return batchExpiry;
2755
+ if (vtxo.isSwept) return -Infinity;
2756
+ if (vtxo.expiresAt !== void 0) return vtxo.expiresAt.getTime();
2757
+ if (vtxo.expiresAtHeight !== void 0) {
2758
+ return isPastExpiry(vtxo, now) ? -Infinity : Infinity;
2759
+ }
2760
+ return Infinity;
2768
2761
  };
2769
- return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
2762
+ return [...vtxos].sort((a, b) => {
2763
+ const ka = expiryKey(a);
2764
+ const kb = expiryKey(b);
2765
+ if (ka === kb) return 0;
2766
+ return ka < kb ? -1 : 1;
2767
+ });
2770
2768
  }
2771
2769
  function capSettlementBatch(sorted, maxAmount) {
2772
2770
  const batch = [];
@@ -2792,22 +2790,19 @@ var DEFAULT_SETTLEMENT_CONFIG = {
2792
2790
  pollIntervalMs: 6e4,
2793
2791
  deprecatedSignerMigration: true
2794
2792
  };
2795
- function getRecoverableVtxos(vtxos, dustAmount) {
2793
+ function getRecoverableVtxos(vtxos, dustAmount, now) {
2796
2794
  return vtxos.filter((vtxo) => {
2797
- if (isRecoverable(vtxo)) {
2798
- return true;
2799
- }
2800
- if (isSpendable(vtxo) && isExpired(vtxo)) {
2795
+ if (canRecoverOnchain(vtxo, now)) {
2801
2796
  return true;
2802
2797
  }
2803
- if (vtxo.virtualStatus.state === "preconfirmed" && isSubdust(vtxo, dustAmount)) {
2798
+ if (canSpendOffchain(vtxo, now) && vtxo.isPreconfirmed && isSubdust(vtxo, dustAmount)) {
2804
2799
  return true;
2805
2800
  }
2806
2801
  return false;
2807
2802
  });
2808
2803
  }
2809
- function getRecoverableWithSubdust(vtxos, dustAmount) {
2810
- const recoverableVtxos = getRecoverableVtxos(vtxos, dustAmount);
2804
+ function getRecoverableWithSubdust(vtxos, dustAmount, now) {
2805
+ const recoverableVtxos = getRecoverableVtxos(vtxos, dustAmount, now);
2811
2806
  const subdust = [];
2812
2807
  const regular = [];
2813
2808
  for (const vtxo of recoverableVtxos) {
@@ -2831,17 +2826,16 @@ function getRecoverableWithSubdust(vtxos, dustAmount) {
2831
2826
  }
2832
2827
  function isVtxoExpiringSoon(vtxo, thresholdMs) {
2833
2828
  const realThresholdMs = thresholdMs <= 100 ? DEFAULT_THRESHOLD_MS : thresholdMs;
2834
- const { batchExpiry } = vtxo.virtualStatus;
2835
- if (!batchExpiry) return false;
2836
- const expireAt = new Date(batchExpiry);
2837
- if (expireAt.getFullYear() < 2025) return false;
2829
+ const expiresAt = normalizeVtxo(vtxo).expiresAt;
2830
+ if (expiresAt === void 0) return false;
2838
2831
  const now = Date.now();
2832
+ const batchExpiry = expiresAt.getTime();
2839
2833
  if (batchExpiry <= now) return false;
2840
2834
  return batchExpiry - now <= realThresholdMs;
2841
2835
  }
2842
- function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
2836
+ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount, now) {
2843
2837
  return vtxos.filter(
2844
- (vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || isRecoverable(vtxo) || isSpendable(vtxo) && isExpired(vtxo) || isSubdust(vtxo, dustAmount)
2838
+ (vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || canRecoverOnchain(vtxo, now) || isSubdust(vtxo, dustAmount)
2845
2839
  );
2846
2840
  }
2847
2841
  function isMigrationCapable(wallet) {
@@ -2990,7 +2984,8 @@ var VtxoManager = class _VtxoManager {
2990
2984
  withUnrolled: false
2991
2985
  });
2992
2986
  const dustAmount = getDustAmount(this.wallet);
2993
- let { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
2987
+ const now = await fetchTimeHeight(this.wallet);
2988
+ let { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount, now);
2994
2989
  if (vtxosToRecover.length === 0) {
2995
2990
  throw new Error("No recoverable VTXOs found");
2996
2991
  }
@@ -2999,7 +2994,7 @@ var VtxoManager = class _VtxoManager {
2999
2994
  const capped = capSettlementBatch(byValueDescending(vtxosToRecover), vtxoMaxAmount);
3000
2995
  if (capped.length < vtxosToRecover.length) {
3001
2996
  const recoverableCount = vtxosToRecover.length;
3002
- ({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
2997
+ ({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount, now));
3003
2998
  if (vtxosToRecover.length === 0) {
3004
2999
  throw new Error(
3005
3000
  `Capped recovery batch (highest-value subset of ${recoverableCount} recoverable VTXOs within the ${MAX_VTXOS_PER_SETTLEMENT}-input and ${vtxoMaxAmount}-sat limits) is below the dust threshold ${dustAmount}`
@@ -3051,7 +3046,8 @@ var VtxoManager = class _VtxoManager {
3051
3046
  const dustAmount = getDustAmount(this.wallet);
3052
3047
  const { vtxosToRecover, includesSubdust, totalAmount } = getRecoverableWithSubdust(
3053
3048
  allVtxos,
3054
- dustAmount
3049
+ dustAmount,
3050
+ await fetchTimeHeight(this.wallet)
3055
3051
  );
3056
3052
  const subdustAmount = vtxosToRecover.filter((v) => BigInt(v.value) < dustAmount).reduce((sum, v) => sum + BigInt(v.value), 0n);
3057
3053
  return {
@@ -3085,6 +3081,16 @@ var VtxoManager = class _VtxoManager {
3085
3081
  * ```
3086
3082
  */
3087
3083
  async getExpiringVtxos(thresholdMs) {
3084
+ return this.selectExpiringVtxos(thresholdMs);
3085
+ }
3086
+ /**
3087
+ * {@link getExpiringVtxos}, against a caller-supplied chain tip.
3088
+ *
3089
+ * The settle paths select, re-select after pre-flight, and sort by expiry within one pass;
3090
+ * each of those judges expiry, so they share one tip rather than fetching (and possibly
3091
+ * disagreeing on) one apiece. Fetches its own when `now` is omitted.
3092
+ */
3093
+ async selectExpiringVtxos(thresholdMs, now) {
3088
3094
  if (this.settlementConfig === false && thresholdMs === void 0) {
3089
3095
  return [];
3090
3096
  }
@@ -3097,7 +3103,12 @@ var VtxoManager = class _VtxoManager {
3097
3103
  } else {
3098
3104
  threshold = this.renewalConfig?.thresholdMs ?? DEFAULT_RENEWAL_CONFIG.thresholdMs;
3099
3105
  }
3100
- return getExpiringAndRecoverableVtxos(vtxos, threshold, getDustAmount(this.wallet));
3106
+ return getExpiringAndRecoverableVtxos(
3107
+ vtxos,
3108
+ threshold,
3109
+ getDustAmount(this.wallet),
3110
+ now ?? await fetchTimeHeight(this.wallet)
3111
+ );
3101
3112
  }
3102
3113
  /**
3103
3114
  * Renew expiring virtual outputs by settling them back to the wallet's address
@@ -3150,17 +3161,18 @@ var VtxoManager = class _VtxoManager {
3150
3161
  } else {
3151
3162
  threshold = DEFAULT_RENEWAL_CONFIG.thresholdMs;
3152
3163
  }
3153
- let vtxos = await this.getExpiringVtxos(threshold);
3164
+ const now = await fetchTimeHeight(this.wallet);
3165
+ let vtxos = await this.selectExpiringVtxos(threshold, now);
3154
3166
  if (vtxos.length === 0) {
3155
3167
  throw new Error("No VTXOs available to renew");
3156
3168
  }
3157
- vtxos = await this.revalidateBeforeSettle(vtxos, threshold);
3169
+ vtxos = await this.revalidateBeforeSettle(vtxos, threshold, now);
3158
3170
  if (vtxos.length === 0) {
3159
3171
  throw new Error("No VTXOs available to renew");
3160
3172
  }
3161
3173
  const info = await this.getInfoProvider()?.getInfo();
3162
3174
  const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
3163
- const capped = capSettlementBatch(byExpiryAscending(vtxos), vtxoMaxAmount);
3175
+ const capped = capSettlementBatch(byExpiryAscending(vtxos, now), vtxoMaxAmount);
3164
3176
  if (vtxoMaxAmount >= 0n) {
3165
3177
  const oversized = vtxos.filter((vtxo) => BigInt(vtxo.value) > vtxoMaxAmount);
3166
3178
  if (oversized.length > 0) {
@@ -3421,6 +3433,7 @@ var VtxoManager = class _VtxoManager {
3421
3433
  signers: reports
3422
3434
  };
3423
3435
  if (vtxoMigratable.length > 0) {
3436
+ const now = await fetchTimeHeight(this.wallet);
3424
3437
  report.vtxos = await this.runMigrationLeg(
3425
3438
  vtxoMigratable,
3426
3439
  (c) => c.vtxo.value,
@@ -3428,7 +3441,11 @@ var VtxoManager = class _VtxoManager {
3428
3441
  vtxoMaxAmount,
3429
3442
  dustAmount,
3430
3443
  "VTXO",
3431
- (capped) => wallet.sendSelectedVtxosToSelf(capped.map((c) => c.vtxo))
3444
+ (capped) => wallet.sendSelectedVtxosToSelf(
3445
+ capped.map((c) => c.vtxo),
3446
+ now
3447
+ ),
3448
+ (c) => canMigrateBySend(c.vtxo, now)
3432
3449
  );
3433
3450
  }
3434
3451
  if (boardingMigratable.length > 0) {
@@ -3468,10 +3485,22 @@ var VtxoManager = class _VtxoManager {
3468
3485
  * the server ceiling by the cap). The dust floor guards the degenerate cases
3469
3486
  * where every input was oversized or the whole holding sums below dust.
3470
3487
  */
3471
- async runMigrationLeg(candidates, valueOf, toRef, vtxoMaxAmount, dustAmount, legName, submit) {
3488
+ async runMigrationLeg(candidates, valueOf, toRef, vtxoMaxAmount, dustAmount, legName, submit, eligible) {
3489
+ const notSpendableRefs = [];
3490
+ const migratable = eligible ? candidates.filter((c) => {
3491
+ if (eligible(c)) return true;
3492
+ notSpendableRefs.push(toRef(c));
3493
+ return false;
3494
+ }) : candidates;
3495
+ if (notSpendableRefs.length > 0) {
3496
+ console.warn(
3497
+ `Deprecated-signer migration (${legName}): ${notSpendableRefs.length} input(s) are no longer cooperatively spendable at the current chain tip and were excluded; they recover through the sweep/recovery path instead.`
3498
+ );
3499
+ }
3500
+ const notSpendableField = notSpendableRefs.length > 0 ? { notSpendableOffchain: notSpendableRefs } : {};
3472
3501
  const oversizedRefs = [];
3473
3502
  const sized = [];
3474
- for (const c of candidates) {
3503
+ for (const c of migratable) {
3475
3504
  if (vtxoMaxAmount >= 0n && BigInt(valueOf(c)) > vtxoMaxAmount) {
3476
3505
  oversizedRefs.push(toRef(c));
3477
3506
  } else {
@@ -3491,11 +3520,12 @@ var VtxoManager = class _VtxoManager {
3491
3520
  const deferred = sized.length - capped.length;
3492
3521
  const totalAmount = capped.reduce((sum, c) => sum + BigInt(valueOf(c)), 0n);
3493
3522
  if (totalAmount < dustAmount) {
3494
- const onlyOversized = sized.length === 0 && oversizedRefs.length > 0;
3523
+ const skipped = sized.length > 0 ? "below-dust" : oversizedRefs.length > 0 ? "oversized-only" : notSpendableRefs.length > 0 ? "not-spendable-only" : "below-dust";
3495
3524
  return {
3496
3525
  migrated: [],
3497
- skipped: onlyOversized ? "oversized-only" : "below-dust",
3498
- ...oversizedField
3526
+ skipped,
3527
+ ...oversizedField,
3528
+ ...notSpendableField
3499
3529
  };
3500
3530
  }
3501
3531
  try {
@@ -3504,13 +3534,15 @@ var VtxoManager = class _VtxoManager {
3504
3534
  txid,
3505
3535
  migrated: capped.map(toRef),
3506
3536
  ...deferred > 0 ? { deferred } : {},
3507
- ...oversizedField
3537
+ ...oversizedField,
3538
+ ...notSpendableField
3508
3539
  };
3509
3540
  } catch (e) {
3510
3541
  return {
3511
3542
  migrated: [],
3512
3543
  error: e instanceof Error ? e.message : String(e),
3513
- ...oversizedField
3544
+ ...oversizedField,
3545
+ ...notSpendableField
3514
3546
  };
3515
3547
  }
3516
3548
  }
@@ -3538,8 +3570,8 @@ var VtxoManager = class _VtxoManager {
3538
3570
  if (!serverPubKey) continue;
3539
3571
  const cls = classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds);
3540
3572
  if (cls.status === "CURRENT") continue;
3541
- const recoverable = vtxos.filter((v) => isRecoverable(v));
3542
- const spendable = vtxos.filter((v) => isSpendable(v) && !isRecoverable(v));
3573
+ const recoverable = vtxos.filter((v) => v.isSwept && !hasTerminalSpend(v));
3574
+ const spendable = vtxos.filter((v) => !hasTerminalSpend(v) && !v.isSwept);
3543
3575
  const value = spendable.reduce((sum, v) => sum + v.value, 0);
3544
3576
  let recoverableCount = 0;
3545
3577
  let recoverableValue = 0;
@@ -3552,7 +3584,7 @@ var VtxoManager = class _VtxoManager {
3552
3584
  awaitingSweepCount = spendable.length;
3553
3585
  awaitingSweepValue = value;
3554
3586
  for (const v of spendable) {
3555
- const exp = v.virtualStatus.batchExpiry;
3587
+ const exp = toBatchExpiry(v);
3556
3588
  if (exp !== void 0 && (nextSweepEta === void 0 || exp < nextSweepEta)) {
3557
3589
  nextSweepEta = exp;
3558
3590
  }
@@ -3588,7 +3620,7 @@ var VtxoManager = class _VtxoManager {
3588
3620
  }
3589
3621
  if (isCooperativelyMigratable(cls.status)) {
3590
3622
  for (const v of spendable) {
3591
- if (!v.virtualStatus.batchExpiry) continue;
3623
+ if (v.expiresAt === void 0 && v.expiresAtHeight === void 0) continue;
3592
3624
  migratable.push({ vtxo: v, classification: cls });
3593
3625
  }
3594
3626
  } else if (cls.status === "EXPIRED") {
@@ -3966,7 +3998,7 @@ var VtxoManager = class _VtxoManager {
3966
3998
  * candidates and lets the post-submit `VTXO_ALREADY_SPENT` recovery
3967
3999
  * handle whatever slipped through.
3968
4000
  */
3969
- async revalidateBeforeSettle(candidates, thresholdMs) {
4001
+ async revalidateBeforeSettle(candidates, thresholdMs, now) {
3970
4002
  if (candidates.length === 0) return candidates;
3971
4003
  try {
3972
4004
  const cm = await this.wallet.getContractManager();
@@ -3976,7 +4008,7 @@ var VtxoManager = class _VtxoManager {
3976
4008
  return candidates;
3977
4009
  }
3978
4010
  try {
3979
- const refreshed = await this.getExpiringVtxos(thresholdMs);
4011
+ const refreshed = await this.selectExpiringVtxos(thresholdMs, now);
3980
4012
  const candidateKeys = new Set(candidates.map((v) => `${v.txid}:${v.vout}`));
3981
4013
  return refreshed.filter((v) => candidateKeys.has(`${v.txid}:${v.vout}`));
3982
4014
  } catch (e) {
@@ -4094,10 +4126,12 @@ var VtxoManager = class _VtxoManager {
4094
4126
  (u) => u.status.confirmed && !this.knownBoardingUtxos.has(`${u.txid}:${u.vout}`) && !expiredSet.has(`${u.txid}:${u.vout}`)
4095
4127
  );
4096
4128
  let expiringVtxos = [];
4129
+ let now;
4097
4130
  if (!this.renewalInProgress) {
4098
4131
  try {
4099
- expiringVtxos = await this.getExpiringVtxos();
4100
- expiringVtxos = await this.revalidateBeforeSettle(expiringVtxos);
4132
+ now = await fetchTimeHeight(this.wallet);
4133
+ expiringVtxos = await this.selectExpiringVtxos(void 0, now);
4134
+ expiringVtxos = await this.revalidateBeforeSettle(expiringVtxos, void 0, now);
4101
4135
  } catch (e) {
4102
4136
  console.error("Error fetching expiring VTXOs:", e);
4103
4137
  }
@@ -4129,17 +4163,11 @@ var VtxoManager = class _VtxoManager {
4129
4163
  totalAmount += BigInt(u.value) - BigInt(inputFee.satoshis);
4130
4164
  }
4131
4165
  const filteredVtxos = [];
4132
- for (const v of byExpiryAscending(expiringVtxos)) {
4166
+ for (const v of byExpiryAscending(expiringVtxos, now ?? { timestamp: /* @__PURE__ */ new Date() })) {
4133
4167
  if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
4134
4168
  break;
4135
4169
  }
4136
- const inputFee = estimator.evalOffchainInput({
4137
- amount: BigInt(v.value),
4138
- type: v.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
4139
- weight: 0,
4140
- birth: v.createdAt,
4141
- expiry: v.virtualStatus.batchExpiry ? new Date(v.virtualStatus.batchExpiry) : void 0
4142
- });
4170
+ const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(v));
4143
4171
  if (inputFee.satoshis >= v.value) {
4144
4172
  continue;
4145
4173
  }
@@ -4343,8 +4371,8 @@ function readUInt32BE(array, offset) {
4343
4371
  function noteTapscript(preimageHash) {
4344
4372
  return Script.encode(["SHA256", preimageHash, "EQUAL"]);
4345
4373
  }
4346
- var ArkCash = class _ArkCash {
4347
- constructor(privateKey, serverPubKey, csvTimelock, hrp = _ArkCash.DefaultHRP) {
4374
+ var ArkadeCash = class _ArkadeCash {
4375
+ constructor(privateKey, serverPubKey, csvTimelock, hrp = _ArkadeCash.DefaultHRP) {
4348
4376
  this.csvTimelock = csvTimelock;
4349
4377
  this.hrp = hrp;
4350
4378
  if (privateKey.length !== 32) {
@@ -4363,7 +4391,7 @@ var ArkCash = class _ArkCash {
4363
4391
  }
4364
4392
  csvTimelock;
4365
4393
  hrp;
4366
- static DefaultHRP = "arkcash";
4394
+ static DefaultHRP = "arkadecash";
4367
4395
  static Version = 0;
4368
4396
  static PayloadLength = 1 + 32 + 32 + 4;
4369
4397
  // 69 bytes
@@ -4386,32 +4414,32 @@ var ArkCash = class _ArkCash {
4386
4414
  return this._publicKey.slice();
4387
4415
  }
4388
4416
  static generate(serverPubKey, csvTimelock, hrp) {
4389
- return new _ArkCash(randomPrivateKeyBytes(), serverPubKey, csvTimelock, hrp);
4417
+ return new _ArkadeCash(randomPrivateKeyBytes(), serverPubKey, csvTimelock, hrp);
4390
4418
  }
4391
4419
  static fromString(encoded) {
4392
4420
  const decoded = bech32m.decodeUnsafe(encoded.trim().toLowerCase(), 1023);
4393
4421
  if (!decoded) {
4394
- throw new Error("Invalid arkcash string: failed to decode bech32m");
4422
+ throw new Error("Invalid arkadeCash string: failed to decode bech32m");
4395
4423
  }
4396
4424
  const data = new Uint8Array(bech32m.fromWords(decoded.words));
4397
- if (data.length !== _ArkCash.PayloadLength) {
4425
+ if (data.length !== _ArkadeCash.PayloadLength) {
4398
4426
  throw new Error(
4399
- `Invalid arkcash data length: expected ${_ArkCash.PayloadLength} bytes, got ${data.length}`
4427
+ `Invalid arkadeCash data length: expected ${_ArkadeCash.PayloadLength} bytes, got ${data.length}`
4400
4428
  );
4401
4429
  }
4402
4430
  const version = data[0];
4403
- if (version !== _ArkCash.Version) {
4404
- throw new Error(`Unsupported arkcash version: ${version}`);
4431
+ if (version !== _ArkadeCash.Version) {
4432
+ throw new Error(`Unsupported arkadeCash version: ${version}`);
4405
4433
  }
4406
4434
  const privateKey = data.slice(1, 33);
4407
4435
  const serverPubKey = data.slice(33, 65);
4408
4436
  const sequence = new DataView(data.buffer, data.byteOffset + 65, 4).getUint32(0, false);
4409
4437
  const csvTimelock = sequenceToTimelock(sequence);
4410
- return new _ArkCash(privateKey, serverPubKey, csvTimelock, decoded.prefix);
4438
+ return new _ArkadeCash(privateKey, serverPubKey, csvTimelock, decoded.prefix);
4411
4439
  }
4412
4440
  toString() {
4413
- const data = new Uint8Array(_ArkCash.PayloadLength);
4414
- data[0] = _ArkCash.Version;
4441
+ const data = new Uint8Array(_ArkadeCash.PayloadLength);
4442
+ data[0] = _ArkadeCash.Version;
4415
4443
  data.set(this._privateKey, 1);
4416
4444
  data.set(this._serverPubKey, 33);
4417
4445
  const sequence = timelockToSequence(this.csvTimelock);
@@ -5497,13 +5525,13 @@ function subtractAssets(spent, change) {
5497
5525
  return Array.from(map, ([assetId, amount]) => ({ assetId, amount }));
5498
5526
  }
5499
5527
  async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, getTxCreatedAt) {
5500
- const fromOldestVtxo = [...vtxos].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
5528
+ const fromOldestVtxo = vtxos.map(normalizeVtxo).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
5501
5529
  const unmatchedSettledBoardingTxs = allBoardingTxs.filter(isSettledBoardingReceive).sort((a, b) => a.createdAt - b.createdAt);
5502
5530
  const sent = [];
5503
5531
  let received = [];
5504
5532
  for (const vtxo of fromOldestVtxo) {
5505
5533
  if (vtxo.status.isLeaf) {
5506
- const commitmentTxid = vtxo.virtualStatus.commitmentTxIds[0];
5534
+ const commitmentTxid = vtxo.commitmentTxIds[0];
5507
5535
  const vtxoCreatedAt = vtxo.createdAt.getTime();
5508
5536
  const ignoredCommitment = commitmentsToIgnore.has(commitmentTxid) || !!vtxo.settledBy && commitmentsToIgnore.has(vtxo.settledBy);
5509
5537
  if (ignoredCommitment) {
@@ -5511,7 +5539,7 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
5511
5539
  unmatchedSettledBoardingTxs,
5512
5540
  (tx) => tx.createdAt <= vtxoCreatedAt && (tx.key.commitmentTxid === commitmentTxid || tx.key.commitmentTxid === vtxo.settledBy)
5513
5541
  );
5514
- } else if (fromOldestVtxo.filter((v) => v.settledBy === vtxo.virtualStatus.commitmentTxIds[0]).length === 0) {
5542
+ } else if (fromOldestVtxo.filter((v) => v.settledBy === vtxo.commitmentTxIds[0]).length === 0) {
5515
5543
  const duplicateBoardingReceive = consumeBoardingReceive(
5516
5544
  unmatchedSettledBoardingTxs,
5517
5545
  (tx) => tx.amount === vtxo.value && tx.createdAt <= vtxoCreatedAt
@@ -5574,7 +5602,7 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
5574
5602
  }
5575
5603
  if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
5576
5604
  const changes = fromOldestVtxo.filter(
5577
- (v) => v.status.isLeaf && v.virtualStatus.commitmentTxIds?.every((_) => vtxo.settledBy === _)
5605
+ (v) => v.status.isLeaf && v.commitmentTxIds.length > 0 && v.commitmentTxIds.every((_) => vtxo.settledBy === _)
5578
5606
  );
5579
5607
  const forfeitVtxos = fromOldestVtxo.filter((v) => v.settledBy === vtxo.settledBy);
5580
5608
  const forfeitAmount = forfeitVtxos.reduce((acc, v) => acc + v.value, 0);
@@ -5963,7 +5991,7 @@ var DelegateManagerImpl = class {
5963
5991
  const destinationScript = ArkAddress.decode(destination).pkScript;
5964
5992
  const arkInfo = await this.arkInfoProvider.getInfo();
5965
5993
  const delegateInfo = await this.delegateProvider.getDelegateInfo();
5966
- const eligible = vtxos.filter(
5994
+ const eligible = vtxos.map(normalizeVtxo).filter(
5967
5995
  (v) => isAnnotated(v) && findDelegateTapLeaf(v, delegateInfo.pubkey) !== void 0
5968
5996
  );
5969
5997
  if (eligible.length === 0) {
@@ -5988,14 +6016,15 @@ var DelegateManagerImpl = class {
5988
6016
  }
5989
6017
  return { delegated: eligible, failed: [] };
5990
6018
  }
6019
+ const now = { timestamp: /* @__PURE__ */ new Date() };
5991
6020
  const groupByExpiry = /* @__PURE__ */ new Map();
5992
6021
  let recoverableVtxos = [];
5993
6022
  for (const vtxo of eligible) {
5994
- if (isRecoverable(vtxo)) {
6023
+ if (canRecoverOnchain(vtxo, now)) {
5995
6024
  recoverableVtxos.push(vtxo);
5996
6025
  continue;
5997
6026
  }
5998
- const expiry = vtxo.virtualStatus.batchExpiry;
6027
+ const expiry = toBatchExpiry(vtxo);
5999
6028
  if (!expiry) continue;
6000
6029
  const dayKey = getDayTimestamp(expiry);
6001
6030
  groupByExpiry.set(dayKey, [...groupByExpiry.get(dayKey) ?? [], vtxo]);
@@ -6059,10 +6088,8 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
6059
6088
  throw new Error("unable to delegate: delegate provider not configured");
6060
6089
  }
6061
6090
  if (!delegateAt) {
6062
- const expiryTimestamp = vtxos.filter((coin) => !isRecoverable(coin) && coin.virtualStatus.batchExpiry).reduce(
6063
- (min, coin) => Math.min(min, coin.virtualStatus.batchExpiry),
6064
- Number.MAX_SAFE_INTEGER
6065
- );
6091
+ const now = { timestamp: /* @__PURE__ */ new Date() };
6092
+ const expiryTimestamp = vtxos.filter((coin) => !canRecoverOnchain(coin, now) && toBatchExpiry(coin)).reduce((min, coin) => Math.min(min, toBatchExpiry(coin)), Number.MAX_SAFE_INTEGER);
6066
6093
  if (!expiryTimestamp || expiryTimestamp === Number.MAX_SAFE_INTEGER) {
6067
6094
  delegateAt = new Date(Date.now() + 1 * 60 * 1e3);
6068
6095
  } else {
@@ -6091,11 +6118,8 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
6091
6118
  let amount = 0n;
6092
6119
  for (const coin of vtxos) {
6093
6120
  const inputFee = estimator.evalOffchainInput({
6094
- amount: BigInt(coin.value),
6095
- type: "vtxo",
6096
- weight: 0,
6097
- birth: coin.createdAt,
6098
- expiry: coin.virtualStatus.batchExpiry ? new Date(coin.virtualStatus.batchExpiry) : void 0
6121
+ ...toOffchainInputFeeParams(coin),
6122
+ type: "vtxo"
6099
6123
  });
6100
6124
  if (inputFee.value >= coin.value) {
6101
6125
  continue;
@@ -6144,7 +6168,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
6144
6168
  Address(getNetwork(network)).decode(forfeitAddress)
6145
6169
  );
6146
6170
  const forfeits = await Promise.all(
6147
- vtxos.filter((v) => !isRecoverable(v)).map(async (coin) => {
6171
+ vtxos.filter((v) => !canRecoverOnchain(v, { timestamp: /* @__PURE__ */ new Date() })).map(async (coin) => {
6148
6172
  const forfeit = await makeDelegateForfeitTx(
6149
6173
  coin,
6150
6174
  dust,
@@ -6932,7 +6956,8 @@ function validateVtxosForScript(vtxos, script, context) {
6932
6956
  );
6933
6957
  }
6934
6958
  async function getVtxosForContract(repo, contract) {
6935
- return repo.getVtxosForScript ? repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
6959
+ const vtxos = repo.getVtxosForScript ? await repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
6960
+ return vtxos.map(normalizeVtxo);
6936
6961
  }
6937
6962
  async function saveVtxosForContract(repo, contract, vtxos) {
6938
6963
  if (repo.saveVtxosForScript) {
@@ -7136,7 +7161,7 @@ var deserializeTapLeaf = (t) => {
7136
7161
  const s = hex.decode(t.s);
7137
7162
  return [cb, s];
7138
7163
  };
7139
- var deserializeVtxo = (o) => ({
7164
+ var deserializeVtxo = (o) => normalizeVtxo({
7140
7165
  ...o,
7141
7166
  createdAt: new Date(o.createdAt),
7142
7167
  tapTree: hex.decode(o.tapTree),
@@ -7735,50 +7760,12 @@ function getLifecycleWeight(v) {
7735
7760
  if (v.arkTxId) weight += 2;
7736
7761
  return weight;
7737
7762
  }
7763
+
7764
+ // src/repositories/migrations/walletRepositoryImpl.ts
7738
7765
  var getVtxosStorageKey = (address) => `vtxos:${address}`;
7739
7766
  var getUtxosStorageKey = (address) => `utxos:${address}`;
7740
7767
  var getTransactionsStorageKey = (address) => `tx:${address}`;
7741
7768
  var walletStateStorageKey = "wallet:state";
7742
- var serializeVtxo2 = (v) => ({
7743
- ...v,
7744
- tapTree: hex.encode(v.tapTree),
7745
- forfeitTapLeafScript: serializeTapLeaf2(v.forfeitTapLeafScript),
7746
- intentTapLeafScript: serializeTapLeaf2(v.intentTapLeafScript),
7747
- extraWitness: v.extraWitness?.map(hex.encode),
7748
- assets: serializeAssets(v.assets)
7749
- });
7750
- var serializeUtxo2 = (u) => ({
7751
- ...u,
7752
- tapTree: hex.encode(u.tapTree),
7753
- forfeitTapLeafScript: serializeTapLeaf2(u.forfeitTapLeafScript),
7754
- intentTapLeafScript: serializeTapLeaf2(u.intentTapLeafScript),
7755
- extraWitness: u.extraWitness?.map(hex.encode)
7756
- });
7757
- var deserializeVtxo2 = (o) => ({
7758
- ...o,
7759
- createdAt: new Date(o.createdAt),
7760
- tapTree: hex.decode(o.tapTree),
7761
- forfeitTapLeafScript: deserializeTapLeaf2(o.forfeitTapLeafScript),
7762
- intentTapLeafScript: deserializeTapLeaf2(o.intentTapLeafScript),
7763
- extraWitness: o.extraWitness?.map(hex.decode),
7764
- assets: deserializeAssets(o.assets)
7765
- });
7766
- var deserializeUtxo2 = (o) => ({
7767
- ...o,
7768
- tapTree: hex.decode(o.tapTree),
7769
- forfeitTapLeafScript: deserializeTapLeaf2(o.forfeitTapLeafScript),
7770
- intentTapLeafScript: deserializeTapLeaf2(o.intentTapLeafScript),
7771
- extraWitness: o.extraWitness?.map(hex.decode)
7772
- });
7773
- var serializeTapLeaf2 = ([cb, s]) => ({
7774
- cb: hex.encode(TaprootControlBlock.encode(cb)),
7775
- s: hex.encode(s)
7776
- });
7777
- var deserializeTapLeaf2 = (t) => {
7778
- const cb = TaprootControlBlock.decode(hex.decode(t.cb));
7779
- const s = hex.decode(t.s);
7780
- return [cb, s];
7781
- };
7782
7769
  var WalletRepositoryImpl = class {
7783
7770
  version = 1;
7784
7771
  storage;
@@ -7790,7 +7777,7 @@ var WalletRepositoryImpl = class {
7790
7777
  if (!stored) return [];
7791
7778
  try {
7792
7779
  const parsed = JSON.parse(stored);
7793
- return parsed.map(deserializeVtxo2);
7780
+ return parsed.map(deserializeVtxo);
7794
7781
  } catch (error) {
7795
7782
  console.error(`Failed to parse VTXOs for address ${address}:`, error);
7796
7783
  return [];
@@ -7810,7 +7797,7 @@ var WalletRepositoryImpl = class {
7810
7797
  }
7811
7798
  await this.storage.setItem(
7812
7799
  getVtxosStorageKey(address),
7813
- JSON.stringify(storedVtxos.map(serializeVtxo2))
7800
+ JSON.stringify(storedVtxos.map(serializeVtxo))
7814
7801
  );
7815
7802
  }
7816
7803
  async clearVtxos(address) {
@@ -7824,7 +7811,7 @@ var WalletRepositoryImpl = class {
7824
7811
  if (!stored) return [];
7825
7812
  try {
7826
7813
  const parsed = JSON.parse(stored);
7827
- return parsed.map(deserializeUtxo2);
7814
+ return parsed.map(deserializeUtxo);
7828
7815
  } catch (error) {
7829
7816
  console.error(`Failed to parse UTXOs for address ${address}:`, error);
7830
7817
  return [];
@@ -7844,7 +7831,7 @@ var WalletRepositoryImpl = class {
7844
7831
  });
7845
7832
  await this.storage.setItem(
7846
7833
  getUtxosStorageKey(address),
7847
- JSON.stringify(storedUtxos.map(serializeUtxo2))
7834
+ JSON.stringify(storedUtxos.map(serializeUtxo))
7848
7835
  );
7849
7836
  }
7850
7837
  async clearUtxos(address) {
@@ -8496,15 +8483,27 @@ var ContractWatcher = class {
8496
8483
  }
8497
8484
  /**
8498
8485
  * Handle a subscription update.
8486
+ *
8487
+ * Normalization boundary: `getSubscription` is part of the public `IndexerProvider` interface,
8488
+ * so a consumer implementation may yield legacy-shaped VTXOs. Normalizing on ingest also fixes
8489
+ * the shape of the payloads emitted to external event consumers.
8499
8490
  */
8500
8491
  handleSubscriptionUpdate(update) {
8501
8492
  if (!this.eventCallback) return;
8502
8493
  const timestamp = Date.now();
8503
8494
  if (update.newVtxos?.length) {
8504
- this.processSubscriptionVtxos(update.newVtxos, "vtxo_received", timestamp);
8495
+ this.processSubscriptionVtxos(
8496
+ update.newVtxos.map(normalizeVtxo),
8497
+ "vtxo_received",
8498
+ timestamp
8499
+ );
8505
8500
  }
8506
8501
  if (update.spentVtxos?.length) {
8507
- this.processSubscriptionVtxos(update.spentVtxos, "vtxo_spent", timestamp);
8502
+ this.processSubscriptionVtxos(
8503
+ update.spentVtxos.map(normalizeVtxo),
8504
+ "vtxo_spent",
8505
+ timestamp
8506
+ );
8508
8507
  }
8509
8508
  }
8510
8509
  /**
@@ -8634,7 +8633,7 @@ async function reconcileIntents(deps) {
8634
8633
  let vtxosByOutpoint = /* @__PURE__ */ new Map();
8635
8634
  if (outpoints.length > 0) {
8636
8635
  try {
8637
- const { vtxos } = await deps.indexerProvider.getVtxos({ outpoints });
8636
+ const { vtxos } = await getNormalizedVtxos(deps.indexerProvider, { outpoints });
8638
8637
  vtxosByOutpoint = new Map(vtxos.map((v) => [outpointKey(v), v]));
8639
8638
  } catch (e) {
8640
8639
  console.error("reconcileIntents: failed to fetch input VTXO state", e);
@@ -8887,9 +8886,10 @@ var ContractManager = class _ContractManager {
8887
8886
  * Safety-critical invariants (spec §2.C / §4):
8888
8887
  * - `opts.materialize(i)` throwing is structural/fatal: it is NOT
8889
8888
  * wrapped — it propagates and aborts the scan.
8890
- * - A `discoverAt` rejection is collected into `handlerErrors` and the
8891
- * loop continues (the gap counter still advances for that index if no
8892
- * other handler hit it).
8889
+ * - A `discoverAt` rejection is collected into `handlerErrors` and makes
8890
+ * its index *indeterminate*: it does NOT advance the gap counter, and
8891
+ * the scan stops verifying there, reporting `truncatedAt`. Only an index
8892
+ * every handler answered for can be a confirmed miss.
8893
8893
  * - `persistAndWatchContract` rejecting is operational/fatal and
8894
8894
  * propagates (only `discoverAt` is guarded).
8895
8895
  * - Within an index the handler probes run concurrently (independent
@@ -8903,8 +8903,12 @@ var ContractManager = class _ContractManager {
8903
8903
  * over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
8904
8904
  * invoked on exactly the same index set. The window's hits are still
8905
8905
  * processed strictly in ascending index order, so the discovered set,
8906
- * persisted rows, `lastIndexUsed`, and `handlerErrors` are byte-for-byte
8907
- * identical to the serial path — only the wall-clock differs.
8906
+ * persisted rows, `highestConfirmedUsedIndex`, and `handlerErrors` are
8907
+ * byte-for-byte identical to the serial path — only the wall-clock
8908
+ * differs. Truncation is the one exception: a window's concurrent probes
8909
+ * can surface hits above the failed index that a serial scan would never
8910
+ * have reached, so a truncated batched scan discovers a superset — never
8911
+ * a subset — of the serial one.
8908
8912
  */
8909
8913
  async scanContracts(opts) {
8910
8914
  const gapLimit = opts.gapLimit ?? 20;
@@ -8926,7 +8930,8 @@ var ContractManager = class _ContractManager {
8926
8930
  ];
8927
8931
  const maxIdx = opts.hd ? SCAN_MAX_INDEX : 0;
8928
8932
  const handlerErrors = [];
8929
- let lastIndexUsed = -1;
8933
+ let highestConfirmedUsedIndex = -1;
8934
+ let truncatedAt;
8930
8935
  let unused = 0;
8931
8936
  let i = 0;
8932
8937
  const probeIndex = async (index) => {
@@ -8953,6 +8958,7 @@ var ContractManager = class _ContractManager {
8953
8958
  const index = windowIndices[w];
8954
8959
  const probes = windowProbes[w];
8955
8960
  let hitAtThisIndex = false;
8961
+ let indeterminate = false;
8956
8962
  for (let h = 0; h < discoverables.length; h++) {
8957
8963
  const probe = probes[h];
8958
8964
  if (!probe.ok) {
@@ -8961,6 +8967,7 @@ var ContractManager = class _ContractManager {
8961
8967
  index,
8962
8968
  error: probe.error
8963
8969
  });
8970
+ indeterminate = true;
8964
8971
  continue;
8965
8972
  }
8966
8973
  for (const c of probe.found) {
@@ -8968,21 +8975,26 @@ var ContractManager = class _ContractManager {
8968
8975
  hitAtThisIndex = true;
8969
8976
  }
8970
8977
  }
8971
- if (hitAtThisIndex) {
8972
- lastIndexUsed = index;
8973
- unused = 0;
8974
- } else {
8975
- unused += 1;
8976
- }
8978
+ if (indeterminate && truncatedAt === void 0) truncatedAt = index;
8979
+ if (hitAtThisIndex) highestConfirmedUsedIndex = index;
8980
+ if (truncatedAt !== void 0) continue;
8981
+ if (hitAtThisIndex) unused = 0;
8982
+ else unused += 1;
8977
8983
  }
8984
+ if (truncatedAt !== void 0) break;
8978
8985
  i = windowEnd + 1;
8979
8986
  }
8980
- if (opts.hd && i > maxIdx && unused < gapLimit) {
8987
+ if (opts.hd && truncatedAt === void 0 && i > maxIdx && unused < gapLimit) {
8981
8988
  throw new Error(
8982
8989
  `scanContracts: reached SCAN_MAX_INDEX (${SCAN_MAX_INDEX}) without closing the ${gapLimit}-index gap window; a Discoverable handler may be returning unconditional hits`
8983
8990
  );
8984
8991
  }
8985
- return { lastIndexUsed, handlerErrors };
8992
+ return {
8993
+ lastIndexUsed: highestConfirmedUsedIndex,
8994
+ highestConfirmedUsedIndex,
8995
+ ...truncatedAt !== void 0 && { truncatedAt },
8996
+ handlerErrors
8997
+ };
8986
8998
  }
8987
8999
  /**
8988
9000
  * Get contracts with optional filters.
@@ -9029,7 +9041,9 @@ var ContractManager = class _ContractManager {
9029
9041
  byScript.set(contract.script, contract);
9030
9042
  }
9031
9043
  const tapscriptCache = /* @__PURE__ */ new Map();
9032
- return vtxos.map((vtxo) => extendVirtualCoinForContract(vtxo, byScript, tapscriptCache));
9044
+ return vtxos.map(
9045
+ (vtxo) => extendVirtualCoinForContract(normalizeVtxo(vtxo), byScript, tapscriptCache)
9046
+ );
9033
9047
  }
9034
9048
  buildContractsDbFilter(filter) {
9035
9049
  return {
@@ -9197,7 +9211,7 @@ var ContractManager = class _ContractManager {
9197
9211
  }
9198
9212
  async refreshOutpoints(outpoints) {
9199
9213
  if (outpoints.length === 0) return;
9200
- const { vtxos } = await this.config.indexerProvider.getVtxos({
9214
+ const { vtxos } = await getNormalizedVtxos(this.config.indexerProvider, {
9201
9215
  outpoints
9202
9216
  });
9203
9217
  if (vtxos.length === 0) return;
@@ -9325,7 +9339,7 @@ var ContractManager = class _ContractManager {
9325
9339
  async reconcilePendingFrontier(contracts) {
9326
9340
  const scripts = contracts.map((c) => c.script);
9327
9341
  const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
9328
- const { vtxos } = await this.config.indexerProvider.getVtxos({
9342
+ const { vtxos } = await getNormalizedVtxos(this.config.indexerProvider, {
9329
9343
  scripts,
9330
9344
  pendingOnly: true
9331
9345
  });
@@ -9416,7 +9430,7 @@ var ContractManager = class _ContractManager {
9416
9430
  let pageIndex = 0;
9417
9431
  let hasMore = true;
9418
9432
  while (hasMore) {
9419
- const { vtxos, page } = await this.config.indexerProvider.getVtxos({
9433
+ const { vtxos, page } = await getNormalizedVtxos(this.config.indexerProvider, {
9420
9434
  scripts,
9421
9435
  ...windowOpts,
9422
9436
  pageIndex,
@@ -10233,14 +10247,14 @@ var cashReport = (vtxo, reason) => ({
10233
10247
  value: vtxo.value,
10234
10248
  reason
10235
10249
  });
10236
- var ArkCashCreateError = class extends Error {
10250
+ var ArkadeCashCreateError = class extends Error {
10237
10251
  constructor(cash, cause) {
10238
10252
  super(
10239
- `Failed to create ArkCash: send failed after the note may have been submitted. Recover with claimCash using the token on this error's \`cash\` field.`
10253
+ `Failed to create ArkadeCash: send failed after the note may have been submitted. Recover with claimCash using the token on this error's \`cash\` field.`
10240
10254
  );
10241
10255
  this.cash = cash;
10242
10256
  this.cause = cause;
10243
- this.name = "ArkCashCreateError";
10257
+ this.name = "ArkadeCashCreateError";
10244
10258
  }
10245
10259
  cash;
10246
10260
  cause;
@@ -10703,20 +10717,21 @@ var ReadonlyWallet = class _ReadonlyWallet {
10703
10717
  }
10704
10718
  let recoverable = 0;
10705
10719
  let pendingRecovery = 0;
10706
- const settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
10707
- const preconfirmed = vtxos.filter(
10708
- (coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
10720
+ const now = { timestamp: /* @__PURE__ */ new Date() };
10721
+ const settled = vtxos.filter(
10722
+ (coin) => canSpendOffchain(coin, now) && !coin.isPreconfirmed && !isPendingRecovery(coin)
10709
10723
  ).reduce((sum, coin) => sum + coin.value, 0);
10710
- const available = unlockedVtxos.filter(
10711
- (coin) => (coin.virtualStatus.state === "settled" || coin.virtualStatus.state === "preconfirmed") && !isPendingRecovery(coin)
10724
+ const preconfirmed = vtxos.filter(
10725
+ (coin) => canSpendOffchain(coin, now) && coin.isPreconfirmed && !isPendingRecovery(coin)
10712
10726
  ).reduce((sum, coin) => sum + coin.value, 0);
10713
- recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
10727
+ const available = unlockedVtxos.filter((coin) => canSpendOffchain(coin, now) && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
10728
+ recoverable = vtxos.filter((coin) => canRecoverOnchain(coin, now) && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
10714
10729
  pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
10715
10730
  const totalBoarding = confirmed + unconfirmed;
10716
10731
  const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
10717
10732
  const assetBalances = /* @__PURE__ */ new Map();
10718
10733
  for (const vtxo of vtxos) {
10719
- if (!isSpendable(vtxo)) continue;
10734
+ if (hasTerminalSpend(vtxo)) continue;
10720
10735
  if (vtxo.assets) {
10721
10736
  for (const a of vtxo.assets) {
10722
10737
  const current = assetBalances.get(a.assetId) ?? 0n;
@@ -10752,12 +10767,13 @@ var ReadonlyWallet = class _ReadonlyWallet {
10752
10767
  const f = filter ?? { withRecoverable: true, withUnrolled: false };
10753
10768
  const contractManager = await this.getContractManager();
10754
10769
  const vtxos = await contractManager.getContractsWithVtxos();
10770
+ const now = { timestamp: /* @__PURE__ */ new Date() };
10755
10771
  return vtxos.flatMap((_) => _.vtxos).filter((vtxo) => {
10756
10772
  if (this._pendingSpendOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
10757
10773
  return false;
10758
10774
  }
10759
- if (isSpendable(vtxo)) {
10760
- if (!f.withRecoverable && (isRecoverable(vtxo) || isExpired(vtxo))) {
10775
+ if (!hasTerminalSpend(vtxo)) {
10776
+ if (!f.withRecoverable && canRecoverOnchain(vtxo, now)) {
10761
10777
  return false;
10762
10778
  }
10763
10779
  return true;
@@ -10790,7 +10806,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
10790
10806
  const response = await contractManager.getContractsWithVtxos();
10791
10807
  const allVtxos = response.flatMap((_) => _.vtxos);
10792
10808
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
10793
- const getTxCreatedAt = (txid) => this.indexerProvider.getVtxos({ outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime()).catch((err) => {
10809
+ const getTxCreatedAt = (txid) => getNormalizedVtxos(this.indexerProvider, { outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime()).catch((err) => {
10794
10810
  if (isRetryableProviderError(err)) return void 0;
10795
10811
  throw err;
10796
10812
  });
@@ -10866,6 +10882,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
10866
10882
  if (spent && commitmentTxid) {
10867
10883
  commitmentsToIgnore.add(commitmentTxid);
10868
10884
  }
10885
+ const boardingFacts = {
10886
+ isSpent: spent,
10887
+ isSwept: false,
10888
+ isPreconfirmed: false,
10889
+ commitmentTxIds: spent && commitmentTxid ? [commitmentTxid] : []
10890
+ };
10869
10891
  utxos.push({
10870
10892
  txid: tx.txid,
10871
10893
  vout: i,
@@ -10875,10 +10897,9 @@ var ReadonlyWallet = class _ReadonlyWallet {
10875
10897
  block_time: tx.status.block_time
10876
10898
  },
10877
10899
  isUnrolled: true,
10878
- virtualStatus: {
10879
- state: spent ? "spent" : "settled",
10880
- commitmentTxIds: spent && commitmentTxid ? [commitmentTxid] : void 0
10881
- },
10900
+ ...boardingFacts,
10901
+ virtualStatus: toVirtualStatus(boardingFacts),
10902
+ spentBy: "",
10882
10903
  createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
10883
10904
  script: scriptHex
10884
10905
  });
@@ -10892,12 +10913,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
10892
10913
  const tx = {
10893
10914
  key: {
10894
10915
  boardingTxid: utxo.txid,
10895
- commitmentTxid: utxo.virtualStatus.commitmentTxIds?.[0] ?? "",
10916
+ commitmentTxid: utxo.commitmentTxIds[0] ?? "",
10896
10917
  arkTxid: ""
10897
10918
  },
10898
10919
  amount: utxo.value,
10899
10920
  type: "RECEIVED" /* TxReceived */,
10900
- settled: utxo.virtualStatus.state === "spent",
10921
+ settled: utxo.isSpent,
10901
10922
  createdAt: utxo.status.block_time ? new Date(utxo.status.block_time * 1e3).getTime() : 0
10902
10923
  };
10903
10924
  if (!utxo.status.block_time) {
@@ -11121,11 +11142,11 @@ var ReadonlyWallet = class _ReadonlyWallet {
11121
11142
  /** Fetch Arkade transaction ids that are still pending final settlement. */
11122
11143
  async fetchPendingTxs() {
11123
11144
  const scripts = await this.getWalletScripts();
11124
- let { vtxos } = await this.indexerProvider.getVtxos({
11145
+ let { vtxos } = await getNormalizedVtxos(this.indexerProvider, {
11125
11146
  scripts
11126
11147
  });
11127
11148
  return vtxos.filter(
11128
- (vtxo) => vtxo.virtualStatus.state !== "swept" && vtxo.virtualStatus.state !== "settled" && vtxo.arkTxId !== void 0
11149
+ (vtxo) => (vtxo.isSpent || vtxo.isPreconfirmed && !vtxo.isSwept) && vtxo.arkTxId !== void 0
11129
11150
  ).map((_) => _.arkTxId);
11130
11151
  }
11131
11152
  // ========================================================================
@@ -11224,7 +11245,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
11224
11245
  });
11225
11246
  onVtxosPersisted = async (_contract, vtxos) => {
11226
11247
  for (const v of vtxos) {
11227
- if (v.virtualStatus.state === "spent") continue;
11248
+ if (v.isSpent) continue;
11228
11249
  await captureExitBranch({
11229
11250
  resolver,
11230
11251
  repository: virtualTxRepository,
@@ -11701,14 +11722,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11701
11722
  _restoreInFlight;
11702
11723
  _addPendingSpends(inputs) {
11703
11724
  for (const input of inputs) {
11704
- if ("virtualStatus" in input) {
11725
+ if (isVirtualCoin(input)) {
11705
11726
  this._pendingSpendOutpoints.add(`${input.txid}:${input.vout}`);
11706
11727
  }
11707
11728
  }
11708
11729
  }
11709
11730
  _removePendingSpends(inputs) {
11710
11731
  for (const input of inputs) {
11711
- if ("virtualStatus" in input) {
11732
+ if (isVirtualCoin(input)) {
11712
11733
  this._pendingSpendOutpoints.delete(`${input.txid}:${input.vout}`);
11713
11734
  }
11714
11735
  }
@@ -11796,15 +11817,22 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11796
11817
  materialize,
11797
11818
  deps
11798
11819
  });
11799
- if (hd && result.lastIndexUsed >= 0) {
11800
- await provider.advanceLastIndexUsed(result.lastIndexUsed);
11820
+ if (hd && result.highestConfirmedUsedIndex >= 0) {
11821
+ await provider.advanceLastIndexUsed(result.highestConfirmedUsedIndex);
11801
11822
  }
11802
11823
  await manager.refreshVtxos({ includeInactive: true });
11824
+ const causes = result.handlerErrors.map(
11825
+ (e) => e.error instanceof Error ? e.error : new Error(String(e.error))
11826
+ );
11827
+ if (result.truncatedAt !== void 0) {
11828
+ throw new AggregateError(
11829
+ causes,
11830
+ `restore: scan truncated at index ${result.truncatedAt}; indices >= ${result.truncatedAt} are unverified (${result.handlerErrors.length} discovery handler failure(s)). Retry is safe (idempotent).`
11831
+ );
11832
+ }
11803
11833
  if (result.handlerErrors.length > 0) {
11804
11834
  throw new AggregateError(
11805
- result.handlerErrors.map(
11806
- (e) => e.error instanceof Error ? e.error : new Error(String(e.error))
11807
- ),
11835
+ causes,
11808
11836
  `restore: ${result.handlerErrors.length} discovery handler(s) failed; the gap window may have closed early \u2014 retry is safe (idempotent).`
11809
11837
  );
11810
11838
  }
@@ -12116,13 +12144,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12116
12144
  if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
12117
12145
  break;
12118
12146
  }
12119
- const inputFee = estimator.evalOffchainInput({
12120
- amount: BigInt(vtxo.value),
12121
- type: vtxo.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
12122
- weight: 0,
12123
- birth: vtxo.createdAt,
12124
- expiry: vtxo.virtualStatus.batchExpiry ? new Date(vtxo.virtualStatus.batchExpiry) : void 0
12125
- });
12147
+ const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
12126
12148
  if (inputFee.satoshis >= vtxo.value) {
12127
12149
  continue;
12128
12150
  }
@@ -12338,9 +12360,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12338
12360
  * renewal / offboard) is not a board and leaves the boarding address
12339
12361
  * untouched.
12340
12362
  *
12341
- * Boarding inputs are the non-VTXO coins (no `virtualStatus`), the same
12363
+ * Boarding inputs are the non-VTXO coins (no `script`), the same
12342
12364
  * discriminator {@link handleSettlementFinalizationEvent} uses; the
12343
- * `typeof` guard skips arknote string inputs before the `in` test.
12365
+ * `typeof` guard skips the arknote strings settle also accepts.
12344
12366
  *
12345
12367
  * No-ops for static / `auto` wallets (no descriptor provider — boarding
12346
12368
  * stays on its fixed index-0 address). Best-effort and non-fatal: the
@@ -12353,7 +12375,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12353
12375
  async maybeRotateBoardingAfterBoard(inputs) {
12354
12376
  if (!this._descriptorProvider) return;
12355
12377
  const consumedBoarding = inputs.some(
12356
- (input) => typeof input !== "string" && !("virtualStatus" in input)
12378
+ (input) => typeof input !== "string" && !isVirtualCoin(input)
12357
12379
  );
12358
12380
  if (!consumedBoarding) return;
12359
12381
  try {
@@ -12364,13 +12386,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12364
12386
  }
12365
12387
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
12366
12388
  const signedForfeits = [];
12367
- const isVtxo = (input) => "virtualStatus" in input;
12368
12389
  let settlementPsbt = Transaction$2.fromPSBT(base64.decode(event.commitmentTx));
12369
12390
  let hasBoardingUtxos = false;
12370
12391
  let connectorIndex = 0;
12371
12392
  const connectorsLeaves = connectorsGraph?.leaves() || [];
12372
12393
  for (const input of inputs) {
12373
- if (!isVtxo(input)) {
12394
+ if (!isVirtualCoin(input)) {
12374
12395
  for (let i = 0; i < settlementPsbt.inputsLength; i++) {
12375
12396
  const settlementInput = settlementPsbt.getInput(i);
12376
12397
  if (!settlementInput.txid || settlementInput.index === void 0) {
@@ -12401,7 +12422,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12401
12422
  }
12402
12423
  continue;
12403
12424
  }
12404
- if (isRecoverable(input) || isSubdust({ value: input.value }, this.dustAmount)) {
12425
+ if (canRecoverOnchain(input, { timestamp: /* @__PURE__ */ new Date() }) || isSubdust({ value: input.value }, this.dustAmount)) {
12405
12426
  continue;
12406
12427
  }
12407
12428
  if (connectorsLeaves.length === 0) {
@@ -12708,13 +12729,13 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12708
12729
  const scriptMap = await this.getScriptMap();
12709
12730
  const allExtended = [];
12710
12731
  const allScripts = [...scriptMap.keys()];
12711
- const { vtxos: fetchedVtxos } = await this.indexerProvider.getVtxos({
12732
+ const { vtxos: fetchedVtxos } = await getNormalizedVtxos(this.indexerProvider, {
12712
12733
  scripts: allScripts
12713
12734
  });
12714
12735
  for (const vtxo of fetchedVtxos) {
12715
12736
  const vtxoScript = scriptMap.get(vtxo.script);
12716
12737
  if (!vtxoScript) continue;
12717
- if (vtxo.virtualStatus.state === "swept" || vtxo.virtualStatus.state === "settled") {
12738
+ if (!vtxo.isSpent && !(vtxo.isPreconfirmed && !vtxo.isSwept)) {
12718
12739
  continue;
12719
12740
  }
12720
12741
  allExtended.push({
@@ -12814,14 +12835,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12814
12835
  }));
12815
12836
  }
12816
12837
  /**
12817
- * Create an ArkCash bearer instrument.
12838
+ * Create an ArkadeCash bearer instrument.
12818
12839
  *
12819
12840
  * Generates a fresh keypair, sends the specified amount to a DefaultVtxo
12820
- * controlled by the new key, and returns the encoded arkcash string.
12841
+ * controlled by the new key, and returns the encoded arkadeCash string.
12821
12842
  * The receiver can claim the funds using `claimCash()` without ever
12822
12843
  * sharing their address.
12823
12844
  *
12824
- * ArkCash is a short-lived instrument: it carries a private key, so it is
12845
+ * ArkadeCash is a short-lived instrument: it carries a private key, so it is
12825
12846
  * meant to be handed over and claimed promptly, not held. A note left
12826
12847
  * unclaimed past its batch expiry is swept by the server and `claimCash`
12827
12848
  * can only report it, not move it.
@@ -12829,16 +12850,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12829
12850
  * @param amount - Amount in satoshis to send. Must be a whole number of
12830
12851
  * sats at or above the dust threshold — a below-dust amount would mint an
12831
12852
  * OP_RETURN output that is unspendable as cash.
12832
- * @returns The encoded arkcash string (e.g., "arkcash1...")
12853
+ * @returns The encoded arkadeCash string (e.g., "arkadecash1...")
12833
12854
  */
12834
12855
  async createCash(amount) {
12835
12856
  if (!Number.isSafeInteger(amount) || amount < Number(this.dustAmount)) {
12836
12857
  throw new Error(
12837
- `Invalid ArkCash amount ${amount}: must be a whole number of sats >= dust (${this.dustAmount})`
12858
+ `Invalid ArkadeCash amount ${amount}: must be a whole number of sats >= dust (${this.dustAmount})`
12838
12859
  );
12839
12860
  }
12840
- const cashHrp = this.network.hrp.replace(/ark$/, "arkcash");
12841
- const cash = ArkCash.generate(
12861
+ const cashHrp = this.network.hrp.replace(/ark$/, "arkadecash");
12862
+ const cash = ArkadeCash.generate(
12842
12863
  this.arkServerPublicKey,
12843
12864
  this.offchainTapscript.options.csvTimelock,
12844
12865
  cashHrp
@@ -12848,37 +12869,37 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12848
12869
  try {
12849
12870
  await this.send({ address, amount });
12850
12871
  } catch (error) {
12851
- throw new ArkCashCreateError(cashStr, error);
12872
+ throw new ArkadeCashCreateError(cashStr, error);
12852
12873
  }
12853
12874
  return cashStr;
12854
12875
  }
12855
12876
  /**
12856
- * Claim an ArkCash bearer instrument: sweep what can be swept, report the
12877
+ * Claim an ArkadeCash bearer instrument: sweep what can be swept, report the
12857
12878
  * rest.
12858
12879
  *
12859
- * Every spendable VTXO at the arkcash address is swept to this wallet in
12860
- * its own offchain transaction, signed with the key carried by the arkcash
12880
+ * Every spendable VTXO at the arkadeCash address is swept to this wallet in
12881
+ * its own offchain transaction, signed with the key carried by the arkadeCash
12861
12882
  * string. Nothing is ever persisted: no contract is imported, and the
12862
- * arkcash key is not stored. Anything that cannot be swept — server-swept,
12883
+ * arkadeCash key is not stored. Anything that cannot be swept — server-swept,
12863
12884
  * subdust, already claimed, or asset-bearing — is returned in `unclaimed`
12864
12885
  * with a per-VTXO reason and otherwise ignored.
12865
12886
  *
12866
- * The arkcash string is the recovery token: a claim interrupted between
12887
+ * The arkadeCash string is the recovery token: a claim interrupted between
12867
12888
  * submit and finalize is completed by simply re-running `claimCash`, which
12868
- * drains any pending arkcash transaction on the server before sweeping.
12889
+ * drains any pending arkadeCash transaction on the server before sweeping.
12869
12890
  *
12870
- * @param cashStr - The encoded arkcash string (e.g., "arkcash1...")
12891
+ * @param cashStr - The encoded arkadeCash string (e.g., "arkadecash1...")
12871
12892
  * @returns The swept total and the report of what was left behind. The
12872
12893
  * shape is open: further buckets may be added alongside `unclaimed`.
12873
12894
  */
12874
12895
  async claimCash(cashStr) {
12875
- const cash = ArkCash.fromString(cashStr);
12896
+ const cash = ArkadeCash.fromString(cashStr);
12876
12897
  const cashScript = cash.vtxoScript;
12877
12898
  const cashPkScript = hex.encode(cashScript.pkScript);
12878
12899
  const scripts = [cashPkScript];
12879
12900
  let vtxos = await this.fetchAllVtxos(scripts);
12880
12901
  if (vtxos.length === 0) {
12881
- throw new Error("No VTXOs found for this arkcash");
12902
+ throw new Error("No VTXOs found for this arkadeCash");
12882
12903
  }
12883
12904
  const myPkScript = ArkAddress.decode(await this.getAddress()).pkScript;
12884
12905
  let { spendable, unclaimed } = this.classifyCashVtxos(vtxos);
@@ -12894,7 +12915,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12894
12915
  myPkScript
12895
12916
  );
12896
12917
  } catch (error) {
12897
- console.error("Failed to drain pending arkcash txs:", error);
12918
+ console.error("Failed to drain pending arkadeCash txs:", error);
12898
12919
  }
12899
12920
  if (drained.count > 0) {
12900
12921
  vtxos = await this.fetchAllVtxos(scripts);
@@ -12929,7 +12950,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12929
12950
  });
12930
12951
  swept += vtxo.value;
12931
12952
  } catch (error) {
12932
- console.error(`Failed to sweep arkcash VTXO ${vtxo.txid}:${vtxo.vout}:`, error);
12953
+ console.error(
12954
+ `Failed to sweep arkadeCash VTXO ${vtxo.txid}:${vtxo.vout}:`,
12955
+ error
12956
+ );
12933
12957
  unclaimed.push(cashReport(vtxo, "sweep-failed"));
12934
12958
  }
12935
12959
  }
@@ -12943,18 +12967,18 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12943
12967
  };
12944
12968
  }
12945
12969
  /**
12946
- * Split the VTXOs at an arkcash address into the ones the thin sweep can
12970
+ * Split the VTXOs at an arkadeCash address into the ones the thin sweep can
12947
12971
  * move and the ones it can only report.
12948
12972
  */
12949
12973
  classifyCashVtxos(vtxos) {
12950
12974
  const spendable = [];
12951
12975
  const unclaimed = [];
12952
12976
  for (const vtxo of vtxos) {
12953
- if (!isSpendable(vtxo)) {
12977
+ if (hasTerminalSpend(vtxo)) {
12954
12978
  unclaimed.push(cashReport(vtxo, "already-spent"));
12955
12979
  } else if (isSubdust(vtxo, this.dustAmount)) {
12956
12980
  unclaimed.push(cashReport(vtxo, "subdust"));
12957
- } else if (isRecoverable(vtxo)) {
12981
+ } else if (vtxo.isSwept) {
12958
12982
  unclaimed.push(cashReport(vtxo, "swept"));
12959
12983
  } else if (vtxo.assets && vtxo.assets.length > 0) {
12960
12984
  unclaimed.push(cashReport(vtxo, "has-assets"));
@@ -12970,7 +12994,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12970
12994
  const all = [];
12971
12995
  const seen = /* @__PURE__ */ new Set();
12972
12996
  for (let pageIndex = 0; ; pageIndex++) {
12973
- const { vtxos, page } = await this.indexerProvider.getVtxos({
12997
+ const { vtxos, page } = await getNormalizedVtxos(this.indexerProvider, {
12974
12998
  scripts,
12975
12999
  pageIndex,
12976
13000
  pageSize
@@ -12986,7 +13010,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12986
13010
  }
12987
13011
  /**
12988
13012
  * Complete any sweep a previous `claimCash` registered but never finalized.
12989
- * A thin variant of {@link finalizePendingTxs}, scoped to the arkcash key:
13013
+ * A thin variant of {@link finalizePendingTxs}, scoped to the arkadeCash key:
12990
13014
  * the intent is signed with it, and so are the returned checkpoints.
12991
13015
  *
12992
13016
  * A drained sweep is not automatically *ours*. Whoever registered it chose
@@ -12995,7 +13019,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
12995
13019
  * completing it unsticks the funds), but only the value it actually pays to
12996
13020
  * `myPkScript` may be counted as claimed here.
12997
13021
  *
12998
- * @param vtxos - VTXOs at the arkcash contract's own pkScript, spent ones
13022
+ * @param vtxos - VTXOs at the arkadeCash contract's own pkScript, spent ones
12999
13023
  * included (a registered sweep marks its input spent). Any other outpoint —
13000
13024
  * subdust above all — would make the proof invalid, since it describes every
13001
13025
  * input by that pkScript.
@@ -13056,7 +13080,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13056
13080
  }
13057
13081
  }
13058
13082
  } catch (error) {
13059
- console.error(`Failed to finalize pending arkcash tx ${pendingTx.arkTxid}:`, error);
13083
+ console.error(
13084
+ `Failed to finalize pending arkadeCash tx ${pendingTx.arkTxid}:`,
13085
+ error
13086
+ );
13060
13087
  }
13061
13088
  }
13062
13089
  return result;
@@ -13300,7 +13327,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13300
13327
  * there is no separate change output. It records no `TxSent` history — the
13301
13328
  * funds never leave the wallet.
13302
13329
  */
13303
- async sendSelectedVtxosToSelf(inputs) {
13330
+ async sendSelectedVtxosToSelf(inputs, now) {
13304
13331
  if (inputs.length === 0) {
13305
13332
  throw new Error("sendSelectedVtxosToSelf: no inputs");
13306
13333
  }
@@ -13309,26 +13336,28 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13309
13336
  const serverPubKey = this.arkServerPublicKey;
13310
13337
  const serverUnrollScript = this.serverUnrollScript;
13311
13338
  const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
13312
- for (const input of inputs) {
13313
- if (!isSpendable(input) || isRecoverable(input)) {
13339
+ const normalizedInputs = inputs.map(normalizeVtxo);
13340
+ const at = now ?? await resolveTimeHeight(this.onchainProvider);
13341
+ for (const input of normalizedInputs) {
13342
+ if (!canSpendOffchain(input, at)) {
13314
13343
  throw new Error(
13315
13344
  `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} is not cooperatively spendable`
13316
13345
  );
13317
13346
  }
13318
- if (!input.virtualStatus.batchExpiry) {
13347
+ if (input.expiresAt === void 0 && input.expiresAtHeight === void 0) {
13319
13348
  throw new Error(
13320
- `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} has no batchExpiry`
13349
+ `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} has no batch expiry`
13321
13350
  );
13322
13351
  }
13323
13352
  }
13324
- const total = inputs.reduce((sum, c) => sum + BigInt(c.value), 0n);
13353
+ const total = normalizedInputs.reduce((sum, c) => sum + BigInt(c.value), 0n);
13325
13354
  const outputs = [
13326
13355
  {
13327
13356
  script: total < this.dustAmount ? arkAddress.subdustPkScript : arkAddress.pkScript,
13328
13357
  amount: total
13329
13358
  }
13330
13359
  ];
13331
- const assetInputs = selectedCoinsToAssetInputs(inputs);
13360
+ const assetInputs = selectedCoinsToAssetInputs(normalizedInputs);
13332
13361
  let selfAssets;
13333
13362
  if (assetInputs.size > 0) {
13334
13363
  const totals = /* @__PURE__ */ new Map();
@@ -13345,7 +13374,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13345
13374
  const packet = createAssetPacket(assetInputs, [], selfReceiver);
13346
13375
  outputs.push(Extension.create([packet]).txOut());
13347
13376
  }
13348
- return this._submitOffchainSpend(inputs, outputs, {
13377
+ return this._submitOffchainSpend(normalizedInputs, outputs, {
13349
13378
  sentAmount: 0,
13350
13379
  changeAmount: total,
13351
13380
  changeVout: 0,
@@ -13446,43 +13475,42 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13446
13475
  const cm = await this.getContractManager();
13447
13476
  const annotatedInputs = await cm.annotateVtxos(inputs);
13448
13477
  for (const [inputIndex, vtxo] of annotatedInputs.entries()) {
13478
+ const spentFacts = { ...vtxo, isSpent: true };
13449
13479
  if (inputIndex < safeLength && signedCheckpointTxs[inputIndex]) {
13450
13480
  const checkpoint = Transaction$2.fromPSBT(
13451
13481
  base64.decode(signedCheckpointTxs[inputIndex])
13452
13482
  );
13453
13483
  spentVtxos.push({
13454
- ...vtxo,
13455
- virtualStatus: {
13456
- ...vtxo.virtualStatus,
13457
- state: "spent"
13458
- },
13484
+ ...spentFacts,
13485
+ virtualStatus: toVirtualStatus(spentFacts),
13459
13486
  spentBy: checkpoint.id,
13460
- arkTxId: arkTxid,
13461
- isSpent: true
13487
+ arkTxId: arkTxid
13462
13488
  });
13463
13489
  } else {
13464
13490
  spentVtxos.push({
13465
- ...vtxo,
13466
- virtualStatus: {
13467
- ...vtxo.virtualStatus,
13468
- state: "spent"
13469
- },
13470
- arkTxId: arkTxid,
13471
- isSpent: true
13491
+ ...spentFacts,
13492
+ virtualStatus: toVirtualStatus(spentFacts),
13493
+ arkTxId: arkTxid
13472
13494
  });
13473
13495
  }
13474
- if (vtxo.virtualStatus.commitmentTxIds) {
13475
- for (const id of vtxo.virtualStatus.commitmentTxIds) {
13476
- commitmentTxIds.add(id);
13477
- }
13496
+ for (const id of vtxo.commitmentTxIds) {
13497
+ commitmentTxIds.add(id);
13478
13498
  }
13479
- if (vtxo.virtualStatus.batchExpiry) {
13480
- batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
13499
+ const vtxoExpiry = toBatchExpiry(vtxo);
13500
+ if (vtxoExpiry) {
13501
+ batchExpiry = Math.min(batchExpiry, vtxoExpiry);
13481
13502
  }
13482
13503
  }
13483
13504
  const createdAt = Date.now();
13484
13505
  let changeVtxo;
13485
13506
  if (changeAmount > 0n && batchExpiry !== Number.MAX_SAFE_INTEGER) {
13507
+ const changeFacts = {
13508
+ isSpent: false,
13509
+ isSwept: false,
13510
+ isPreconfirmed: true,
13511
+ commitmentTxIds: Array.from(commitmentTxIds),
13512
+ ...parseLegacyExpiry(batchExpiry)
13513
+ };
13486
13514
  changeVtxo = {
13487
13515
  txid: arkTxid,
13488
13516
  vout: changeVout,
@@ -13490,14 +13518,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13490
13518
  forfeitTapLeafScript: offchainTapscript.forfeit(),
13491
13519
  intentTapLeafScript: offchainTapscript.forfeit(),
13492
13520
  isUnrolled: false,
13493
- isSpent: false,
13494
13521
  tapTree: offchainTapscript.encode(),
13495
13522
  value: Number(changeAmount),
13496
- virtualStatus: {
13497
- state: "preconfirmed",
13498
- commitmentTxIds: Array.from(commitmentTxIds),
13499
- batchExpiry
13500
- },
13523
+ ...changeFacts,
13524
+ virtualStatus: toVirtualStatus(changeFacts),
13525
+ spentBy: "",
13501
13526
  status: {
13502
13527
  confirmed: false
13503
13528
  },
@@ -13565,25 +13590,25 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13565
13590
  const spentVtxos = [];
13566
13591
  const inputArkTxIds = /* @__PURE__ */ new Set();
13567
13592
  const boardingRemovalsByAddress = /* @__PURE__ */ new Map();
13568
- const isVtxo = (input) => "virtualStatus" in input;
13569
- const vtxoInputs = inputs.filter(isVtxo);
13593
+ const vtxoInputs = inputs.filter(isVirtualCoin);
13570
13594
  const cm = await this.getContractManager();
13571
13595
  const annotatedVtxos = await cm.annotateVtxos(vtxoInputs);
13572
13596
  const annotatedByKey = new Map(annotatedVtxos.map((v) => [`${v.txid}:${v.vout}`, v]));
13573
13597
  for (const input of inputs) {
13574
- if (isVtxo(input)) {
13575
- const vtxo = annotatedByKey.get(`${input.txid}:${input.vout}`);
13598
+ if (isVirtualCoin(input)) {
13599
+ const outpoint = `${input.txid}:${input.vout}`;
13600
+ const vtxo = annotatedByKey.get(outpoint);
13601
+ if (!vtxo) {
13602
+ throw new Error(`missing annotation for virtual coin ${outpoint}`);
13603
+ }
13576
13604
  if (vtxo.arkTxId) {
13577
13605
  inputArkTxIds.add(vtxo.arkTxId);
13578
13606
  }
13607
+ const settledFacts = { ...vtxo, isSpent: true };
13579
13608
  spentVtxos.push({
13580
- ...vtxo,
13581
- virtualStatus: {
13582
- ...vtxo.virtualStatus,
13583
- state: "settled"
13584
- },
13585
- settledBy: commitmentTxid,
13586
- isSpent: true
13609
+ ...settledFacts,
13610
+ virtualStatus: toVirtualStatus(settledFacts),
13611
+ settledBy: commitmentTxid
13587
13612
  });
13588
13613
  } else {
13589
13614
  let sourceAddress;
@@ -13646,9 +13671,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13646
13671
  }
13647
13672
  };
13648
13673
  function selectVirtualCoins(coins, targetAmount) {
13649
- const sortedCoins = [...coins].sort((a, b) => {
13650
- const expiryA = a.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
13651
- const expiryB = b.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
13674
+ const sortedCoins = coins.map(normalizeVtxo).sort((a, b) => {
13675
+ const expiryA = toBatchExpiry(a) || Number.MAX_SAFE_INTEGER;
13676
+ const expiryB = toBatchExpiry(b) || Number.MAX_SAFE_INTEGER;
13652
13677
  if (expiryA !== expiryB) {
13653
13678
  return expiryA - expiryB;
13654
13679
  }
@@ -14342,13 +14367,7 @@ var Ramps = class {
14342
14367
  const filteredVtxos = [];
14343
14368
  let totalAmount = 0n;
14344
14369
  for (const vtxo of vtxos) {
14345
- const inputFee = estimator.evalOffchainInput({
14346
- amount: BigInt(vtxo.value),
14347
- type: vtxo.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
14348
- weight: 0,
14349
- birth: vtxo.createdAt,
14350
- expiry: vtxo.virtualStatus.batchExpiry ? new Date(vtxo.virtualStatus.batchExpiry) : void 0
14351
- });
14370
+ const inputFee = estimator.evalOffchainInput(toOffchainInputFeeParams(vtxo));
14352
14371
  if (inputFee.satoshis >= vtxo.value) {
14353
14372
  continue;
14354
14373
  }
@@ -15083,30 +15102,30 @@ var WalletMessageHandler = class {
15083
15102
  unconfirmed += utxo.value;
15084
15103
  }
15085
15104
  }
15086
- const spendableVtxos = allVtxos.filter(isSpendable);
15087
- const sweptVtxos = allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept");
15105
+ const now = { timestamp: /* @__PURE__ */ new Date() };
15088
15106
  let settled = 0;
15089
15107
  let preconfirmed = 0;
15090
15108
  let recoverable = 0;
15091
15109
  let pendingRecovery = 0;
15092
- for (const vtxo of spendableVtxos) {
15110
+ for (const vtxo of allVtxos) {
15111
+ if (hasTerminalSpend(vtxo)) continue;
15093
15112
  if (pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
15094
15113
  pendingRecovery += vtxo.value;
15095
- } else if (vtxo.virtualStatus.state === "settled") {
15096
- settled += vtxo.value;
15097
- } else if (vtxo.virtualStatus.state === "preconfirmed") {
15098
- preconfirmed += vtxo.value;
15099
- }
15100
- }
15101
- for (const vtxo of sweptVtxos) {
15102
- if (isSpendable(vtxo)) {
15114
+ } else if (canRecoverOnchain(vtxo, now)) {
15103
15115
  recoverable += vtxo.value;
15116
+ } else if (canSpendOffchain(vtxo, now)) {
15117
+ if (vtxo.isPreconfirmed) {
15118
+ preconfirmed += vtxo.value;
15119
+ } else {
15120
+ settled += vtxo.value;
15121
+ }
15104
15122
  }
15105
15123
  }
15106
15124
  const totalBoarding = confirmed + unconfirmed;
15107
15125
  const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
15108
15126
  const assetBalances = /* @__PURE__ */ new Map();
15109
- for (const vtxo of spendableVtxos) {
15127
+ for (const vtxo of allVtxos) {
15128
+ if (hasTerminalSpend(vtxo)) continue;
15110
15129
  if (vtxo.assets) {
15111
15130
  for (const a of vtxo.assets) {
15112
15131
  const current = assetBalances.get(a.assetId) ?? 0n;
@@ -15142,7 +15161,7 @@ var WalletMessageHandler = class {
15142
15161
  */
15143
15162
  async getSpendableVtxos() {
15144
15163
  const vtxos = await this.getVtxosFromRepo();
15145
- return vtxos.filter(isSpendable);
15164
+ return vtxos.filter((v) => !hasTerminalSpend(v));
15146
15165
  }
15147
15166
  async onWalletInitialized() {
15148
15167
  if (!this.readonlyWallet || !this.arkProvider || !this.indexerProvider || !this.walletRepository) {
@@ -15154,9 +15173,7 @@ var WalletMessageHandler = class {
15154
15173
  try {
15155
15174
  const vtxos = await this.getVtxosFromRepo();
15156
15175
  const { pending, finalized } = await this.wallet.finalizePendingTxs(
15157
- vtxos.filter(
15158
- (vtxo) => vtxo.virtualStatus.state !== "swept" && vtxo.virtualStatus.state !== "settled"
15159
- )
15176
+ vtxos.filter((vtxo) => vtxo.isSpent || vtxo.isPreconfirmed && !vtxo.isSwept)
15160
15177
  );
15161
15178
  console.info(
15162
15179
  `Recovered ${finalized.length}/${pending.length} pending transactions: ${finalized.join(", ")}`
@@ -15415,8 +15432,12 @@ var WalletMessageHandler = class {
15415
15432
  `WalletMessageHandler.getVtxosFromRepo: failed to derive script from wallet address ${walletAddress}: ${e instanceof Error ? e.message : String(e)}`
15416
15433
  );
15417
15434
  }
15418
- const walletVtxos = await this.walletRepository.getVtxos(walletAddress);
15419
- addVtxos(filterVtxosForScript(walletVtxos, walletScript));
15435
+ addVtxos(
15436
+ await getVtxosForContract(this.walletRepository, {
15437
+ script: walletScript,
15438
+ address: walletAddress
15439
+ })
15440
+ );
15420
15441
  return allVtxos;
15421
15442
  }
15422
15443
  /**
@@ -15448,7 +15469,7 @@ var WalletMessageHandler = class {
15448
15469
  }));
15449
15470
  const BATCH_SIZE = 100;
15450
15471
  for (let i = 0; i < outpoints.length; i += BATCH_SIZE) {
15451
- const res = await this.indexerProvider.getVtxos({
15472
+ const res = await getNormalizedVtxos(this.indexerProvider, {
15452
15473
  outpoints: outpoints.slice(i, i + BATCH_SIZE)
15453
15474
  });
15454
15475
  for (const v of res.vtxos) {
@@ -16150,7 +16171,7 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
16150
16171
  };
16151
16172
  try {
16152
16173
  const response = await this.sendMessage(message);
16153
- return response.payload.vtxos;
16174
+ return response.payload.vtxos.map(normalizeVtxo);
16154
16175
  } catch (error) {
16155
16176
  throw new Error(`Failed to get vtxos: ${error}`);
16156
16177
  }
@@ -16263,7 +16284,7 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
16263
16284
  };
16264
16285
  try {
16265
16286
  const response = await sendContractMessage(message);
16266
- return response.payload.vtxos;
16287
+ return response.payload.vtxos.map(normalizeVtxo);
16267
16288
  } catch (e) {
16268
16289
  throw new Error("Failed to annotate vtxos");
16269
16290
  }
@@ -16708,7 +16729,7 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
16708
16729
  };
16709
16730
  try {
16710
16731
  const response = await wallet.sendMessage(message);
16711
- return response.payload.vtxos;
16732
+ return response.payload.vtxos.map(normalizeVtxo);
16712
16733
  } catch (e) {
16713
16734
  throw new Error(`Failed to get expiring vtxos: ${e}`);
16714
16735
  }
@@ -18448,7 +18469,7 @@ async function resolveFeeRate(opts) {
18448
18469
  }
18449
18470
  async function selectExitVtxos(opts) {
18450
18471
  if (!opts.vtxos) return opts.wallet.getVtxos();
18451
- const res = await opts.wallet.indexerProvider.getVtxos({ outpoints: opts.vtxos });
18472
+ const res = await getNormalizedVtxos(opts.wallet.indexerProvider, { outpoints: opts.vtxos });
18452
18473
  const tapTrees = /* @__PURE__ */ new Map();
18453
18474
  const out = [];
18454
18475
  for (const vtxo of res.vtxos) {
@@ -19117,7 +19138,7 @@ __export(arkade_exports, {
19117
19138
  validateProgram: () => validateProgram,
19118
19139
  witnessRefToBytes: () => witnessRefToBytes
19119
19140
  });
19120
- var isVtxoCoin = (input) => "virtualStatus" in input;
19141
+ var isVtxoCoin = (input) => isVirtualCoin(input);
19121
19142
  function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message, session, arkProvider, emulator, network, recipients) {
19122
19143
  let batchId;
19123
19144
  let sweepTapTreeRoot;
@@ -19194,7 +19215,11 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
19194
19215
  break;
19195
19216
  }
19196
19217
  }
19197
- if (boardingIdx === null) continue;
19218
+ if (boardingIdx === null) {
19219
+ throw new Error(
19220
+ `input ${input.txid}:${input.vout} is neither a virtual output nor an input of the commitment tx; a virtual output must carry its \`script\``
19221
+ );
19222
+ }
19198
19223
  commitmentPsbt.updateInput(boardingIdx, {
19199
19224
  tapLeafScript: [input.forfeitTapLeafScript]
19200
19225
  });
@@ -19474,13 +19499,13 @@ var ArkadeContract = class _ArkadeContract {
19474
19499
  const [registered] = await manager.getContracts({ script: scriptHex });
19475
19500
  if (registered) {
19476
19501
  const [withVtxos] = await manager.getContractsWithVtxos({ script: scriptHex });
19477
- return (withVtxos?.vtxos ?? []).filter(isSpendable);
19502
+ return (withVtxos?.vtxos ?? []).filter((v) => !hasTerminalSpend(v));
19478
19503
  }
19479
19504
  }
19480
19505
  if (!this.client.indexer) {
19481
19506
  throw new Error("ArkadeContract.getUtxos: an indexer is required");
19482
19507
  }
19483
- const { vtxos } = await this.client.indexer.getVtxos({
19508
+ const { vtxos } = await getNormalizedVtxos(this.client.indexer, {
19484
19509
  scripts: [scriptHex],
19485
19510
  spendableOnly: true
19486
19511
  });
@@ -19788,6 +19813,6 @@ function isArkContract(str) {
19788
19813
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
19789
19814
  }
19790
19815
 
19791
- export { ARKADE_MAGIC, ActivityRegistry, ArkCash, ArkCashCreateError, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, arkade_exports, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
19792
- //# sourceMappingURL=chunk-SV4PCGKM.js.map
19793
- //# sourceMappingURL=chunk-SV4PCGKM.js.map
19816
+ export { ARKADE_MAGIC, ActivityRegistry, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, arkade_exports, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isRetryableProviderError, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
19817
+ //# sourceMappingURL=chunk-NYR5H6GR.js.map
19818
+ //# sourceMappingURL=chunk-NYR5H6GR.js.map