@arkade-os/sdk 0.4.71 → 0.4.73

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 (80) hide show
  1. package/README.md +29 -0
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/adapters/indexedDB.cjs +5 -5
  7. package/dist/adapters/indexedDB.js +4 -4
  8. package/dist/{asyncStorageTaskQueue-CjKAUiuj.d.ts → asyncStorageTaskQueue-C5IwA_Hs.d.ts} +1 -1
  9. package/dist/{asyncStorageTaskQueue-DgXf4iuU.d.cts → asyncStorageTaskQueue-DA2OdpJ0.d.cts} +1 -1
  10. package/dist/{chunk-P6EXRY5L.cjs → chunk-2D7F5IU3.cjs} +49 -49
  11. package/dist/{chunk-P6EXRY5L.cjs.map → chunk-2D7F5IU3.cjs.map} +1 -1
  12. package/dist/{chunk-NOWVA5CM.js → chunk-5YCAC6XE.js} +4 -4
  13. package/dist/{chunk-NOWVA5CM.js.map → chunk-5YCAC6XE.js.map} +1 -1
  14. package/dist/{chunk-MK5EPTTB.cjs → chunk-C4RKZY7D.cjs} +798 -513
  15. package/dist/chunk-C4RKZY7D.cjs.map +1 -0
  16. package/dist/{chunk-FM263GKH.cjs → chunk-D5DLGVN4.cjs} +51 -25
  17. package/dist/chunk-D5DLGVN4.cjs.map +1 -0
  18. package/dist/{chunk-ALZ2CZ3T.js → chunk-G5RPUZJE.js} +3 -3
  19. package/dist/{chunk-ALZ2CZ3T.js.map → chunk-G5RPUZJE.js.map} +1 -1
  20. package/dist/{chunk-5J5PDZRR.cjs → chunk-KJ5E646Y.cjs} +30 -4
  21. package/dist/chunk-KJ5E646Y.cjs.map +1 -0
  22. package/dist/{chunk-7PHMT3T4.cjs → chunk-L7IEXVXI.cjs} +10 -10
  23. package/dist/{chunk-7PHMT3T4.cjs.map → chunk-L7IEXVXI.cjs.map} +1 -1
  24. package/dist/{chunk-ZT3EM4CE.js → chunk-M6HIWM5N.js} +37 -11
  25. package/dist/chunk-M6HIWM5N.js.map +1 -0
  26. package/dist/{chunk-2ZX4Z2IP.js → chunk-OV4GLPZI.js} +30 -5
  27. package/dist/chunk-OV4GLPZI.js.map +1 -0
  28. package/dist/{chunk-SCSLCU66.js → chunk-QX2M67YI.js} +8 -4
  29. package/dist/chunk-QX2M67YI.js.map +1 -0
  30. package/dist/{chunk-E5JNYFOF.js → chunk-YHJJSLE5.js} +367 -83
  31. package/dist/chunk-YHJJSLE5.js.map +1 -0
  32. package/dist/{chunk-I7ZNWBBU.cjs → chunk-ZLDO4QDR.cjs} +21 -17
  33. package/dist/chunk-ZLDO4QDR.cjs.map +1 -0
  34. package/dist/contracts/handlers/index.cjs +9 -9
  35. package/dist/contracts/handlers/index.d.cts +3 -3
  36. package/dist/contracts/handlers/index.d.ts +3 -3
  37. package/dist/contracts/handlers/index.js +2 -2
  38. package/dist/{delegate-D-gEEqrP.d.cts → delegate-BJp8f6LT.d.cts} +1 -1
  39. package/dist/{delegate-CKJdsC99.d.ts → delegate-Ckz_TWzM.d.ts} +1 -1
  40. package/dist/{hdWalletCapable-CwLPmWct.d.cts → hdWalletCapable-DA9bJ5eB.d.cts} +2 -2
  41. package/dist/{hdWalletCapable-BJ4pvkai.d.ts → hdWalletCapable-DABgAD-T.d.ts} +2 -2
  42. package/dist/{index-Boppyqav.d.ts → index-B8xrELXY.d.ts} +2 -2
  43. package/dist/{index-BE1uFXrV.d.cts → index-DSbDm0Io.d.cts} +2 -2
  44. package/dist/{index-CyGnhCSS.d.ts → index-Dg0-TvBQ.d.cts} +51 -3
  45. package/dist/{index-CyGnhCSS.d.cts → index-Dg0-TvBQ.d.ts} +51 -3
  46. package/dist/index.cjs +306 -298
  47. package/dist/index.d.cts +57 -10
  48. package/dist/index.d.ts +57 -10
  49. package/dist/index.js +4 -4
  50. package/dist/repositories/realm/index.cjs +19 -19
  51. package/dist/repositories/realm/index.d.cts +2 -2
  52. package/dist/repositories/realm/index.d.ts +2 -2
  53. package/dist/repositories/realm/index.js +5 -5
  54. package/dist/repositories/sqlite/index.cjs +18 -18
  55. package/dist/repositories/sqlite/index.d.cts +2 -2
  56. package/dist/repositories/sqlite/index.d.ts +2 -2
  57. package/dist/repositories/sqlite/index.js +5 -5
  58. package/dist/{taskRunner-CjmMH_rW.d.cts → taskRunner-C_uAJDsn.d.cts} +1 -1
  59. package/dist/{taskRunner-l3zyfW_Q.d.ts → taskRunner-DARTFnZN.d.ts} +1 -1
  60. package/dist/wallet/expo/background.cjs +14 -14
  61. package/dist/wallet/expo/background.d.cts +3 -3
  62. package/dist/wallet/expo/background.d.ts +3 -3
  63. package/dist/wallet/expo/background.js +6 -6
  64. package/dist/wallet/expo/index.cjs +14 -14
  65. package/dist/wallet/expo/index.d.cts +4 -4
  66. package/dist/wallet/expo/index.d.ts +4 -4
  67. package/dist/wallet/expo/index.js +5 -5
  68. package/dist/worker/expo/index.cjs +9 -9
  69. package/dist/worker/expo/index.d.cts +4 -4
  70. package/dist/worker/expo/index.d.ts +4 -4
  71. package/dist/worker/expo/index.js +5 -5
  72. package/package.json +1 -1
  73. package/dist/chunk-2ZX4Z2IP.js.map +0 -1
  74. package/dist/chunk-5J5PDZRR.cjs.map +0 -1
  75. package/dist/chunk-E5JNYFOF.js.map +0 -1
  76. package/dist/chunk-FM263GKH.cjs.map +0 -1
  77. package/dist/chunk-I7ZNWBBU.cjs.map +0 -1
  78. package/dist/chunk-MK5EPTTB.cjs.map +0 -1
  79. package/dist/chunk-SCSLCU66.js.map +0 -1
  80. package/dist/chunk-ZT3EM4CE.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-ZT3EM4CE.js';
2
- import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-ALZ2CZ3T.js';
3
- import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, getAllNormalizedVtxos, isPastExpiry, isRetryableProviderError, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, PrevoutTxField, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1, fetchVtxoCreatedAtByTxid } from './chunk-2ZX4Z2IP.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-M6HIWM5N.js';
2
+ import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-G5RPUZJE.js';
3
+ import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, getAllNormalizedVtxos, isPastExpiry, isRetryableProviderError, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, PrevoutTxField, fetchVtxoCreatedAtByTxid, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1 } from './chunk-OV4GLPZI.js';
4
4
  import { __export } from './chunk-NSBPE2FW.js';
5
5
  import { sha256 as sha256$1, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
6
6
  import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
@@ -2698,6 +2698,12 @@ var ErrOnchainOutputNotFound = (address) => new ServerResponseMismatchError(`onc
2698
2698
  var ErrUnvalidatedOffchainOutput = (address) => new ServerResponseMismatchError(
2699
2699
  `offchain output ${address} cannot be validated: virtual output tree signing did not run`
2700
2700
  );
2701
+ var ErrIntentOutputNotFound = (index, kind) => new ServerResponseMismatchError(
2702
+ `${kind} output ${index} of the intent proof is not present in what the server built`
2703
+ );
2704
+ var ErrUnvalidatedIntentOutput = (index) => new ServerResponseMismatchError(
2705
+ `offchain output ${index} of the intent proof cannot be validated: virtual output tree signing did not run`
2706
+ );
2701
2707
  var ErrInvalidOnchainOutputAmount = (address) => new Error(`invalid onchain output amount: ${address}`);
2702
2708
  var ErrInvalidOnchainOutputAssets = (address) => new Error(`onchain output ${address} cannot have assets`);
2703
2709
  var ErrInvalidOffchainOutputAmount = (address) => new Error(`invalid offchain output ${address}, missing amount`);
@@ -2734,6 +2740,74 @@ function validateBatchRecipientsWithoutTree(commitmentTx, recipients, network) {
2734
2740
  throw ErrUnvalidatedOffchainOutput(recipient.address);
2735
2741
  }
2736
2742
  }
2743
+ function declaredIntentOutputs(signedProof, onchainOutputIndexes) {
2744
+ const proof = Transaction.fromPSBT(base64.decode(signedProof));
2745
+ const onchain = new Set(onchainOutputIndexes);
2746
+ const declared = [];
2747
+ for (let i = 0; i < proof.outputsLength; i++) {
2748
+ const output = proof.getOutput(i);
2749
+ if (!output?.script || output.script.length === 0 || !output.amount) continue;
2750
+ declared.push({
2751
+ index: i,
2752
+ script: output.script,
2753
+ amount: output.amount,
2754
+ onchain: onchain.has(i)
2755
+ });
2756
+ }
2757
+ return declared;
2758
+ }
2759
+ function validateBatchAgainstIntent(commitmentTx, vtxoTreeLeaves, declared) {
2760
+ const usedOnchain = /* @__PURE__ */ new Set();
2761
+ const usedLeaf = /* @__PURE__ */ new Set();
2762
+ for (const output of declared) {
2763
+ if (output.onchain) {
2764
+ if (!takeOnchainOutput(commitmentTx, output, usedOnchain)) {
2765
+ throw ErrIntentOutputNotFound(output.index, "onchain");
2766
+ }
2767
+ continue;
2768
+ }
2769
+ if (!takeLeafOutput(vtxoTreeLeaves, output, usedLeaf)) {
2770
+ throw ErrIntentOutputNotFound(output.index, "offchain");
2771
+ }
2772
+ }
2773
+ }
2774
+ function validateBatchAgainstIntentWithoutTree(commitmentTx, declared) {
2775
+ const usedOnchain = /* @__PURE__ */ new Set();
2776
+ for (const output of declared) {
2777
+ if (!output.onchain) {
2778
+ throw ErrUnvalidatedIntentOutput(output.index);
2779
+ }
2780
+ if (!takeOnchainOutput(commitmentTx, output, usedOnchain)) {
2781
+ throw ErrIntentOutputNotFound(output.index, "onchain");
2782
+ }
2783
+ }
2784
+ }
2785
+ function takeOnchainOutput(commitmentTx, declared, used) {
2786
+ for (let i = 0; i < commitmentTx.outputsLength; i++) {
2787
+ if (used.has(i)) continue;
2788
+ const output = commitmentTx.getOutput(i);
2789
+ if (!output?.script || output.amount !== declared.amount) continue;
2790
+ if (!equalBytes(output.script, declared.script)) continue;
2791
+ used.add(i);
2792
+ return true;
2793
+ }
2794
+ return false;
2795
+ }
2796
+ function takeLeafOutput(leaves, declared, used) {
2797
+ for (let leafIdx = 0; leafIdx < leaves.length; leafIdx++) {
2798
+ const leaf = leaves[leafIdx];
2799
+ for (let i = 0; i < leaf.outputsLength; i++) {
2800
+ const key = `${leafIdx}:${i}`;
2801
+ if (used.has(key)) continue;
2802
+ const output = leaf.getOutput(i);
2803
+ if (!output?.script || output.amount !== declared.amount) continue;
2804
+ if (!equalBytes(output.script, declared.script)) continue;
2805
+ used.add(key);
2806
+ return true;
2807
+ }
2808
+ }
2809
+ return false;
2810
+ }
2737
2811
  function validateOnchainRecipient(commitmentTx, recipient, network, usedOutputs) {
2738
2812
  const addr = Address(network).decode(recipient.address);
2739
2813
  const expectedPkScript = OutScript.encode(addr);
@@ -2983,6 +3057,39 @@ function createDefaultActivityRegistry() {
2983
3057
  return registry;
2984
3058
  }
2985
3059
 
3060
+ // src/wallet/restoreHooks.ts
3061
+ var restoreHooks = /* @__PURE__ */ new WeakMap();
3062
+ function registerWalletRestoreHook(wallet, hook) {
3063
+ const hooks = restoreHooks.get(wallet) ?? [];
3064
+ const entry = { ...hook };
3065
+ const existing = hooks.findIndex(({ id }) => id === hook.id);
3066
+ if (existing === -1) hooks.push(entry);
3067
+ else hooks[existing] = entry;
3068
+ restoreHooks.set(wallet, hooks);
3069
+ return () => {
3070
+ const current = restoreHooks.get(wallet);
3071
+ if (!current) return;
3072
+ const index = current.indexOf(entry);
3073
+ if (index === -1) return;
3074
+ current.splice(index, 1);
3075
+ if (current.length === 0) restoreHooks.delete(wallet);
3076
+ };
3077
+ }
3078
+ async function runWalletRestoreHooks(wallet) {
3079
+ const snapshot = [...restoreHooks.get(wallet) ?? []];
3080
+ const errors = [];
3081
+ for (const hook of snapshot) {
3082
+ try {
3083
+ await hook.restore(wallet);
3084
+ } catch (error) {
3085
+ errors.push(error);
3086
+ }
3087
+ }
3088
+ if (errors.length > 0) {
3089
+ throw new AggregateError(errors, "Wallet restore hooks failed");
3090
+ }
3091
+ }
3092
+
2986
3093
  // src/wallet/index.ts
2987
3094
  var TxType = /* @__PURE__ */ ((TxType2) => {
2988
3095
  TxType2["TxSent"] = "SENT";
@@ -3463,21 +3570,38 @@ function assertSubmittedArkTxid(response, signedArkTx, context) {
3463
3570
  );
3464
3571
  }
3465
3572
  }
3466
- function assertServerSignedLeaf(serverTx, localTx, inputIndex, serverPubkeyHex, context) {
3573
+ function assertServerSignedLeaf(serverTx, localTx, inputIndex, serverPubkeys, context) {
3467
3574
  const leaf = localTx.getInput(inputIndex).tapLeafScript?.[0];
3468
3575
  if (!leaf) {
3469
3576
  throw new Error(
3470
3577
  `${context}: input ${inputIndex} carries no spend leaf to verify the server signature against`
3471
3578
  );
3472
3579
  }
3580
+ const script = scriptFromTapLeafScript(leaf);
3581
+ let leafPubkeys;
3582
+ try {
3583
+ leafPubkeys = decodeTapscript(script).params.pubkeys.map(
3584
+ (key) => hex.encode(toXOnly(key, "leaf key"))
3585
+ );
3586
+ } catch (error) {
3587
+ throw new ServerResponseMismatchError(
3588
+ `${context}: input ${inputIndex} spend leaf cannot be decoded (${error instanceof Error ? error.message : String(error)})`
3589
+ );
3590
+ }
3591
+ const signer = leafPubkeys.find((key) => serverPubkeys.has(key));
3592
+ if (!signer) {
3593
+ throw new ServerResponseMismatchError(
3594
+ `${context}: input ${inputIndex} spend leaf names no signer key the server advertises`
3595
+ );
3596
+ }
3473
3597
  try {
3474
3598
  verifyTapscriptSignatures(
3475
3599
  serverTx,
3476
3600
  inputIndex,
3477
- [serverPubkeyHex],
3601
+ [signer],
3478
3602
  void 0,
3479
3603
  void 0,
3480
- tapLeafHash(scriptFromTapLeafScript(leaf))
3604
+ tapLeafHash(script)
3481
3605
  );
3482
3606
  } catch (error) {
3483
3607
  throw new ServerResponseMismatchError(
@@ -3551,24 +3675,22 @@ async function submitOffchainTx(provider, offchainTx, signer, hooks, options) {
3551
3675
  );
3552
3676
  }
3553
3677
  const finalArkTx = Transaction.fromPSBT(base64.decode(response.finalArkTx));
3554
- const serverPubkeyHex = hex.encode(toXOnly(verify.serverPubkey, "server key"));
3678
+ const serverPubkeys = new Set(
3679
+ [verify.serverPubkey, ...verify.deprecatedServerPubkeys ?? []].map(
3680
+ (key) => hex.encode(toXOnly(key, "server key"))
3681
+ )
3682
+ );
3555
3683
  for (let i = 0; i < offchainTx.arkTx.inputsLength; i++) {
3556
3684
  assertServerSignedLeaf(
3557
3685
  finalArkTx,
3558
3686
  offchainTx.arkTx,
3559
3687
  i,
3560
- serverPubkeyHex,
3688
+ serverPubkeys,
3561
3689
  "submitTx ark tx"
3562
3690
  );
3563
3691
  }
3564
3692
  matched.forEach(
3565
- ({ server, local }, index) => assertServerSignedLeaf(
3566
- server,
3567
- local,
3568
- 0,
3569
- serverPubkeyHex,
3570
- `submitTx checkpoint ${index}`
3571
- )
3693
+ ({ server, local }, index) => assertServerSignedLeaf(server, local, 0, serverPubkeys, `submitTx checkpoint ${index}`)
3572
3694
  );
3573
3695
  }
3574
3696
  let finalCheckpoints;
@@ -3957,9 +4079,18 @@ function gatedContracts(contracts) {
3957
4079
  }
3958
4080
  return gated;
3959
4081
  }
4082
+ function gatedFrom(snapshot) {
4083
+ return gatedContracts(snapshot.map((entry) => entry.contract));
4084
+ }
4085
+ function gatedTypeOf(vtxo, gated) {
4086
+ return vtxo.script === void 0 ? void 0 : gated.get(vtxo.script);
4087
+ }
4088
+ function isGatedVtxo(vtxo, gated) {
4089
+ return gatedTypeOf(vtxo, gated) !== void 0;
4090
+ }
3960
4091
  function gateExclusion(gated) {
3961
4092
  return (vtxo) => {
3962
- const type = vtxo.script === void 0 ? void 0 : gated.get(vtxo.script);
4093
+ const type = gatedTypeOf(vtxo, gated);
3963
4094
  if (type === void 0) return void 0;
3964
4095
  if (!contractHandlers.has(type)) {
3965
4096
  return `at ${vtxo.script} (contract type '${type}') has no handler registered in this build`;
@@ -6937,6 +7068,24 @@ var Batch;
6937
7068
  })(Batch || (Batch = {}));
6938
7069
 
6939
7070
  // src/utils/transactionHistory.ts
7071
+ function localCreatedAtByTxid(vtxos) {
7072
+ const createdAt = /* @__PURE__ */ new Map();
7073
+ for (const vtxo of vtxos) {
7074
+ if (vtxo.txid && !createdAt.has(vtxo.txid)) {
7075
+ createdAt.set(vtxo.txid, vtxo.createdAt.getTime());
7076
+ }
7077
+ }
7078
+ return createdAt;
7079
+ }
7080
+ function gatedTouchpoints(gated) {
7081
+ const paidIn = /* @__PURE__ */ new Set();
7082
+ const paidOut = /* @__PURE__ */ new Set();
7083
+ for (const vtxo of gated) {
7084
+ if (vtxo.txid) paidIn.add(vtxo.txid);
7085
+ if (vtxo.isSpent && vtxo.arkTxId) paidOut.add(vtxo.arkTxId);
7086
+ }
7087
+ return { paidIn, paidOut };
7088
+ }
6940
7089
  var txKey = {
6941
7090
  commitmentTxid: "",
6942
7091
  boardingTxid: "",
@@ -6988,24 +7137,27 @@ function subtractAssets(spent, change) {
6988
7137
  if (map.size === 0) return void 0;
6989
7138
  return Array.from(map, ([assetId, amount]) => ({ assetId, amount }));
6990
7139
  }
6991
- function collectArkTxidsNeedingCreatedAt(vtxos) {
6992
- const ownTxids = new Set(vtxos.map((v) => v.txid));
7140
+ function collectArkTxidsNeedingCreatedAt(vtxos, localCreatedAt) {
6993
7141
  const txids = /* @__PURE__ */ new Set();
6994
7142
  for (const vtxo of vtxos) {
6995
- if (vtxo.isSpent && vtxo.arkTxId && !ownTxids.has(vtxo.arkTxId)) {
7143
+ if (vtxo.isSpent && vtxo.arkTxId && !localCreatedAt.has(vtxo.arkTxId)) {
6996
7144
  txids.add(vtxo.arkTxId);
6997
7145
  }
6998
7146
  }
6999
7147
  return [...txids];
7000
7148
  }
7001
- async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, resolveTxCreatedAt) {
7002
- const fromOldestVtxo = vtxos.map(normalizeVtxo).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
7003
- const txidsNeedingCreatedAt = resolveTxCreatedAt ? collectArkTxidsNeedingCreatedAt(fromOldestVtxo) : [];
7149
+ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, resolveTxCreatedAt, gatedScripts = /* @__PURE__ */ new Map()) {
7150
+ const normalized = vtxos.map(normalizeVtxo).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
7151
+ const localCreatedAt = localCreatedAtByTxid(normalized);
7152
+ const gatedVtxos = normalized.filter((vtxo) => isGatedVtxo(vtxo, gatedScripts));
7153
+ const ownVtxos = normalized.filter((vtxo) => !isGatedVtxo(vtxo, gatedScripts));
7154
+ const { paidIn: paidIntoGated, paidOut: paidOutOfGated } = gatedTouchpoints(gatedVtxos);
7155
+ const txidsNeedingCreatedAt = resolveTxCreatedAt ? collectArkTxidsNeedingCreatedAt(ownVtxos, localCreatedAt) : [];
7004
7156
  const resolvedCreatedAt = resolveTxCreatedAt && txidsNeedingCreatedAt.length > 0 ? await resolveTxCreatedAt(txidsNeedingCreatedAt) : /* @__PURE__ */ new Map();
7005
7157
  const unmatchedSettledBoardingTxs = allBoardingTxs.filter(isSettledBoardingReceive).sort((a, b) => a.createdAt - b.createdAt);
7006
7158
  const sent = [];
7007
7159
  let received = [];
7008
- for (const vtxo of fromOldestVtxo) {
7160
+ for (const vtxo of ownVtxos) {
7009
7161
  if (vtxo.status.isLeaf) {
7010
7162
  const commitmentTxid = vtxo.commitmentTxIds[0];
7011
7163
  const vtxoCreatedAt = vtxo.createdAt.getTime();
@@ -7015,7 +7167,7 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
7015
7167
  unmatchedSettledBoardingTxs,
7016
7168
  (tx) => tx.createdAt <= vtxoCreatedAt && (tx.key.commitmentTxid === commitmentTxid || tx.key.commitmentTxid === vtxo.settledBy)
7017
7169
  );
7018
- } else if (fromOldestVtxo.filter((v) => v.settledBy === vtxo.commitmentTxIds[0]).length === 0) {
7170
+ } else if (ownVtxos.filter((v) => v.settledBy === vtxo.commitmentTxIds[0]).length === 0) {
7019
7171
  const duplicateBoardingReceive = consumeBoardingReceive(
7020
7172
  unmatchedSettledBoardingTxs,
7021
7173
  (tx) => tx.amount === vtxo.value && tx.createdAt <= vtxoCreatedAt
@@ -7036,11 +7188,14 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
7036
7188
  });
7037
7189
  }
7038
7190
  }
7039
- } else if (fromOldestVtxo.filter((v) => v.arkTxId === vtxo.txid).length === 0) {
7191
+ } else if (ownVtxos.filter((v) => v.arkTxId === vtxo.txid).length === 0) {
7040
7192
  const assets = collectAssets([vtxo]);
7041
7193
  received.push({
7042
7194
  key: { ...txKey, arkTxid: vtxo.txid },
7043
- tag: "offchain",
7195
+ // The escrow paying out: a swap fill or a cancel spends the
7196
+ // gated coin and lands the proceeds here. Nothing else about
7197
+ // the row changes — the tag only names the counterparty.
7198
+ tag: paidOutOfGated.has(vtxo.txid) ? "gated" : "offchain",
7044
7199
  type: "RECEIVED" /* TxReceived */,
7045
7200
  amount: vtxo.value,
7046
7201
  settled: vtxo.status.isLeaf || vtxo.isSpent,
@@ -7050,8 +7205,8 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
7050
7205
  }
7051
7206
  if (vtxo.isSpent) {
7052
7207
  if (vtxo.arkTxId && !sent.some((s) => s.key.arkTxid === vtxo.arkTxId)) {
7053
- const changes = fromOldestVtxo.filter((_) => _.txid === vtxo.arkTxId);
7054
- const allSpent = fromOldestVtxo.filter((v) => v.arkTxId === vtxo.arkTxId);
7208
+ const changes = ownVtxos.filter((_) => _.txid === vtxo.arkTxId);
7209
+ const allSpent = ownVtxos.filter((v) => v.arkTxId === vtxo.arkTxId);
7055
7210
  const spentAmount = allSpent.reduce((acc, v) => acc + v.value, 0);
7056
7211
  let txAmount = 0;
7057
7212
  let txTime = 0;
@@ -7061,13 +7216,16 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
7061
7216
  txTime = changes[0].createdAt.getTime();
7062
7217
  } else {
7063
7218
  txAmount = spentAmount;
7064
- txTime = resolvedCreatedAt.get(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1;
7219
+ txTime = localCreatedAt.get(vtxo.arkTxId) ?? resolvedCreatedAt.get(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1;
7065
7220
  }
7066
7221
  const assets = subtractAssets(allSpent, changes);
7067
7222
  if (txAmount !== 0 || assets) {
7068
7223
  sent.push({
7069
7224
  key: { ...txKey, arkTxid: vtxo.arkTxId },
7070
- tag: "offchain",
7225
+ // Funding the escrow: an output of this tx landed on a
7226
+ // gated contract of the wallet's, so the send is to the
7227
+ // user's own covenant rather than to a stranger.
7228
+ tag: paidIntoGated.has(vtxo.arkTxId) ? "gated" : "offchain",
7071
7229
  type: "SENT" /* TxSent */,
7072
7230
  amount: txAmount,
7073
7231
  settled: true,
@@ -7077,10 +7235,10 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
7077
7235
  }
7078
7236
  }
7079
7237
  if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
7080
- const changes = fromOldestVtxo.filter(
7238
+ const changes = ownVtxos.filter(
7081
7239
  (v) => v.status.isLeaf && v.commitmentTxIds.length > 0 && v.commitmentTxIds.every((_) => vtxo.settledBy === _)
7082
7240
  );
7083
- const forfeitVtxos = fromOldestVtxo.filter((v) => v.settledBy === vtxo.settledBy);
7241
+ const forfeitVtxos = ownVtxos.filter((v) => v.settledBy === vtxo.settledBy);
7084
7242
  const forfeitAmount = forfeitVtxos.reduce((acc, v) => acc + v.value, 0);
7085
7243
  if (changes.length > 0) {
7086
7244
  const settledAmount = changes.reduce((acc, v) => acc + v.value, 0);
@@ -7569,12 +7727,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
7569
7727
  if (!expiryTimestamp || expiryTimestamp === Number.MAX_SAFE_INTEGER) {
7570
7728
  delegateAt = new Date(Date.now() + 1 * 60 * 1e3);
7571
7729
  } else {
7572
- const remainingTimeMs = expiryTimestamp - Date.now();
7573
- if (remainingTimeMs <= 0) {
7574
- delegateAt = new Date(Date.now() + 1 * 60 * 1e3);
7575
- } else {
7576
- delegateAt = new Date(expiryTimestamp - remainingTimeMs * 0.1);
7577
- }
7730
+ delegateAt = defaultDelegateAt(expiryTimestamp, arkInfo.sessionDuration);
7578
7731
  }
7579
7732
  }
7580
7733
  const { fees, dust, forfeitAddress, network } = arkInfo;
@@ -7734,6 +7887,14 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
7734
7887
  message
7735
7888
  };
7736
7889
  }
7890
+ function defaultDelegateAt(expiryTimestamp, sessionDurationSeconds, now = Date.now()) {
7891
+ const remainingTimeMs = expiryTimestamp - now;
7892
+ if (remainingTimeMs <= 0) return new Date(now + 6e4);
7893
+ const sessionLeadMs = Number(sessionDurationSeconds * 1000n);
7894
+ const leadTimeMs = Math.max(remainingTimeMs * 0.1, sessionLeadMs);
7895
+ const delegateAt = expiryTimestamp - leadTimeMs;
7896
+ return new Date(delegateAt > now ? delegateAt : now + 2e3);
7897
+ }
7737
7898
  function findDestinationOutputIndex(outputs, destinationScript) {
7738
7899
  return outputs.findIndex((o) => o.script && equalBytes(o.script, destinationScript));
7739
7900
  }
@@ -9144,13 +9305,14 @@ var ContractWatcher = class {
9144
9305
  try {
9145
9306
  const vtxosMap = await this.getContractVtxos({
9146
9307
  contractScripts,
9147
- includeSpent: false
9148
- // only spendable ones!
9308
+ includeSpent: true
9149
9309
  });
9150
9310
  for (const contractScript of contractScripts) {
9151
9311
  const state = this.contracts.get(contractScript);
9152
9312
  if (!state) continue;
9153
- const currentVtxos = vtxosMap.get(contractScript) || [];
9313
+ const known = vtxosMap.get(contractScript) || [];
9314
+ const knownByKey = new Map(known.map((v) => [`${v.txid}:${v.vout}`, v]));
9315
+ const currentVtxos = known.filter((v) => !v.isSpent);
9154
9316
  const currentKeys = new Set(currentVtxos.map((v) => `${v.txid}:${v.vout}`));
9155
9317
  const newVtxos = [];
9156
9318
  for (const vtxo of currentVtxos) {
@@ -9163,7 +9325,7 @@ var ContractWatcher = class {
9163
9325
  const spentVtxos = [];
9164
9326
  for (const [key, vtxo] of state.lastKnownVtxos) {
9165
9327
  if (!currentKeys.has(key)) {
9166
- spentVtxos.push(vtxo);
9328
+ spentVtxos.push(knownByKey.get(key) ?? vtxo);
9167
9329
  state.lastKnownVtxos.delete(key);
9168
9330
  }
9169
9331
  }
@@ -9557,6 +9719,7 @@ var ContractManager = class _ContractManager {
9557
9719
  syncDegradedReason;
9558
9720
  /** Epoch-ms of the last successful provider sync, if any. */
9559
9721
  lastSyncedAt;
9722
+ syncedAtByScript = /* @__PURE__ */ new Map();
9560
9723
  /** Last chain tip read, with the epoch-ms it was read at. @see currentChainTip */
9561
9724
  chainTipCache;
9562
9725
  /** In-flight chain tip read, so concurrent cache misses share one. */
@@ -9610,6 +9773,10 @@ var ContractManager = class _ContractManager {
9610
9773
  lastSyncedAt: this.lastSyncedAt
9611
9774
  };
9612
9775
  }
9776
+ /** @see ContractManagerConfig.vtxoSyncMaxAgeMs — for factory-built managers. */
9777
+ setVtxoSyncMaxAge(maxAgeMs) {
9778
+ this.config.vtxoSyncMaxAgeMs = maxAgeMs;
9779
+ }
9613
9780
  markSyncOnline() {
9614
9781
  this.lastSyncedAt = Date.now();
9615
9782
  this.syncDegradedReason = this.annotationDegradedReason();
@@ -9913,6 +10080,41 @@ var ContractManager = class _ContractManager {
9913
10080
  }
9914
10081
  return contract;
9915
10082
  }
10083
+ /**
10084
+ * `createContract`'s step order, fetch batched: persist all, hydrate once,
10085
+ * then watch. Hydrating first keeps it equivalent — the watcher seeds from
10086
+ * the repository, so an unhydrated row reads as all-new. A part-way failure
10087
+ * still watches what it wrote; a retry reports those rows as existing.
10088
+ */
10089
+ async createContracts(paramsList) {
10090
+ if (paramsList.length === 0) return [];
10091
+ return this.watcher.withCoalescedSubscription(async () => {
10092
+ const upserted = [];
10093
+ try {
10094
+ for (const params of paramsList) upserted.push(await this.upsertContract(params));
10095
+ } catch (err) {
10096
+ for (const u of upserted) {
10097
+ if (!u.persisted) continue;
10098
+ await this.watcher.addContract(u.contract).catch(
10099
+ (e) => console.error(`ContractManager: ${u.contract.script} left dark`, e)
10100
+ );
10101
+ }
10102
+ throw err;
10103
+ }
10104
+ const fresh = upserted.filter((u) => u.persisted).map((u) => u.contract);
10105
+ if (fresh.length > 0) {
10106
+ try {
10107
+ await this.fetchContractVxosFromIndexer(fresh);
10108
+ this.markSyncOnline();
10109
+ } catch (err) {
10110
+ if (!isRetryableProviderError(err)) throw err;
10111
+ this.markSyncDegraded(err);
10112
+ }
10113
+ for (const contract of fresh) await this.watcher.addContract(contract);
10114
+ }
10115
+ return upserted.map((u) => u.contract);
10116
+ });
10117
+ }
9916
10118
  /**
9917
10119
  * Lightweight variant of {@link createContract} for batch discovery
9918
10120
  * paths (currently: {@link scanContracts}). Validates, dedupes, persists,
@@ -10174,12 +10376,16 @@ var ContractManager = class _ContractManager {
10174
10376
  }
10175
10377
  async getContractsWithVtxos(filter, pageSize) {
10176
10378
  const contracts = await this.getContracts(filter);
10177
- try {
10178
- await this.syncContracts({ contracts, pageSize });
10179
- this.markSyncOnline();
10180
- } catch (err) {
10181
- if (!isRetryableProviderError(err)) throw err;
10182
- this.markSyncDegraded(err);
10379
+ if (this.syncedWithin(contracts, this.config.vtxoSyncMaxAgeMs ?? 0)) {
10380
+ await this.demoteFundedAwaitingContracts(contracts);
10381
+ } else {
10382
+ try {
10383
+ await this.syncContracts({ contracts, pageSize });
10384
+ this.markSyncOnline();
10385
+ } catch (err) {
10386
+ if (!isRetryableProviderError(err)) throw err;
10387
+ this.markSyncDegraded(err);
10388
+ }
10183
10389
  }
10184
10390
  const vtxos = await this.getVtxosForContracts(contracts);
10185
10391
  return contracts.map((contract) => ({
@@ -10594,6 +10800,7 @@ var ContractManager = class _ContractManager {
10594
10800
  }
10595
10801
  break;
10596
10802
  case "connection_reset":
10803
+ this.syncedAtByScript.clear();
10597
10804
  await this.scheduleLookAheadDrain();
10598
10805
  await this.reconcileWatched();
10599
10806
  this.markSyncOnline();
@@ -10640,9 +10847,21 @@ var ContractManager = class _ContractManager {
10640
10847
  const cutoff = cursorCutoff(requestStartedAt);
10641
10848
  await advanceSyncCursor(this.config.walletRepository, cutoff);
10642
10849
  }
10850
+ if (options.window === void 0) {
10851
+ for (const contract of contracts) {
10852
+ this.syncedAtByScript.set(contract.script, requestStartedAt);
10853
+ }
10854
+ }
10643
10855
  await this.demoteFundedAwaitingContracts(contracts);
10644
10856
  return result;
10645
10857
  }
10858
+ syncedWithin(contracts, maxAgeMs) {
10859
+ if (maxAgeMs <= 0) return false;
10860
+ const floor = Date.now() - maxAgeMs;
10861
+ return contracts.every(
10862
+ (contract) => (this.syncedAtByScript.get(contract.script) ?? 0) >= floor
10863
+ );
10864
+ }
10646
10865
  /**
10647
10866
  * Demote every `awaiting-funds` contract in `contracts` that has been
10648
10867
  * funded — the automatic half of {@link ContractWatchState}.
@@ -13048,7 +13267,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
13048
13267
  const vtxos = filterSnapshotVtxos(snapshot, filter, this._pendingSpendOutpoints);
13049
13268
  const { gated, pendingRecovery } = this.spendabilityView(snapshot);
13050
13269
  const selectable = vtxos.filter(
13051
- (vtxo) => !gated.has(vtxo.script) && !pendingRecovery.has(`${vtxo.txid}:${vtxo.vout}`)
13270
+ (vtxo) => !isGatedVtxo(vtxo, gated) && !pendingRecovery.has(`${vtxo.txid}:${vtxo.vout}`)
13052
13271
  );
13053
13272
  const unlocked = await spendableVtxosExcludingLocked(selectable, this.intentRepository);
13054
13273
  logExcludedVtxos("getSpendableVtxos", vtxos, [
@@ -13146,7 +13365,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
13146
13365
  */
13147
13366
  spendabilityView(snapshot) {
13148
13367
  return {
13149
- gated: gatedContracts(snapshot.map((_) => _.contract)),
13368
+ gated: gatedFrom(snapshot),
13150
13369
  pendingRecovery: this.selectPendingRecovery(snapshot)
13151
13370
  };
13152
13371
  }
@@ -13161,7 +13380,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
13161
13380
  return {
13162
13381
  now: { timestamp: /* @__PURE__ */ new Date() },
13163
13382
  isPendingRecovery: (vtxo) => pendingRecovery.has(`${vtxo.txid}:${vtxo.vout}`),
13164
- isGenericallySpendable: (vtxo) => !gated.has(vtxo.script),
13383
+ isGenericallySpendable: (vtxo) => !isGatedVtxo(vtxo, gated),
13165
13384
  isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`)
13166
13385
  };
13167
13386
  }
@@ -13201,16 +13420,18 @@ var ReadonlyWallet = class _ReadonlyWallet {
13201
13420
  * Return wallet transaction history derived from Arkade state and boarding transactions.
13202
13421
  */
13203
13422
  async getTransactionHistory() {
13204
- const contractManager = await this.getContractManager();
13205
- const response = await contractManager.getContractsWithVtxos();
13206
- const allVtxos = response.flatMap((_) => _.vtxos);
13207
- const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
13423
+ const [snapshot, { boardingTxs, commitmentsToIgnore }] = await Promise.all([
13424
+ this.contractSnapshot(),
13425
+ this.getBoardingTxs()
13426
+ ]);
13427
+ const allVtxos = snapshot.flatMap((_) => _.vtxos);
13208
13428
  const resolveTxCreatedAt = (txids) => fetchVtxoCreatedAtByTxid(this.indexerProvider, txids);
13209
13429
  return buildTransactionHistory(
13210
13430
  allVtxos,
13211
13431
  boardingTxs,
13212
13432
  commitmentsToIgnore,
13213
- resolveTxCreatedAt
13433
+ resolveTxCreatedAt,
13434
+ gatedFrom(snapshot)
13214
13435
  );
13215
13436
  }
13216
13437
  /**
@@ -14570,7 +14791,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
14570
14791
  `restore: gapLimit must be a positive integer (got ${String(opts?.gapLimit)})`
14571
14792
  );
14572
14793
  }
14573
- this._restoreInFlight = this._runRestore(gapLimit).finally(() => {
14794
+ this._restoreInFlight = (async () => {
14795
+ await this._runRestore(gapLimit);
14796
+ await runWalletRestoreHooks(this);
14797
+ })().finally(() => {
14574
14798
  this._restoreInFlight = void 0;
14575
14799
  });
14576
14800
  return this._restoreInFlight;
@@ -15238,6 +15462,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15238
15462
  const connectorsLeaves = connectorsGraph?.leaves() || [];
15239
15463
  for (const input of inputs) {
15240
15464
  if (!isVirtualCoin(input)) {
15465
+ let matched = false;
15241
15466
  for (let i = 0; i < settlementPsbt.inputsLength; i++) {
15242
15467
  const settlementInput = settlementPsbt.getInput(i);
15243
15468
  if (!settlementInput.txid || settlementInput.index === void 0) {
@@ -15264,8 +15489,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15264
15489
  throw new Error(await this.unsignableBoardingInputError(input, script));
15265
15490
  }
15266
15491
  hasBoardingUtxos = true;
15492
+ matched = true;
15267
15493
  break;
15268
15494
  }
15495
+ if (!matched && !(input instanceof ArkNote)) {
15496
+ throw new Error(
15497
+ `boarding input ${input.txid}:${input.vout} is not an input of the commitment tx`
15498
+ );
15499
+ }
15269
15500
  continue;
15270
15501
  }
15271
15502
  if (canRecoverOnchain(input, { timestamp: /* @__PURE__ */ new Date() }) || isSubdust({ value: input.value }, this.dustAmount)) {
@@ -16406,18 +16637,33 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
16406
16637
  this.inputSigningJobsFromWitnessUtxos(checkpoint)
16407
16638
  )
16408
16639
  };
16409
- return submitOffchainTx(this.arkProvider, offchainTx, signer, {
16410
- // Mark pending before submitting — if we crash between submit and
16411
- // finalize, the next init recovers via finalizePendingTxs.
16412
- beforeSubmit: () => this.setPendingTxFlag(true),
16413
- afterFinalize: async () => {
16414
- try {
16415
- await this.setPendingTxFlag(false);
16416
- } catch (error) {
16417
- console.error("Failed to clear pending tx flag:", error);
16640
+ return submitOffchainTx(
16641
+ this.arkProvider,
16642
+ offchainTx,
16643
+ signer,
16644
+ {
16645
+ // Mark pending before submitting — if we crash between submit and
16646
+ // finalize, the next init recovers via finalizePendingTxs.
16647
+ beforeSubmit: () => this.setPendingTxFlag(true),
16648
+ afterFinalize: async () => {
16649
+ try {
16650
+ await this.setPendingTxFlag(false);
16651
+ } catch (error) {
16652
+ console.error("Failed to clear pending tx flag:", error);
16653
+ }
16654
+ }
16655
+ },
16656
+ {
16657
+ // Deprecated keys too: a vtxo built before a rotation is still
16658
+ // spent under the signer its leaf names.
16659
+ verifyServerSignatures: {
16660
+ serverPubkey: this._arkServerPublicKey,
16661
+ deprecatedServerPubkeys: [...this._deprecatedSigners.keys()].map(
16662
+ (h) => hex.decode(h)
16663
+ )
16418
16664
  }
16419
16665
  }
16420
- });
16666
+ );
16421
16667
  }
16422
16668
  // mark virtual outputs as spent, save change outputs if any.
16423
16669
  // `offchainTapscript` and `serverPubKey` are the epoch snapshot the
@@ -17890,8 +18136,7 @@ var WalletMessageHandler = class {
17890
18136
  });
17891
18137
  }
17892
18138
  case "GET_TRANSACTION_HISTORY": {
17893
- const allVtxos = await this.getVtxosFromRepo();
17894
- const transactions = await this.buildTransactionHistoryFromCache(allVtxos) ?? [];
18139
+ const transactions = await this.buildTransactionHistoryFromCache() ?? [];
17895
18140
  return this.tagged({
17896
18141
  id,
17897
18142
  type: "TRANSACTION_HISTORY",
@@ -18373,7 +18618,7 @@ var WalletMessageHandler = class {
18373
18618
  unconfirmed += utxo.value;
18374
18619
  }
18375
18620
  }
18376
- const gated = gatedContracts(snapshot.map((_) => _.contract));
18621
+ const gated = gatedFrom(snapshot);
18377
18622
  const unlocked = new Set(
18378
18623
  (await spendableVtxosExcludingLocked(allVtxos, this.readonlyWallet?.intentRepository)).map((vtxo) => `${vtxo.txid}:${vtxo.vout}`)
18379
18624
  );
@@ -18381,7 +18626,7 @@ var WalletMessageHandler = class {
18381
18626
  const offchain = computeOffchainBalance(allVtxos, {
18382
18627
  now: { timestamp: /* @__PURE__ */ new Date() },
18383
18628
  isPendingRecovery: (vtxo) => pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`),
18384
- isGenericallySpendable: (vtxo) => !gated.has(vtxo.script),
18629
+ isGenericallySpendable: (vtxo) => !isGatedVtxo(vtxo, gated),
18385
18630
  isUnlocked: (vtxo) => unlocked.has(`${vtxo.txid}:${vtxo.vout}`)
18386
18631
  });
18387
18632
  return {
@@ -18521,7 +18766,6 @@ var WalletMessageHandler = class {
18521
18766
  if (!this.readonlyWallet || !this.walletRepository) {
18522
18767
  return;
18523
18768
  }
18524
- const vtxos = await this.getVtxosFromRepo();
18525
18769
  const boardingAddresses = await this.readonlyWallet.getBoardingAddresses();
18526
18770
  const fresh = await this.readonlyWallet.getBoardingUtxos();
18527
18771
  const freshKeys = new Set(fresh.map((u) => `${u.txid}:${u.vout}`));
@@ -18533,7 +18777,7 @@ var WalletMessageHandler = class {
18533
18777
  if (kept.length > 0) await this.walletRepository.saveUtxos(addr, kept);
18534
18778
  }
18535
18779
  const address = await this.readonlyWallet.getAddress();
18536
- const txs = await this.buildTransactionHistoryFromCache(vtxos);
18780
+ const txs = await this.buildTransactionHistoryFromCache();
18537
18781
  if (txs) await this.walletRepository.saveTransactions(address, txs);
18538
18782
  }
18539
18783
  /**
@@ -18741,13 +18985,29 @@ var WalletMessageHandler = class {
18741
18985
  /**
18742
18986
  * Build transaction history from cached virtual outputs, hitting the indexer only for
18743
18987
  * uncached timestamps. Best-effort, like the plain Wallet path.
18744
- */
18745
- async buildTransactionHistoryFromCache(vtxos) {
18988
+ *
18989
+ * Takes its own {@link repoSnapshot} rather than a caller-supplied VTXO
18990
+ * list: the coins and the gate that judges them have to come off one read
18991
+ * or they answer about different instants, and being the worker's only
18992
+ * history builder means neither caller can supply one without the other.
18993
+ * The snapshot and the boarding read are independent, so they run together
18994
+ * — the same pairing `handleGetBalance` makes.
18995
+ */
18996
+ async buildTransactionHistoryFromCache() {
18746
18997
  if (!this.readonlyWallet) return null;
18747
- const { boardingTxs, commitmentsToIgnore } = await this.readonlyWallet.getBoardingTxs();
18998
+ const [{ snapshot, vtxos }, { boardingTxs, commitmentsToIgnore }] = await Promise.all([
18999
+ this.repoSnapshot(),
19000
+ this.readonlyWallet.getBoardingTxs()
19001
+ ]);
18748
19002
  const indexerProvider = this.indexerProvider;
18749
19003
  const resolveTxCreatedAt = indexerProvider ? (txids) => fetchVtxoCreatedAtByTxid(indexerProvider, txids) : void 0;
18750
- return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, resolveTxCreatedAt);
19004
+ return buildTransactionHistory(
19005
+ vtxos,
19006
+ boardingTxs,
19007
+ commitmentsToIgnore,
19008
+ resolveTxCreatedAt,
19009
+ gatedFrom(snapshot)
19010
+ );
18751
19011
  }
18752
19012
  async ensureContractEventBroadcasting() {
18753
19013
  const wallet = this.readonlyWallet;
@@ -19843,6 +20103,7 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
19843
20103
  identity;
19844
20104
  _assetManager;
19845
20105
  hasDelegate;
20106
+ _restoreInFlight;
19846
20107
  get assetManager() {
19847
20108
  return this._assetManager;
19848
20109
  }
@@ -20105,6 +20366,16 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
20105
20366
  * reconstructed here so callers can inspect `.errors`.
20106
20367
  */
20107
20368
  async restore(opts) {
20369
+ if (this._restoreInFlight) return this._restoreInFlight;
20370
+ this._restoreInFlight = (async () => {
20371
+ await this._restoreWorkerWallet(opts);
20372
+ await runWalletRestoreHooks(this);
20373
+ })().finally(() => {
20374
+ this._restoreInFlight = void 0;
20375
+ });
20376
+ return this._restoreInFlight;
20377
+ }
20378
+ async _restoreWorkerWallet(opts) {
20108
20379
  const message = {
20109
20380
  id: getRandomId(),
20110
20381
  tag: this.messageTag,
@@ -21694,7 +21965,7 @@ var RestEmulatorProvider = class {
21694
21965
  serverUrl;
21695
21966
  async getInfo() {
21696
21967
  const url = `${this.serverUrl}/v1/info`;
21697
- const response = await fetch(url);
21968
+ const response = await baseFetch(url);
21698
21969
  if (!response.ok) {
21699
21970
  const errorText2 = await response.text();
21700
21971
  throw new Error(`Failed to get emulator info: ${errorText2}`);
@@ -23081,6 +23352,12 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
23081
23352
  validateVtxoTxGraph(vtxoTree, commitmentTx, sweepTapTreeRoot);
23082
23353
  if (recipients && recipients.length > 0) {
23083
23354
  validateBatchRecipients(commitmentTx, vtxoTree.leaves(), recipients, network);
23355
+ } else {
23356
+ validateBatchAgainstIntent(
23357
+ commitmentTx,
23358
+ vtxoTree.leaves(),
23359
+ declaredIntentOutputs(signedProof, message.onchain_output_indexes)
23360
+ );
23084
23361
  }
23085
23362
  const sharedOutput = commitmentTx.getOutput(0);
23086
23363
  if (!sharedOutput?.amount) {
@@ -23115,8 +23392,15 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
23115
23392
  validatedCommitmentTxid,
23116
23393
  "arkade batch finalization"
23117
23394
  );
23118
- if (!validatedCommitmentTxid && recipients && recipients.length > 0) {
23119
- validateBatchRecipientsWithoutTree(commitmentPsbt, recipients, network);
23395
+ if (!validatedCommitmentTxid) {
23396
+ if (recipients && recipients.length > 0) {
23397
+ validateBatchRecipientsWithoutTree(commitmentPsbt, recipients, network);
23398
+ } else {
23399
+ validateBatchAgainstIntentWithoutTree(
23400
+ commitmentPsbt,
23401
+ declaredIntentOutputs(signedProof, message.onchain_output_indexes)
23402
+ );
23403
+ }
23120
23404
  }
23121
23405
  const signedForfeits = [];
23122
23406
  let connectorIndex = 0;
@@ -24129,6 +24413,6 @@ function createDefaultPaymentRouter(wallet) {
24129
24413
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
24130
24414
  */
24131
24415
 
24132
- export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertNoAssets, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, requiresMigration, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
24133
- //# sourceMappingURL=chunk-E5JNYFOF.js.map
24134
- //# sourceMappingURL=chunk-E5JNYFOF.js.map
24416
+ export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertNoAssets, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, registerWalletRestoreHook, requiresMigration, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
24417
+ //# sourceMappingURL=chunk-YHJJSLE5.js.map
24418
+ //# sourceMappingURL=chunk-YHJJSLE5.js.map