@arkade-os/sdk 0.5.0-rc.27 → 0.5.0-rc.29

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 +35 -0
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +3 -3
  4. package/dist/adapters/expo.d.ts +3 -3
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/{asyncStorageTaskQueue-Ca-mM3NR.d.ts → asyncStorageTaskQueue-7UQpcjqM.d.ts} +1 -1
  7. package/dist/{asyncStorageTaskQueue-CGZMG8Mc.d.cts → asyncStorageTaskQueue-QiOMZQRD.d.cts} +1 -1
  8. package/dist/{chunk-I4ZWKG7B.js → chunk-34G7TDUS.js} +670 -222
  9. package/dist/chunk-34G7TDUS.js.map +1 -0
  10. package/dist/{chunk-RUWTAAJK.js → chunk-44E37WPJ.js} +5 -4
  11. package/dist/chunk-44E37WPJ.js.map +1 -0
  12. package/dist/{chunk-PM6KYV7S.cjs → chunk-4CU5CECR.cjs} +1150 -701
  13. package/dist/chunk-4CU5CECR.cjs.map +1 -0
  14. package/dist/{chunk-QXZS4J3V.cjs → chunk-7UFHC75B.cjs} +10 -10
  15. package/dist/{chunk-QXZS4J3V.cjs.map → chunk-7UFHC75B.cjs.map} +1 -1
  16. package/dist/{chunk-FTJ2YWJJ.js → chunk-CVJXASA2.js} +21 -9
  17. package/dist/chunk-CVJXASA2.js.map +1 -0
  18. package/dist/{chunk-URILS6HH.cjs → chunk-D57OWBZH.cjs} +36 -24
  19. package/dist/chunk-D57OWBZH.cjs.map +1 -0
  20. package/dist/{chunk-EP6XF7JX.cjs → chunk-DE7HV5LJ.cjs} +201 -96
  21. package/dist/chunk-DE7HV5LJ.cjs.map +1 -0
  22. package/dist/{chunk-BULTXVEN.js → chunk-KLXLTJEG.js} +4 -4
  23. package/dist/{chunk-BULTXVEN.js.map → chunk-KLXLTJEG.js.map} +1 -1
  24. package/dist/{chunk-X42Z36L5.js → chunk-N6RJDTPX.js} +6 -10
  25. package/dist/chunk-N6RJDTPX.js.map +1 -0
  26. package/dist/{chunk-DKHBOT5W.cjs → chunk-QRAQSUCE.cjs} +17 -21
  27. package/dist/chunk-QRAQSUCE.cjs.map +1 -0
  28. package/dist/{chunk-ZN7BX5XT.cjs → chunk-VYRZXLKM.cjs} +5 -3
  29. package/dist/chunk-VYRZXLKM.cjs.map +1 -0
  30. package/dist/{chunk-PO6AJUWK.js → chunk-WNHMY5ET.js} +154 -51
  31. package/dist/chunk-WNHMY5ET.js.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -9
  33. package/dist/contracts/handlers/index.d.cts +3 -3
  34. package/dist/contracts/handlers/index.d.ts +3 -3
  35. package/dist/contracts/handlers/index.js +2 -2
  36. package/dist/{delegate-CG3EwryS.d.ts → delegate---DiIqfr.d.ts} +1 -1
  37. package/dist/{delegate-CH-QUGok.d.cts → delegate-2k8MG5qy.d.cts} +1 -1
  38. package/dist/{index-BpiwCcFb.d.cts → index-8BQ5L5js.d.cts} +172 -5
  39. package/dist/{index-BpiwCcFb.d.ts → index-8BQ5L5js.d.ts} +172 -5
  40. package/dist/{index-BPS3Ki3l.d.cts → index-DJ3xIDCG.d.cts} +58 -52
  41. package/dist/{index-DLn2XUIl.d.ts → index-cG9JYLX4.d.ts} +58 -52
  42. package/dist/index.cjs +328 -316
  43. package/dist/index.d.cts +164 -123
  44. package/dist/index.d.ts +164 -123
  45. package/dist/index.js +4 -4
  46. package/dist/repositories/realm/index.cjs +35 -35
  47. package/dist/repositories/realm/index.d.cts +2 -2
  48. package/dist/repositories/realm/index.d.ts +2 -2
  49. package/dist/repositories/realm/index.js +5 -5
  50. package/dist/repositories/sqlite/index.cjs +33 -33
  51. package/dist/repositories/sqlite/index.d.cts +2 -2
  52. package/dist/repositories/sqlite/index.d.ts +2 -2
  53. package/dist/repositories/sqlite/index.js +5 -5
  54. package/dist/{taskRunner-DtphXKXv.d.cts → taskRunner-Ba2InBIA.d.cts} +1 -1
  55. package/dist/{taskRunner-Dbdr9WMQ.d.ts → taskRunner-SHwLCckR.d.ts} +1 -1
  56. package/dist/wallet/expo/background.cjs +14 -14
  57. package/dist/wallet/expo/background.d.cts +3 -3
  58. package/dist/wallet/expo/background.d.ts +3 -3
  59. package/dist/wallet/expo/background.js +6 -6
  60. package/dist/wallet/expo/index.cjs +15 -15
  61. package/dist/wallet/expo/index.d.cts +4 -4
  62. package/dist/wallet/expo/index.d.ts +4 -4
  63. package/dist/wallet/expo/index.js +5 -5
  64. package/dist/{wallet-CTrmow5R.d.cts → wallet-Bfe7QdhT.d.cts} +2 -2
  65. package/dist/{wallet-QvS_5Jjd.d.ts → wallet-o8UkwASU.d.ts} +2 -2
  66. package/dist/worker/expo/index.cjs +9 -9
  67. package/dist/worker/expo/index.d.cts +4 -4
  68. package/dist/worker/expo/index.d.ts +4 -4
  69. package/dist/worker/expo/index.js +5 -5
  70. package/package.json +1 -1
  71. package/dist/chunk-DKHBOT5W.cjs.map +0 -1
  72. package/dist/chunk-EP6XF7JX.cjs.map +0 -1
  73. package/dist/chunk-FTJ2YWJJ.js.map +0 -1
  74. package/dist/chunk-I4ZWKG7B.js.map +0 -1
  75. package/dist/chunk-PM6KYV7S.cjs.map +0 -1
  76. package/dist/chunk-PO6AJUWK.js.map +0 -1
  77. package/dist/chunk-RUWTAAJK.js.map +0 -1
  78. package/dist/chunk-URILS6HH.cjs.map +0 -1
  79. package/dist/chunk-X42Z36L5.js.map +0 -1
  80. package/dist/chunk-ZN7BX5XT.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-FTJ2YWJJ.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, normalizeToDescriptor, extractPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-PO6AJUWK.js';
3
- import { VtxoScript, __export, Transaction, requiresForfeit, CSVMultisigTapscript, networks, getNetwork, DEFAULT_NETWORK, resolveEmulatorPubkey, isVtxoSpent, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, sequenceToCanonicalTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, assertTimerMs, getSequence, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, getAllNormalizedVtxos, isPastExpiry, assertNonNegativeMs, isRetryableProviderError, DEFAULT_PAGE_SIZE, RestArkProvider, DEFAULT_ARKADE_SERVER_URL, networkFromArkadeInfo, ArkError, toBIP371TapTree, resolveTimeHeight, canSpendOffchain, INFO_FETCH_TIMEOUT_MS, PrevoutTxField, fetchVtxoCreatedAtByTxid, EXPIRY_MIN_PLAUSIBLE_MS, ProviderUnavailableError } from './chunk-RUWTAAJK.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, RestIndexerProvider, BufferReader, BufferWriter, AssetRef } from './chunk-CVJXASA2.js';
2
+ import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, UnsupportedProgramVersionError, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, toXOnly, ARKADE_TYPE_ENCODINGS, validateParamValue, contractHandlers, speculativeReceiveMetadata, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, normalizeToDescriptor, extractPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-WNHMY5ET.js';
3
+ import { VtxoScript, __export, Transaction, requiresForfeit, CSVMultisigTapscript, networks, getNetwork, DEFAULT_NETWORK, resolveEmulatorPubkey, timelockToSequence, isVtxoSpent, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, RestArkProvider, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, assertNonNegativeMs, isRetryableProviderError, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, DEFAULT_ARKADE_SERVER_URL, baseFetch, DEFAULT_NETWORK_NAME, sequenceToCanonicalTimelock, assertTimerMs, isEventSourceError, isEventSourceUnavailableError, normalizeVtxo, isPastExpiry, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, isArkError, ArkErrorName, assertAllowedSighashTypes, Intent, rateGate, formatSighash, getSequence, maybeArkError, toOffchainInputFeeParams, canRecoverOnchain, MultisigTapscript, DEFAULT_ARKADE_EMULATOR_URL, networkFromArkadeInfo, ArkError, toBIP371TapTree, resolveTimeHeight, canSpendOffchain, INFO_FETCH_TIMEOUT_MS, PrevoutTxField, fetchVtxoCreatedAtByTxid, EXPIRY_MIN_PLAUSIBLE_MS, ProviderUnavailableError } from './chunk-44E37WPJ.js';
4
4
  import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
5
5
  import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$1 } from '@scure/btc-signer';
6
6
  import { hex, base58, base64, bech32m } from '@scure/base';
@@ -9773,15 +9773,19 @@ function toWatchOnlyContract(params) {
9773
9773
  return { ...params, state: params.state ?? "active", createdAt: Date.now() };
9774
9774
  }
9775
9775
  var TAPSCRIPT_MEMO_MAX_ENTRIES = 1024;
9776
+ function contractKey(contract) {
9777
+ return `${contract.type}\0${contract.script}\0${JSON.stringify(contract.params)}`;
9778
+ }
9776
9779
  function annotatableIn(scriptToContract, vtxos, memo) {
9777
9780
  const scripts = /* @__PURE__ */ new Set();
9778
9781
  const cache = /* @__PURE__ */ new Map();
9779
9782
  const failures = /* @__PURE__ */ new Map();
9783
+ const unsupported = /* @__PURE__ */ new Map();
9780
9784
  for (const script of new Set(vtxos.map((vtxo) => vtxo.script))) {
9781
9785
  const contract = scriptToContract.get(script);
9782
9786
  if (!contract) continue;
9783
9787
  try {
9784
- const key = `${contract.type}\0${script}\0${JSON.stringify(contract.params)}`;
9788
+ const key = contractKey(contract);
9785
9789
  const handler = contractHandlers.get(contract.type);
9786
9790
  const hit = memo?.get(script);
9787
9791
  let tapscripts;
@@ -9802,13 +9806,15 @@ function annotatableIn(scriptToContract, vtxos, memo) {
9802
9806
  cache.set(script, tapscripts);
9803
9807
  scripts.add(script);
9804
9808
  } catch (err) {
9805
- failures.set(
9806
- script,
9807
- `'${contract.type}' at ${script}: ${err instanceof Error ? err.message : String(err)}`
9808
- );
9809
+ const reason = `'${contract.type}' at ${script}: ${err instanceof Error ? err.message : String(err)}`;
9810
+ if (err instanceof UnsupportedProgramVersionError) {
9811
+ unsupported.set(script, { key: contractKey(contract), reason });
9812
+ } else {
9813
+ failures.set(script, reason);
9814
+ }
9809
9815
  }
9810
9816
  }
9811
- return { scripts, cache, failures };
9817
+ return { scripts, cache, failures, unsupported };
9812
9818
  }
9813
9819
  function annotateAgainst(vtxos, byScript, tapscripts = /* @__PURE__ */ new Map()) {
9814
9820
  return vtxos.map(
@@ -9923,6 +9929,11 @@ var ContractManager = class _ContractManager {
9923
9929
  watcherStartFailure;
9924
9930
  /** Contracts a sync could not annotate, as `script → reason`. */
9925
9931
  annotationFailures = /* @__PURE__ */ new Map();
9932
+ /**
9933
+ * Rows in a program version this SDK cannot read, as `script → contractKey`. Kept out of
9934
+ * {@link annotationFailures}: they are expected after an upgrade, so they must not degrade sync.
9935
+ */
9936
+ unsupportedContracts = /* @__PURE__ */ new Map();
9926
9937
  annotationDegradedReason() {
9927
9938
  if (this.annotationFailures.size === 0) return void 0;
9928
9939
  return `cannot annotate ${this.annotationFailures.size} contract(s), their vtxos are not being synced \u2014 ${[...this.annotationFailures.values()].join("; ")}`;
@@ -9931,12 +9942,39 @@ var ContractManager = class _ContractManager {
9931
9942
  * Fold one batch's verdict in. Merged, not replaced: a batch may cover only a subset of the
9932
9943
  * wallet's contracts and must not erase what a wider sync found.
9933
9944
  */
9934
- recordAnnotationFailures(annotated, failures) {
9935
- for (const script of annotated) this.annotationFailures.delete(script);
9945
+ recordAnnotationFailures(annotated, failures, unsupported) {
9946
+ for (const script of annotated) {
9947
+ this.annotationFailures.delete(script);
9948
+ this.unsupportedContracts.delete(script);
9949
+ }
9936
9950
  for (const [script, reason] of failures) {
9951
+ this.unsupportedContracts.delete(script);
9937
9952
  this.annotationFailures.set(script, reason);
9938
9953
  console.warn(`[contracts] cannot annotate contract ${reason}; skipping its vtxos`);
9939
9954
  }
9955
+ for (const [script, { key, reason }] of unsupported) {
9956
+ this.annotationFailures.delete(script);
9957
+ if (this.unsupportedContracts.get(script) === key) continue;
9958
+ this.unsupportedContracts.set(script, key);
9959
+ console.warn(`[contracts] cannot annotate contract ${reason}; skipping its vtxos`);
9960
+ }
9961
+ }
9962
+ /**
9963
+ * Whether `contract` is in a program version this SDK cannot read. Memoized per
9964
+ * {@link contractKey}, so a re-created row is never answered from its predecessor's verdict.
9965
+ */
9966
+ isUnsupported(contract) {
9967
+ const key = contractKey(contract);
9968
+ if (this.unsupportedContracts.get(contract.script) === key) return true;
9969
+ if (this.tapscriptMemo.get(contract.script)?.key === key) return false;
9970
+ try {
9971
+ contractHandlers.get(contract.type)?.createScript(contract.params);
9972
+ return false;
9973
+ } catch (err) {
9974
+ if (!(err instanceof UnsupportedProgramVersionError)) return false;
9975
+ this.unsupportedContracts.set(contract.script, key);
9976
+ return true;
9977
+ }
9940
9978
  }
9941
9979
  /** A retryable failure degrades sync state instead of propagating; terminal ones still throw. */
9942
9980
  /** Returns the swallowed retryable error, if any. */
@@ -10317,7 +10355,13 @@ var ContractManager = class _ContractManager {
10317
10355
  }
10318
10356
  const [existing] = await this.getContracts({ script: params.script });
10319
10357
  if (existing) {
10320
- if (existing.type === params.type) return { contract: existing, persisted: false };
10358
+ if (existing.type === params.type) {
10359
+ if (!this.isUnsupported(existing)) return { contract: existing, persisted: false };
10360
+ return {
10361
+ contract: await this.replaceUnsupported(existing, params),
10362
+ persisted: true
10363
+ };
10364
+ }
10321
10365
  if (areCoalescibleContractTypes(existing.type, params.type)) {
10322
10366
  return { contract: existing, persisted: false };
10323
10367
  }
@@ -10333,6 +10377,31 @@ var ContractManager = class _ContractManager {
10333
10377
  await this.config.contractRepository.saveContract(contract);
10334
10378
  return { contract, persisted: true };
10335
10379
  }
10380
+ /**
10381
+ * Overwrite a row this SDK cannot read with `params`, already verified to lock its script. One
10382
+ * write, so the script never goes without a row. The row's history (`createdAt`, `state`,
10383
+ * `watch`) survives, as do its `label` and `metadata` unless the caller passes new ones. The
10384
+ * first writer's generic-spend gate holds: a closed one stays closed, and an open one stays
10385
+ * open unless the caller sets the flag explicitly.
10386
+ */
10387
+ async replaceUnsupported(existing, params) {
10388
+ const label = params.label ?? existing.label;
10389
+ const given = params.metadata ?? existing.metadata;
10390
+ const wasOpen = existing.metadata?.genericallySpendable === true;
10391
+ const metadata = wasOpen && given?.genericallySpendable === void 0 ? { ...given, genericallySpendable: true } : given?.genericallySpendable === true && !wasOpen ? { ...given, genericallySpendable: false } : given;
10392
+ const { watch: _, label: _label, metadata: _metadata, ...rest } = params;
10393
+ const contract = {
10394
+ ...rest,
10395
+ ...label !== void 0 && { label },
10396
+ ...metadata !== void 0 && { metadata },
10397
+ ...existing.watch !== void 0 && { watch: existing.watch },
10398
+ createdAt: existing.createdAt,
10399
+ state: existing.state
10400
+ };
10401
+ await this.config.contractRepository.saveContract(contract);
10402
+ this.unsupportedContracts.delete(contract.script);
10403
+ return contract;
10404
+ }
10336
10405
  /**
10337
10406
  * Gap-limit contract discovery (error contract: {@link IContractManager.scanContracts}).
10338
10407
  * Hits go through {@link persistAndWatchContract}; `Wallet.restore` then hydrates with one
@@ -10488,6 +10557,11 @@ var ContractManager = class _ContractManager {
10488
10557
  const dbFilter = this.buildContractsDbFilter(filter ?? {});
10489
10558
  return await collectContracts(this.config.contractRepository, dbFilter);
10490
10559
  }
10560
+ /** @inheritdoc */
10561
+ async getUnsupportedContracts() {
10562
+ const contracts = await this.getContracts();
10563
+ return contracts.filter((contract) => this.isUnsupported(contract));
10564
+ }
10491
10565
  async getContractsWithVtxos(filter, pageSize, options) {
10492
10566
  assertNonNegativeMs("maxSyncAgeMs", options?.maxSyncAgeMs);
10493
10567
  const contracts = await this.getContracts(filter);
@@ -10560,11 +10634,12 @@ var ContractManager = class _ContractManager {
10560
10634
  const contracts = await collectContracts(this.config.contractRepository, {
10561
10635
  script: Array.from(new Set(vtxos.map((vtxo) => vtxo.script)))
10562
10636
  });
10563
- const { scripts, failures } = annotatableIn(
10637
+ const { scripts, failures, unsupported } = annotatableIn(
10564
10638
  new Map(contracts.map((contract) => [contract.script, contract])),
10565
10639
  vtxos,
10566
10640
  this.tapscriptMemo
10567
10641
  );
10642
+ for (const [script, { reason }] of unsupported) failures.set(script, reason);
10568
10643
  const orphans = vtxos.filter(
10569
10644
  (vtxo) => !scripts.has(vtxo.script) && !failures.has(vtxo.script)
10570
10645
  );
@@ -10806,9 +10881,10 @@ var ContractManager = class _ContractManager {
10806
10881
  const {
10807
10882
  scripts: annotatable,
10808
10883
  cache,
10809
- failures
10884
+ failures,
10885
+ unsupported
10810
10886
  } = annotatableIn(scriptToContract, vtxos, this.tapscriptMemo);
10811
- this.recordAnnotationFailures(annotatable, failures);
10887
+ this.recordAnnotationFailures(annotatable, failures, unsupported);
10812
10888
  const owned = vtxos.filter((v) => annotatable.has(v.script));
10813
10889
  if (owned.length === 0) return;
10814
10890
  const annotated = annotateAgainst(owned, scriptToContract, cache);
@@ -10994,9 +11070,10 @@ var ContractManager = class _ContractManager {
10994
11070
  const {
10995
11071
  scripts: annotatable,
10996
11072
  cache,
10997
- failures
11073
+ failures,
11074
+ unsupported
10998
11075
  } = annotatableIn(scriptToContract, vtxos, this.tapscriptMemo);
10999
- this.recordAnnotationFailures(annotatable, failures);
11076
+ this.recordAnnotationFailures(annotatable, failures, unsupported);
11000
11077
  const owned = vtxos.filter((v) => annotatable.has(v.script));
11001
11078
  const annotated = annotateAgainst(owned, scriptToContract, cache);
11002
11079
  const byContract = /* @__PURE__ */ new Map();
@@ -11085,9 +11162,10 @@ var ContractManager = class _ContractManager {
11085
11162
  const {
11086
11163
  scripts: annotatable,
11087
11164
  cache,
11088
- failures
11165
+ failures,
11166
+ unsupported
11089
11167
  } = annotatableIn(scriptToContract, vtxos, this.tapscriptMemo);
11090
- this.recordAnnotationFailures(annotatable, failures);
11168
+ this.recordAnnotationFailures(annotatable, failures, unsupported);
11091
11169
  const owned = vtxos.filter((v) => annotatable.has(v.script));
11092
11170
  const annotated = annotateAgainst(owned, scriptToContract, cache);
11093
11171
  for (const vtxo of annotated) {
@@ -12009,6 +12087,164 @@ var scriptVtxoCursorOf = (row) => ({
12009
12087
  txid: row.vtxo.txid,
12010
12088
  vout: row.vtxo.vout
12011
12089
  });
12090
+
12091
+ // src/providers/emulator.ts
12092
+ var RestEmulatorProvider = class {
12093
+ constructor(serverUrl) {
12094
+ this.serverUrl = serverUrl;
12095
+ }
12096
+ serverUrl;
12097
+ async getInfo() {
12098
+ const url = `${this.serverUrl}/v1/info`;
12099
+ const response = await baseFetch(url);
12100
+ if (!response.ok) {
12101
+ const errorText2 = await response.text();
12102
+ throw new Error(`Failed to get emulator info: ${errorText2}`);
12103
+ }
12104
+ const data = await response.json();
12105
+ const signerPubkey = data.signerPubkey;
12106
+ if (typeof signerPubkey !== "string" || !signerPubkey) {
12107
+ throw new Error("Invalid emulator info response: missing signerPubkey");
12108
+ }
12109
+ return { signerPubkey };
12110
+ }
12111
+ async submitTx(arkTx, checkpointTxs) {
12112
+ const url = `${this.serverUrl}/v1/tx`;
12113
+ const response = await fetch(url, {
12114
+ method: "POST",
12115
+ headers: { "Content-Type": "application/json" },
12116
+ body: JSON.stringify({
12117
+ arkTx,
12118
+ checkpointTxs
12119
+ })
12120
+ });
12121
+ if (!response.ok) {
12122
+ const errorText2 = await response.text();
12123
+ throw new Error(`Failed to submit tx to emulator: ${errorText2}`);
12124
+ }
12125
+ const data = await response.json();
12126
+ if (typeof data.signedArkTx !== "string" || !data.signedArkTx) {
12127
+ throw new Error("Invalid emulator submitTx response: missing signedArkTx");
12128
+ }
12129
+ if (!Array.isArray(data.signedCheckpointTxs) || !data.signedCheckpointTxs.every((item) => typeof item === "string")) {
12130
+ throw new Error(
12131
+ "Invalid emulator submitTx response: signedCheckpointTxs must be an array of strings"
12132
+ );
12133
+ }
12134
+ return {
12135
+ signedArkTx: data.signedArkTx,
12136
+ signedCheckpointTxs: data.signedCheckpointTxs
12137
+ };
12138
+ }
12139
+ async submitIntent(intent) {
12140
+ const url = `${this.serverUrl}/v1/intent`;
12141
+ const response = await fetch(url, {
12142
+ method: "POST",
12143
+ headers: { "Content-Type": "application/json" },
12144
+ body: JSON.stringify({
12145
+ intent: {
12146
+ proof: intent.proof,
12147
+ message: JSON.stringify(intent.message)
12148
+ }
12149
+ })
12150
+ });
12151
+ if (!response.ok) {
12152
+ const errorText2 = await response.text();
12153
+ throw new Error(`Failed to submit intent to emulator: ${errorText2}`);
12154
+ }
12155
+ const data = await response.json();
12156
+ if (typeof data.signedProof !== "string" || !data.signedProof) {
12157
+ throw new Error("Invalid emulator submitIntent response: missing signedProof");
12158
+ }
12159
+ return data.signedProof;
12160
+ }
12161
+ async submitFinalization(intent, forfeits, connectorTree, commitmentTx) {
12162
+ const url = `${this.serverUrl}/v1/finalization`;
12163
+ const response = await fetch(url, {
12164
+ method: "POST",
12165
+ headers: { "Content-Type": "application/json" },
12166
+ // Uses "signedIntent" (not "intent") because the proof was already
12167
+ // co-signed by the emulator via submitIntent in a prior step.
12168
+ body: JSON.stringify({
12169
+ signedIntent: {
12170
+ proof: intent.proof,
12171
+ message: JSON.stringify(intent.message)
12172
+ },
12173
+ forfeits,
12174
+ connectorTree,
12175
+ commitmentTx
12176
+ })
12177
+ });
12178
+ if (!response.ok) {
12179
+ const errorText2 = await response.text();
12180
+ throw new Error(`Failed to submit finalization to emulator: ${errorText2}`);
12181
+ }
12182
+ const data = await response.json();
12183
+ if (!Array.isArray(data.signedForfeits) || !data.signedForfeits.every((item) => typeof item === "string")) {
12184
+ throw new Error(
12185
+ "Invalid emulator submitFinalization response: signedForfeits must be an array of strings"
12186
+ );
12187
+ }
12188
+ if ("signedCommitmentTx" in data && typeof data.signedCommitmentTx !== "string") {
12189
+ throw new Error(
12190
+ "Invalid emulator submitFinalization response: invalid signedCommitmentTx"
12191
+ );
12192
+ }
12193
+ return {
12194
+ signedForfeits: data.signedForfeits,
12195
+ signedCommitmentTx: data.signedCommitmentTx
12196
+ };
12197
+ }
12198
+ async submitOnchainTx(tx) {
12199
+ const url = `${this.serverUrl}/v1/onchain-tx`;
12200
+ const response = await fetch(url, {
12201
+ method: "POST",
12202
+ headers: { "Content-Type": "application/json" },
12203
+ body: JSON.stringify({ tx })
12204
+ });
12205
+ if (!response.ok) {
12206
+ const errorText2 = await response.text();
12207
+ throw new Error(`Failed to submit onchain tx to emulator: ${errorText2}`);
12208
+ }
12209
+ const data = await response.json();
12210
+ if (typeof data.signedTx !== "string" || !data.signedTx) {
12211
+ throw new Error("Invalid emulator submitOnchainTx response: missing signedTx");
12212
+ }
12213
+ return { signedTx: data.signedTx };
12214
+ }
12215
+ };
12216
+
12217
+ // src/arkade/service.ts
12218
+ function arkadeServiceUrl(service) {
12219
+ return "operatorUrl" in service ? service.operatorUrl : DEFAULT_ARKADE_SERVER_URL;
12220
+ }
12221
+ async function resolveArkadeService(service, opts = {}) {
12222
+ const operatorUrl = arkadeServiceUrl(service);
12223
+ const arkProvider = opts.arkProvider ?? new RestArkProvider(operatorUrl);
12224
+ const info = opts.info ?? await arkProvider.getInfo();
12225
+ if ("network" in service && service.network !== info.network) {
12226
+ throw new Error(
12227
+ `Arkade service network mismatch: requested '${service.network}' but the operator at ${operatorUrl} reports '${info.network}'`
12228
+ );
12229
+ }
12230
+ const esploraUrl = ESPLORA_URL[info.network];
12231
+ if (!esploraUrl) {
12232
+ throw new Error(`Arkade service: no default esplora URL for network '${info.network}'`);
12233
+ }
12234
+ let emulator;
12235
+ if ("network" in service) {
12236
+ emulator = new RestEmulatorProvider(DEFAULT_ARKADE_EMULATOR_URL);
12237
+ } else if (service.emulatorUrl) {
12238
+ emulator = new RestEmulatorProvider(service.emulatorUrl);
12239
+ }
12240
+ return {
12241
+ arkProvider,
12242
+ indexerProvider: new RestIndexerProvider(operatorUrl),
12243
+ onchainProvider: new EsploraProvider(esploraUrl),
12244
+ emulator,
12245
+ info
12246
+ };
12247
+ }
12012
12248
  var HD_SETTINGS_KEY = "hd";
12013
12249
  var HARDENED_INDEX_OFFSET = 2147483648;
12014
12250
  var HDDescriptorProvider = class _HDDescriptorProvider {
@@ -12718,6 +12954,20 @@ function extractArkProviderUrl(provider) {
12718
12954
  const serverUrl = provider.serverUrl;
12719
12955
  return typeof serverUrl === "string" && serverUrl.length > 0 ? serverUrl : void 0;
12720
12956
  }
12957
+ function normalizeWalletConfigSpellings(config) {
12958
+ if (config.mode !== void 0 && config.walletMode !== void 0) {
12959
+ throw new Error("Pass either `mode` or `walletMode`, not both");
12960
+ }
12961
+ if (config.settlement !== void 0 && config.settlementConfig !== void 0) {
12962
+ throw new Error("Pass either `settlement` or `settlementConfig`, not both");
12963
+ }
12964
+ const { mode, settlement, ...rest } = config;
12965
+ return {
12966
+ ...rest,
12967
+ ...mode !== void 0 ? { walletMode: mode } : {},
12968
+ ...settlement !== void 0 ? { settlementConfig: settlement } : {}
12969
+ };
12970
+ }
12721
12971
  var DEFAULT_LOOK_AHEAD_WINDOW = 20;
12722
12972
  var MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD = 1e3;
12723
12973
  var MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
@@ -13130,9 +13380,14 @@ var ReadonlyWallet = class _ReadonlyWallet {
13130
13380
  }
13131
13381
  /** Shared setup for ReadonlyWallet.create() and Wallet.create(). */
13132
13382
  static async setupWalletConfig(config, pubKey) {
13133
- const arkProvider = config.arkProvider || new RestArkProvider();
13383
+ if (config.service && (config.arkProvider || config.indexerProvider || config.onchainProvider)) {
13384
+ throw new Error(
13385
+ "Pass either `service` or `arkProvider`/`indexerProvider`/`onchainProvider`, not both"
13386
+ );
13387
+ }
13388
+ const arkProvider = config.arkProvider || new RestArkProvider(config.service ? arkadeServiceUrl(config.service) : void 0);
13134
13389
  let indexerProvider = config.indexerProvider;
13135
- if (!indexerProvider) {
13390
+ if (!indexerProvider && !config.service) {
13136
13391
  const derived = extractArkProviderUrl(arkProvider);
13137
13392
  if (!derived && config.arkProvider) {
13138
13393
  throw new Error(
@@ -13148,6 +13403,11 @@ var ReadonlyWallet = class _ReadonlyWallet {
13148
13403
  source: serverInfoSource,
13149
13404
  lastOnlineAt: serverInfoLastOnlineAt
13150
13405
  } = await resolveArkInfo(arkProvider, walletRepository);
13406
+ const resolvedService = config.service ? await resolveArkadeService(config.service, { arkProvider, info }) : void 0;
13407
+ if (!indexerProvider) {
13408
+ if (!resolvedService) throw new Error("indexerProvider is unresolved");
13409
+ indexerProvider = resolvedService.indexerProvider;
13410
+ }
13151
13411
  const network = networkFromArkadeInfo(info);
13152
13412
  if ("descriptor" in config.identity) {
13153
13413
  const descriptor = config.identity.descriptor;
@@ -13164,7 +13424,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
13164
13424
  );
13165
13425
  }
13166
13426
  }
13167
- const onchainProvider = typeof config.onchainProvider === "function" ? config.onchainProvider(info.network) : config.onchainProvider || new EsploraProvider(ESPLORA_URL[info.network]);
13427
+ const onchainProvider = typeof config.onchainProvider === "function" ? config.onchainProvider(info.network) : config.onchainProvider || resolvedService?.onchainProvider || new EsploraProvider(ESPLORA_URL[info.network]);
13168
13428
  if (config.exitTimelock) {
13169
13429
  const { value, type } = config.exitTimelock;
13170
13430
  if (value < 512n && type !== "blocks" || value >= 512n && type !== "seconds") {
@@ -14920,6 +15180,7 @@ var Wallet2 = class extends ReadonlyWallet {
14920
15180
  }
14921
15181
  }
14922
15182
  static async create(config) {
15183
+ config = normalizeWalletConfigSpellings(config);
14923
15184
  if (config.lookAheadWindow !== void 0 && (!Number.isInteger(config.lookAheadWindow) || config.lookAheadWindow <= 0)) {
14924
15185
  throw new Error(
14925
15186
  `lookAheadWindow must be a positive integer (got ${String(config.lookAheadWindow)})`
@@ -18735,6 +18996,15 @@ var WalletMessageHandler = class {
18735
18996
  payload: { paths }
18736
18997
  });
18737
18998
  }
18999
+ case "GET_UNSUPPORTED_CONTRACTS": {
19000
+ const manager = await this.readonlyWallet.getContractManager();
19001
+ const contracts = await manager.getUnsupportedContracts();
19002
+ return this.tagged({
19003
+ id,
19004
+ type: "UNSUPPORTED_CONTRACTS",
19005
+ payload: { contracts }
19006
+ });
19007
+ }
18738
19008
  case "IS_CONTRACT_MANAGER_WATCHING": {
18739
19009
  const manager = await this.readonlyWallet.getContractManager();
18740
19010
  const isWatching = await manager.isWatching();
@@ -19580,6 +19850,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
19580
19850
  ANNOTATE_VTXOS: 2e4,
19581
19851
  GET_SPENDABLE_PATHS: 2e4,
19582
19852
  GET_ALL_SPENDING_PATHS: 2e4,
19853
+ GET_UNSUPPORTED_CONTRACTS: 2e4,
19583
19854
  GET_ASSET_DETAILS: 2e4,
19584
19855
  GET_EXPIRING_VTXOS: 2e4,
19585
19856
  GET_EXPIRED_BOARDING_UTXOS: 2e4,
@@ -19650,6 +19921,7 @@ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
19650
19921
  "ANNOTATE_VTXOS",
19651
19922
  "GET_SPENDABLE_PATHS",
19652
19923
  "GET_ALL_SPENDING_PATHS",
19924
+ "GET_UNSUPPORTED_CONTRACTS",
19653
19925
  "GET_ASSET_DETAILS",
19654
19926
  "GET_EXPIRING_VTXOS",
19655
19927
  "RELOAD_WALLET"
@@ -20568,6 +20840,19 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
20568
20840
  throw new Error(`Failed to get all spending paths: ${cause}`, { cause });
20569
20841
  }
20570
20842
  },
20843
+ async getUnsupportedContracts() {
20844
+ const message = {
20845
+ type: "GET_UNSUPPORTED_CONTRACTS",
20846
+ id: getRandomId(),
20847
+ tag: messageTag
20848
+ };
20849
+ try {
20850
+ const response = await sendContractMessage(message);
20851
+ return response.payload.contracts;
20852
+ } catch (cause) {
20853
+ throw new Error(`Failed to get unsupported contracts: ${cause}`, { cause });
20854
+ }
20855
+ },
20571
20856
  onContractEvent(callback) {
20572
20857
  const messageHandler = (event) => {
20573
20858
  const response = event.data;
@@ -22545,132 +22830,6 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
22545
22830
  });
22546
22831
  return tx;
22547
22832
  }
22548
-
22549
- // src/providers/emulator.ts
22550
- var RestEmulatorProvider = class {
22551
- constructor(serverUrl) {
22552
- this.serverUrl = serverUrl;
22553
- }
22554
- serverUrl;
22555
- async getInfo() {
22556
- const url = `${this.serverUrl}/v1/info`;
22557
- const response = await baseFetch(url);
22558
- if (!response.ok) {
22559
- const errorText2 = await response.text();
22560
- throw new Error(`Failed to get emulator info: ${errorText2}`);
22561
- }
22562
- const data = await response.json();
22563
- const signerPubkey = data.signerPubkey;
22564
- if (typeof signerPubkey !== "string" || !signerPubkey) {
22565
- throw new Error("Invalid emulator info response: missing signerPubkey");
22566
- }
22567
- return { signerPubkey };
22568
- }
22569
- async submitTx(arkTx, checkpointTxs) {
22570
- const url = `${this.serverUrl}/v1/tx`;
22571
- const response = await fetch(url, {
22572
- method: "POST",
22573
- headers: { "Content-Type": "application/json" },
22574
- body: JSON.stringify({
22575
- arkTx,
22576
- checkpointTxs
22577
- })
22578
- });
22579
- if (!response.ok) {
22580
- const errorText2 = await response.text();
22581
- throw new Error(`Failed to submit tx to emulator: ${errorText2}`);
22582
- }
22583
- const data = await response.json();
22584
- if (typeof data.signedArkTx !== "string" || !data.signedArkTx) {
22585
- throw new Error("Invalid emulator submitTx response: missing signedArkTx");
22586
- }
22587
- if (!Array.isArray(data.signedCheckpointTxs) || !data.signedCheckpointTxs.every((item) => typeof item === "string")) {
22588
- throw new Error(
22589
- "Invalid emulator submitTx response: signedCheckpointTxs must be an array of strings"
22590
- );
22591
- }
22592
- return {
22593
- signedArkTx: data.signedArkTx,
22594
- signedCheckpointTxs: data.signedCheckpointTxs
22595
- };
22596
- }
22597
- async submitIntent(intent) {
22598
- const url = `${this.serverUrl}/v1/intent`;
22599
- const response = await fetch(url, {
22600
- method: "POST",
22601
- headers: { "Content-Type": "application/json" },
22602
- body: JSON.stringify({
22603
- intent: {
22604
- proof: intent.proof,
22605
- message: JSON.stringify(intent.message)
22606
- }
22607
- })
22608
- });
22609
- if (!response.ok) {
22610
- const errorText2 = await response.text();
22611
- throw new Error(`Failed to submit intent to emulator: ${errorText2}`);
22612
- }
22613
- const data = await response.json();
22614
- if (typeof data.signedProof !== "string" || !data.signedProof) {
22615
- throw new Error("Invalid emulator submitIntent response: missing signedProof");
22616
- }
22617
- return data.signedProof;
22618
- }
22619
- async submitFinalization(intent, forfeits, connectorTree, commitmentTx) {
22620
- const url = `${this.serverUrl}/v1/finalization`;
22621
- const response = await fetch(url, {
22622
- method: "POST",
22623
- headers: { "Content-Type": "application/json" },
22624
- // Uses "signedIntent" (not "intent") because the proof was already
22625
- // co-signed by the emulator via submitIntent in a prior step.
22626
- body: JSON.stringify({
22627
- signedIntent: {
22628
- proof: intent.proof,
22629
- message: JSON.stringify(intent.message)
22630
- },
22631
- forfeits,
22632
- connectorTree,
22633
- commitmentTx
22634
- })
22635
- });
22636
- if (!response.ok) {
22637
- const errorText2 = await response.text();
22638
- throw new Error(`Failed to submit finalization to emulator: ${errorText2}`);
22639
- }
22640
- const data = await response.json();
22641
- if (!Array.isArray(data.signedForfeits) || !data.signedForfeits.every((item) => typeof item === "string")) {
22642
- throw new Error(
22643
- "Invalid emulator submitFinalization response: signedForfeits must be an array of strings"
22644
- );
22645
- }
22646
- if ("signedCommitmentTx" in data && typeof data.signedCommitmentTx !== "string") {
22647
- throw new Error(
22648
- "Invalid emulator submitFinalization response: invalid signedCommitmentTx"
22649
- );
22650
- }
22651
- return {
22652
- signedForfeits: data.signedForfeits,
22653
- signedCommitmentTx: data.signedCommitmentTx
22654
- };
22655
- }
22656
- async submitOnchainTx(tx) {
22657
- const url = `${this.serverUrl}/v1/onchain-tx`;
22658
- const response = await fetch(url, {
22659
- method: "POST",
22660
- headers: { "Content-Type": "application/json" },
22661
- body: JSON.stringify({ tx })
22662
- });
22663
- if (!response.ok) {
22664
- const errorText2 = await response.text();
22665
- throw new Error(`Failed to submit onchain tx to emulator: ${errorText2}`);
22666
- }
22667
- const data = await response.json();
22668
- if (typeof data.signedTx !== "string" || !data.signedTx) {
22669
- throw new Error("Invalid emulator submitOnchainTx response: missing signedTx");
22670
- }
22671
- return { signedTx: data.signedTx };
22672
- }
22673
- };
22674
22833
  function finalizeVirtualTx(type, psbtBase64) {
22675
22834
  const tx = Transaction.fromPSBT(base64.decode(psbtBase64));
22676
22835
  if (type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
@@ -23858,6 +24017,7 @@ __export(arkade_exports, {
23858
24017
  OPCODE_VALUES: () => OPCODE_VALUES,
23859
24018
  SUPPORTED_PROGRAM_VERSION: () => SUPPORTED_PROGRAM_VERSION,
23860
24019
  Script: () => Script,
24020
+ UnsupportedProgramVersionError: () => UnsupportedProgramVersionError,
23861
24021
  arkadeScriptHash: () => arkadeScriptHash,
23862
24022
  arkadeWitnessHash: () => arkadeWitnessHash,
23863
24023
  asmToBytes: () => asmToBytes,
@@ -23871,6 +24031,7 @@ __export(arkade_exports, {
23871
24031
  isContractArtifact: () => isContractArtifact,
23872
24032
  parseArtifact: () => parseArtifact,
23873
24033
  programFromArtifact: () => programFromArtifact,
24034
+ resolveArkadeService: () => resolveArkadeService,
23874
24035
  resolveAsm: () => resolveAsm,
23875
24036
  serializeArkadeContractParams: () => serializeArkadeContractParams,
23876
24037
  stringifyArtifact: () => stringifyArtifact,
@@ -24080,6 +24241,24 @@ var NATIVE_STRUCTS = {
24080
24241
  ECPoint: [
24081
24242
  ["x", "int"],
24082
24243
  ["y", "int"]
24244
+ ],
24245
+ ScriptPubKey: [
24246
+ ["program", "bytes"],
24247
+ ["version", "int"]
24248
+ ],
24249
+ AssetEntry: [
24250
+ ["assetId", "AssetId"],
24251
+ ["amount", "int"]
24252
+ ],
24253
+ AssetLookup: [
24254
+ ["amount", "int"],
24255
+ ["found", "bool"]
24256
+ ],
24257
+ G2Point: [
24258
+ ["xC1", "int"],
24259
+ ["xC0", "int"],
24260
+ ["yC1", "int"],
24261
+ ["yC0", "int"]
24083
24262
  ]
24084
24263
  };
24085
24264
  var HASH_OPCODES = /* @__PURE__ */ new Set(["OP_SHA256", "OP_HASH160", "OP_HASH256", "OP_RIPEMD160"]);
@@ -24087,7 +24266,7 @@ function isRecord(value) {
24087
24266
  return typeof value === "object" && value !== null && !Array.isArray(value);
24088
24267
  }
24089
24268
  function isNamed(value) {
24090
- return isRecord(value) && typeof value.name === "string" && typeof value.type === "string" && value.type.length > 0;
24269
+ return isRecord(value) && typeof value.name === "string" && value.name.length > 0 && typeof value.type === "string" && value.type.length > 0;
24091
24270
  }
24092
24271
  function isContractArtifact(value) {
24093
24272
  if (!isRecord(value) || typeof value.contractName !== "string") return false;
@@ -24095,7 +24274,7 @@ function isContractArtifact(value) {
24095
24274
  if (value.structs !== void 0 && !Array.isArray(value.structs)) return false;
24096
24275
  if (!Array.isArray(value.functions) || value.functions.length === 0) return false;
24097
24276
  return value.functions.every(
24098
- (group) => isRecord(group) && typeof group.name === "string" && Array.isArray(group.leaves) && group.leaves.length > 0
24277
+ (group) => isRecord(group) && typeof group.name === "string" && group.name.length > 0 && Array.isArray(group.leaves) && group.leaves.length > 0
24099
24278
  );
24100
24279
  }
24101
24280
  function fail(detail) {
@@ -24105,33 +24284,35 @@ function claim(seen, name, kind) {
24105
24284
  if (seen.has(name)) fail(`duplicate ${kind} '${name}'`);
24106
24285
  seen.add(name);
24107
24286
  }
24108
- var SCALAR_TYPES = {
24109
- pubkey: "pubkey",
24110
- signature: "sig",
24111
- bytes: "bytes",
24112
- bytes20: "hash",
24113
- bytes32: "hash",
24114
- asset: "hash",
24115
- int: "int",
24116
- bool: "int"
24117
- };
24287
+ function inputDef(name, type) {
24288
+ return { name, type: argType(type) };
24289
+ }
24118
24290
  function argType(arkType) {
24119
- if (!Object.hasOwn(SCALAR_TYPES, arkType)) fail(`unknown type '${arkType}'`);
24120
- return SCALAR_TYPES[arkType];
24291
+ if (!Object.hasOwn(ARKADE_TYPE_ENCODINGS, arkType)) fail(`unknown type '${arkType}'`);
24292
+ return arkType;
24121
24293
  }
24122
24294
  function arrayParts(type) {
24123
24295
  const open = type.indexOf("[");
24124
24296
  if (open < 0 || !type.endsWith("]")) return [type, 0];
24125
24297
  const length = Number(type.slice(open + 1, -1));
24126
- return Number.isInteger(length) && length > 0 ? [type.slice(0, open), length] : [type, 0];
24298
+ return Number.isSafeInteger(length) && length > 0 && /^\d+$/.test(type.slice(open + 1, -1)) ? [type.slice(0, open), length] : [type, 0];
24127
24299
  }
24128
24300
  function flatten(param, structs, stack = []) {
24129
24301
  if (!isNamed(param)) fail("parameter needs a name and a type");
24302
+ const [element, length] = arrayParts(param.type);
24303
+ if (length > 0) {
24304
+ return Array.from(
24305
+ { length },
24306
+ (_, index) => flatten({ name: `${param.name}.${index}`, type: element }, structs, stack)
24307
+ ).flat();
24308
+ }
24309
+ if (param.type.includes("[") || param.type.includes("]")) {
24310
+ fail(`invalid array type '${param.type}'`);
24311
+ }
24130
24312
  if (Object.hasOwn(NATIVE_STRUCTS, param.type)) {
24131
- return NATIVE_STRUCTS[param.type].map(([field, type]) => ({
24132
- name: `${param.name}.${field}`,
24133
- type: argType(type)
24134
- }));
24313
+ return NATIVE_STRUCTS[param.type].flatMap(
24314
+ ([field, type]) => flatten({ name: `${param.name}.${field}`, type }, structs, stack)
24315
+ );
24135
24316
  }
24136
24317
  const declared = structs.find((struct) => struct?.name === param.type);
24137
24318
  if (declared) {
@@ -24143,20 +24324,7 @@ function flatten(param, structs, stack = []) {
24143
24324
  ])
24144
24325
  );
24145
24326
  }
24146
- const [element, length] = arrayParts(param.type);
24147
- if (length === 0) {
24148
- if (param.type.includes("[") || param.type.includes("]")) {
24149
- fail(`invalid array type '${param.type}'`);
24150
- }
24151
- return [{ name: param.name, type: argType(param.type) }];
24152
- }
24153
- if (Object.hasOwn(NATIVE_STRUCTS, element) || structs.some((struct) => struct.name === element)) {
24154
- fail(`arrays of structs are not supported: '${param.type}'`);
24155
- }
24156
- return Array.from({ length }, (_, index) => ({
24157
- name: `${param.name}.${index}`,
24158
- type: argType(element)
24159
- }));
24327
+ return [inputDef(param.name, param.type)];
24160
24328
  }
24161
24329
  function opcodeToken(token) {
24162
24330
  const base = token.slice(3);
@@ -24203,6 +24371,15 @@ function providedWitness(witness) {
24203
24371
  if (!Array.isArray(witness)) fail("leaf witness must be an array");
24204
24372
  return witness.filter((item) => {
24205
24373
  if (!isNamed(item)) fail("witness item needs a name and a type");
24374
+ const type = argType(item.type);
24375
+ if (item.injected !== void 0 && typeof item.injected !== "boolean")
24376
+ fail(`witness '${item.name}': injected must be boolean`);
24377
+ if (item.injected && item.type !== "signature")
24378
+ fail(`witness '${item.name}': only infrastructure signatures can be injected`);
24379
+ if (item.encoding !== void 0 && item.encoding !== ARKADE_TYPE_ENCODINGS[type])
24380
+ fail(
24381
+ `witness '${item.name}': encoding '${item.encoding}' does not match type '${item.type}'`
24382
+ );
24206
24383
  return !item.injected && item.type !== "signature";
24207
24384
  });
24208
24385
  }
@@ -24218,11 +24395,12 @@ function parseLeaf(leaf, hasCovenant, instantiations, extras) {
24218
24395
  const asm = leaf.asm;
24219
24396
  let index = 0;
24220
24397
  let condition;
24221
- if (asm.length >= 4 && HASH_OPCODES.has(asm[0]) && asm[2] === "OP_EQUAL") {
24222
- if (asm[3] !== "OP_VERIFY")
24398
+ const hashIndex = asm[0] === "OP_SIZE" && (asm[1] === "20" || asm[1] === "32") && asm[2] === "OP_EQUALVERIFY" ? 3 : 0;
24399
+ if (HASH_OPCODES.has(asm[hashIndex]) && asm[hashIndex + 2] === "OP_EQUAL") {
24400
+ if (asm[hashIndex + 3] !== "OP_VERIFY")
24223
24401
  fail(`leaf '${leaf.name}': hash condition must end in OP_VERIFY`);
24224
- condition = asm.slice(0, 3).map((token) => asmToken(token, instantiations));
24225
- index = 4;
24402
+ condition = asm.slice(0, hashIndex + 3).map((token) => asmToken(token, instantiations));
24403
+ index = hashIndex + 4;
24226
24404
  }
24227
24405
  let csv;
24228
24406
  let cltv;
@@ -24235,10 +24413,14 @@ function parseLeaf(leaf, hasCovenant, instantiations, extras) {
24235
24413
  }
24236
24414
  const signers = [];
24237
24415
  let emulator;
24416
+ let emulatorIndex;
24238
24417
  while (index < asm.length) {
24239
24418
  const key = asm[index];
24240
24419
  const terminator = asm[index + 1];
24241
24420
  const last = terminator === "OP_CHECKSIG";
24421
+ if (last !== (index + 2 === asm.length)) {
24422
+ fail(`leaf '${leaf.name}': only the final signer must end in OP_CHECKSIG`);
24423
+ }
24242
24424
  if (!last && terminator !== "OP_CHECKSIGVERIFY") {
24243
24425
  fail(`leaf '${leaf.name}': expected CHECKSIG after '${key}', found '${terminator}'`);
24244
24426
  }
@@ -24253,8 +24435,9 @@ function parseLeaf(leaf, hasCovenant, instantiations, extras) {
24253
24435
  signers.push({ tweak: `$${base}`, fn });
24254
24436
  } else if (inner.startsWith("EMULATOR_KEY:")) {
24255
24437
  if (!hasCovenant) fail(`leaf '${leaf.name}': ${key} needs a covenant`);
24256
- if (!last) fail(`leaf '${leaf.name}': ${key} must be the last signer`);
24438
+ if (emulator !== void 0) fail(`leaf '${leaf.name}': duplicate emulator signer`);
24257
24439
  emulator = inner.slice("EMULATOR_KEY:".length);
24440
+ emulatorIndex = signers.length;
24258
24441
  } else signers.push(`$${inner}`);
24259
24442
  index += 2;
24260
24443
  }
@@ -24264,10 +24447,12 @@ function parseLeaf(leaf, hasCovenant, instantiations, extras) {
24264
24447
  `leaf '${leaf.name}': covenant leaf must sign with the emulator or a tweaked constructor key`
24265
24448
  );
24266
24449
  }
24267
- if (signers.length === 0) fail(`leaf '${leaf.name}': at least one named signer is required`);
24450
+ if (signers.length === 0 && emulator === void 0)
24451
+ fail(`leaf '${leaf.name}': at least one named signer is required`);
24268
24452
  return {
24269
24453
  signers,
24270
24454
  ...emulator !== void 0 ? { emulator } : hasCovenant ? { emulator: null } : {},
24455
+ ...emulatorIndex !== void 0 ? { emulatorIndex } : {},
24271
24456
  ...condition ? { asm: condition } : {},
24272
24457
  ...csv ? { csv } : {},
24273
24458
  ...cltv !== void 0 ? { cltv } : {},
@@ -24280,21 +24465,34 @@ function programFromArtifact(artifact) {
24280
24465
  "expected a complete arkadec artifact with contractName, constructorInputs, and non-empty functions"
24281
24466
  );
24282
24467
  }
24468
+ if (artifact.formatVersion !== void 0 && artifact.formatVersion !== 1) {
24469
+ fail(`unsupported artifact format version ${artifact.formatVersion}; expected 1`);
24470
+ }
24283
24471
  const structs = artifact.structs ?? [];
24284
24472
  const structNames = /* @__PURE__ */ new Set();
24285
24473
  for (const struct of structs) {
24286
- if (!isRecord(struct) || typeof struct.name !== "string") continue;
24287
- if (Object.hasOwn(SCALAR_TYPES, struct.name) || Object.hasOwn(NATIVE_STRUCTS, struct.name)) {
24474
+ if (!isRecord(struct) || typeof struct.name !== "string" || !Array.isArray(struct.fields)) {
24475
+ fail("struct needs a name and fields array");
24476
+ }
24477
+ const fields = /* @__PURE__ */ new Set();
24478
+ for (const field of struct.fields) {
24479
+ if (!isNamed(field)) fail(`struct '${struct.name}': field needs a name and type`);
24480
+ claim(fields, field.name, "struct field");
24481
+ }
24482
+ if (Object.hasOwn(ARKADE_TYPE_ENCODINGS, struct.name) || Object.hasOwn(NATIVE_STRUCTS, struct.name)) {
24288
24483
  fail(`struct name '${struct.name}' shadows a built-in type`);
24289
24484
  }
24290
24485
  claim(structNames, struct.name, "struct");
24291
24486
  }
24292
24487
  const instantiations = /* @__PURE__ */ new Map();
24293
- const functions = {};
24488
+ const functions = /* @__PURE__ */ Object.create(null);
24294
24489
  const groups = /* @__PURE__ */ new Set();
24295
24490
  const leafNames = /* @__PURE__ */ new Set();
24296
24491
  for (const group of artifact.functions) {
24297
24492
  claim(groups, group.name, "spend group");
24493
+ if (group.arkade !== void 0 && (!isRecord(group.arkade) || !Array.isArray(group.arkade.inputs) || !Array.isArray(group.arkade.asm) || !group.arkade.asm.every((t) => typeof t === "string"))) {
24494
+ fail(`group '${group.name}': covenant needs inputs and string assembly arrays`);
24495
+ }
24298
24496
  const covenantInputs = (group.arkade?.inputs ?? []).flatMap(
24299
24497
  (input) => flatten(input, structs)
24300
24498
  );
@@ -24303,16 +24501,49 @@ function programFromArtifact(artifact) {
24303
24501
  // Covenant witness is reverse declaration order.
24304
24502
  witness: covenantInputs.map((field) => field.name).reverse()
24305
24503
  } : void 0;
24306
- for (const [index, leaf] of group.leaves.entries()) {
24307
- const name = index === 0 ? group.name : `${group.name}/${index}:${leaf.name}`;
24504
+ const covenantNames = /* @__PURE__ */ new Set();
24505
+ for (const input of covenantInputs) claim(covenantNames, input.name, "covenant input");
24506
+ for (const leaf of group.leaves) {
24507
+ if (!isRecord(leaf) || typeof leaf.name !== "string" || leaf.name.length === 0 || !Array.isArray(leaf.asm) || leaf.asm.length === 0 || !leaf.asm.every((t) => typeof t === "string")) {
24508
+ fail(`group '${group.name}': leaf needs a name and non-empty string assembly`);
24509
+ }
24510
+ const name = leaf.name;
24308
24511
  claim(leafNames, name, "function name");
24309
24512
  const extras = providedWitness(leaf.witness);
24310
- const tapscript = parseLeaf(leaf, group.arkade !== void 0, instantiations, extras);
24513
+ for (const input of extras) {
24514
+ if (leaf.asm.includes(`<${input.name}>`)) {
24515
+ fail(
24516
+ `leaf '${leaf.name}': call input '${input.name}' is a script operand; it must be a constructor binding to derive a fixed Taproot tree`
24517
+ );
24518
+ }
24519
+ }
24520
+ const witnessNames = /* @__PURE__ */ new Set();
24521
+ for (const item of leaf.witness ?? []) claim(witnessNames, item.name, "leaf witness");
24522
+ const scopedExtras = extras.map((item) => ({
24523
+ ...item,
24524
+ name: covenantNames.has(item.name) ? `tapscript:${item.name}` : item.name
24525
+ }));
24526
+ const tapscript = parseLeaf(
24527
+ leaf,
24528
+ group.arkade !== void 0,
24529
+ instantiations,
24530
+ scopedExtras
24531
+ );
24532
+ if (tapscript.emulator !== void 0 && tapscript.emulator !== null && tapscript.emulator !== group.name) {
24533
+ fail(
24534
+ `leaf '${leaf.name}': emulator covenant '${tapscript.emulator}' must match group '${group.name}'`
24535
+ );
24536
+ }
24537
+ const signatures = (leaf.witness ?? []).filter((item) => item.type === "signature" && !item.injected).map((item) => inputDef(item.name, item.type));
24538
+ if (signatures.length > 0) tapscript.signatures = signatures;
24311
24539
  const inputs = [
24312
24540
  ...covenantInputs,
24313
- ...extras.map((item) => ({ name: item.name, type: argType(item.type) }))
24541
+ ...scopedExtras.map((item) => inputDef(item.name, item.type))
24314
24542
  ];
24543
+ const inputNames = /* @__PURE__ */ new Set();
24544
+ for (const input of inputs) claim(inputNames, input.name, "function input");
24315
24545
  functions[name] = {
24546
+ group: group.name,
24316
24547
  ...inputs.length > 0 ? { inputs } : {},
24317
24548
  tapscript,
24318
24549
  ...arkadeScript ? { arkadeScript } : {}
@@ -24322,7 +24553,11 @@ function programFromArtifact(artifact) {
24322
24553
  const params = [
24323
24554
  ...artifact.constructorInputs.flatMap((input) => flatten(input, structs)),
24324
24555
  { name: "server", type: "pubkey" },
24325
- ...[...instantiations.keys()].map((name) => ({ name, type: "hash" }))
24556
+ ...Object.values(functions).some((fn) => fn.tapscript.csv?.value === "$SERVER_EXIT_DELAY") ? [{ name: "SERVER_EXIT_DELAY", type: "int" }] : [],
24557
+ ...[...instantiations.keys()].map((name) => ({
24558
+ name,
24559
+ type: "bytes32"
24560
+ }))
24326
24561
  ];
24327
24562
  const paramNames = /* @__PURE__ */ new Set();
24328
24563
  for (const param of params) claim(paramNames, param.name, "program parameter");
@@ -24339,6 +24574,8 @@ var Arkade = class _Arkade {
24339
24574
  emulator;
24340
24575
  network;
24341
24576
  serverKey;
24577
+ /** Bare protocol delay (blocks below 512, seconds otherwise), resolved only when needed. */
24578
+ serverExitDelay;
24342
24579
  /**
24343
24580
  * The co-signer key covenants are built against (33-byte compressed), present only when an
24344
24581
  * emulator is configured. Resolved from the network or `emulatorPubkey`, never the emulator's
@@ -24351,62 +24588,198 @@ var Arkade = class _Arkade {
24351
24588
  identity;
24352
24589
  /** The signing identity's x-only public key, resolved at connect — identifies which inputs the wallet signs. */
24353
24590
  userKey;
24354
- /** The wallet's contract manager, when contract persistence is wired up. */
24591
+ /** The contract manager, when contract persistence is wired up (passed in or owned). */
24355
24592
  contractManager;
24593
+ /** Whether {@link connect} built {@link contractManager}, so {@link dispose} must stop it. */
24594
+ ownsContractManager;
24595
+ /** {@link ArkadeContract.on} handles by contract script (hex). */
24596
+ handles = /* @__PURE__ */ new Map();
24597
+ /** The single `onContractEvent` subscription fanning out to {@link handles}. */
24598
+ unsubscribeEvents;
24599
+ disposed = false;
24356
24600
  constructor(fields) {
24357
24601
  this.arkProvider = fields.arkProvider;
24358
24602
  this.emulator = fields.emulator;
24359
24603
  this.network = fields.network;
24360
24604
  this.serverKey = fields.serverKey;
24605
+ this.serverExitDelay = fields.serverExitDelay;
24361
24606
  this.emulatorKey = fields.emulatorKey;
24362
24607
  this.checkpoint = fields.checkpoint;
24363
24608
  this.indexer = fields.indexer;
24364
24609
  this.identity = fields.identity;
24365
24610
  this.userKey = fields.userKey;
24366
24611
  this.contractManager = fields.contractManager;
24612
+ this.ownsContractManager = fields.ownsContractManager ?? false;
24367
24613
  }
24368
- /** Connect and resolve the server key, checkpoint closure and (if present) the co-signer key. */
24614
+ /**
24615
+ * Connect and resolve the server key, checkpoint closure and (if present) the co-signer key.
24616
+ *
24617
+ * @example
24618
+ * ```typescript
24619
+ * const arkade = await Arkade.connect({
24620
+ * service: { network: "bitcoin" },
24621
+ * identity,
24622
+ * storage: { contractRepository },
24623
+ * });
24624
+ * ```
24625
+ */
24369
24626
  static async connect(opts) {
24370
- const info = await opts.arkade.getInfo();
24627
+ if (opts.storage && opts.contractManager) {
24628
+ throw new Error(
24629
+ "Arkade.connect: pass either `storage` or `contractManager`, not both \u2014 `storage` builds a second manager over another repository"
24630
+ );
24631
+ }
24632
+ let arkProvider;
24633
+ let emulator;
24634
+ let indexer;
24635
+ let info;
24636
+ let resolved;
24637
+ if (opts.service) {
24638
+ resolved = await resolveArkadeService(opts.service);
24639
+ ({ arkProvider, emulator, info } = resolved);
24640
+ indexer = resolved.indexerProvider;
24641
+ } else {
24642
+ arkProvider = opts.arkade;
24643
+ emulator = opts.emulator;
24644
+ indexer = opts.indexer;
24645
+ info = await opts.arkade.getInfo();
24646
+ }
24371
24647
  const serverKey = toXOnly(hex.decode(info.signerPubkey), "ark signer key");
24372
24648
  const checkpoint = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
24373
24649
  const network = opts.network ?? (Object.hasOwn(networks, info.network) ? getNetwork(info.network) : DEFAULT_NETWORK);
24374
24650
  let emulatorKey;
24375
- if (opts.emulator) {
24651
+ if (emulator) {
24376
24652
  emulatorKey = hex.decode(resolveEmulatorPubkey(network, opts.emulatorPubkey));
24377
24653
  }
24378
24654
  let userKey;
24379
24655
  if (opts.identity) {
24380
24656
  userKey = toXOnly(await opts.identity.xOnlyPublicKey(), "identity key");
24381
24657
  }
24658
+ let contractManager = opts.contractManager;
24659
+ let ownsContractManager = false;
24660
+ if (resolved && opts.storage) {
24661
+ const { onchainProvider } = resolved;
24662
+ contractManager = await ContractManager.create({
24663
+ indexerProvider: resolved.indexerProvider,
24664
+ contractRepository: opts.storage.contractRepository,
24665
+ // Mandatory, and session-scoped: VTXOs are re-read from the indexer on reconnect.
24666
+ walletRepository: new InMemoryWalletRepository(),
24667
+ // Parity with the wallet-owned manager: without it every height-typed CLTV reads
24668
+ // as unsatisfied. @see ContractManagerConfig.chainTip
24669
+ chainTip: async () => {
24670
+ const { height, time } = await onchainProvider.getChainTip();
24671
+ return { height, time };
24672
+ }
24673
+ });
24674
+ ownsContractManager = true;
24675
+ }
24382
24676
  return new _Arkade({
24383
- arkProvider: opts.arkade,
24384
- emulator: opts.emulator,
24677
+ arkProvider,
24678
+ emulator,
24385
24679
  network,
24386
24680
  serverKey,
24681
+ serverExitDelay: info.unilateralExitDelay,
24387
24682
  emulatorKey,
24388
24683
  checkpoint,
24389
- indexer: opts.indexer,
24684
+ indexer,
24390
24685
  identity: opts.identity,
24391
24686
  userKey,
24392
- contractManager: opts.contractManager
24687
+ contractManager,
24688
+ ownsContractManager
24393
24689
  });
24394
24690
  }
24691
+ /**
24692
+ * Stop event delivery to every {@link ArkadeContract.on} handle and, when {@link connect}
24693
+ * built the contract manager (`storage`), dispose it. A caller-passed manager keeps running.
24694
+ */
24695
+ dispose() {
24696
+ this.disposed = true;
24697
+ this.unsubscribeEvents?.();
24698
+ this.unsubscribeEvents = void 0;
24699
+ this.handles.clear();
24700
+ if (this.ownsContractManager) this.contractManager?.dispose();
24701
+ }
24702
+ /**
24703
+ * @internal Add a handle for `script`, subscribing to the manager on first use; the last
24704
+ * handle's `off()` releases the subscription.
24705
+ */
24706
+ addEventHandle(manager, script, handle) {
24707
+ if (this.disposed) throw new Error("ArkadeContract.on: the Arkade client is disposed");
24708
+ let set = this.handles.get(script);
24709
+ if (!set) {
24710
+ set = /* @__PURE__ */ new Set();
24711
+ this.handles.set(script, set);
24712
+ }
24713
+ set.add(handle);
24714
+ if (!this.unsubscribeEvents) {
24715
+ this.unsubscribeEvents = manager.onContractEvent((event) => this.dispatch(event));
24716
+ }
24717
+ return () => {
24718
+ const current = this.handles.get(script);
24719
+ if (!current) return;
24720
+ current.delete(handle);
24721
+ if (current.size === 0) this.handles.delete(script);
24722
+ if (this.handles.size === 0) {
24723
+ this.unsubscribeEvents?.();
24724
+ this.unsubscribeEvents = void 0;
24725
+ }
24726
+ };
24727
+ }
24728
+ dispatch(event) {
24729
+ let targets;
24730
+ if (event.type === "connection_reset") {
24731
+ targets = [...this.handles.values()].flatMap((set) => [...set]).filter((h) => h.type === "connection_reset");
24732
+ } else if (isContractVtxoEvent(event)) {
24733
+ targets = [...this.handles.get(event.contractScript) ?? []].filter(
24734
+ (h) => h.type === event.type
24735
+ );
24736
+ } else {
24737
+ return;
24738
+ }
24739
+ const report = (error) => console.error("Error in ArkadeContract event handler:", error);
24740
+ for (const handle of targets) {
24741
+ try {
24742
+ Promise.resolve(handle.callback(event)).catch(report);
24743
+ } catch (error) {
24744
+ report(error);
24745
+ }
24746
+ }
24747
+ }
24395
24748
  /**
24396
24749
  * Instantiate a contract from a program and its constructor arguments. `const` inference keeps
24397
24750
  * the program's literal type, so `functions` is strongly typed.
24398
24751
  *
24399
- * Unbound declared `server`/`user` params default to the client's server key / identity key;
24400
- * explicit args win.
24752
+ * Declared `server`/`user` params default to the client's keys. Compiler
24753
+ * artifacts reserve the server key and exit delay to the connected server.
24401
24754
  */
24402
24755
  contract(program, args = {}) {
24403
- const declared = (program.params ?? []).map(inputName);
24756
+ const declared = (program.params ?? []).map((p) => p.name);
24404
24757
  if (declared.includes("server") && args.server === void 0) {
24405
24758
  args = { ...args, server: this.serverKey };
24406
24759
  }
24407
24760
  if (declared.includes("user") && args.user === void 0 && this.userKey) {
24408
24761
  args = { ...args, user: this.userKey };
24409
24762
  }
24763
+ if (declared.includes("SERVER_EXIT_DELAY")) {
24764
+ const delay = this.serverExitDelay;
24765
+ if (delay === void 0 || delay <= 0n)
24766
+ throw new Error(
24767
+ "compiler artifact: server must provide a positive unilateral exit delay"
24768
+ );
24769
+ const typed = toTimelock(delay);
24770
+ if (typed.type === "seconds" && delay % 512n !== 0n) {
24771
+ throw new Error(
24772
+ `compiler artifact: server exit delay ${delay}s must be a multiple of 512 seconds`
24773
+ );
24774
+ }
24775
+ const sequence = BigInt(timelockToSequence(typed));
24776
+ if (args.SERVER_EXIT_DELAY !== void 0 && args.SERVER_EXIT_DELAY !== sequence && args.SERVER_EXIT_DELAY !== Number(sequence)) {
24777
+ throw new Error(
24778
+ "compiler artifact: reserved SERVER_EXIT_DELAY must match the connected server"
24779
+ );
24780
+ }
24781
+ args = { ...args, SERVER_EXIT_DELAY: sequence };
24782
+ }
24410
24783
  return new ArkadeContract(this, program, args);
24411
24784
  }
24412
24785
  };
@@ -24468,7 +24841,7 @@ var ArkadeContract = class _ArkadeContract {
24468
24841
  }
24469
24842
  /** Callable spending paths: `functions.<name>(...args)` (see {@link ContractFunctions}). */
24470
24843
  get functions() {
24471
- const out = {};
24844
+ const out = /* @__PURE__ */ Object.create(null);
24472
24845
  for (const fn of this.compiled) {
24473
24846
  out[fn.name] = (...callArgs) => new ArkadeTransactionBuilder(this, fn, bindInputs(fn, callArgs));
24474
24847
  }
@@ -24497,18 +24870,65 @@ var ArkadeContract = class _ArkadeContract {
24497
24870
  * counted in balances, re-derivable via the `"arkade"` handler). Idempotent per script.
24498
24871
  */
24499
24872
  async register(options) {
24500
- const manager = this.client.contractManager;
24501
- if (!manager) {
24502
- throw new Error(
24503
- "ArkadeContract.register requires a `contractManager` on the Arkade client \u2014 pass one to Arkade.connect"
24504
- );
24505
- }
24873
+ const manager = this.requireManager("register");
24506
24874
  return manager.createContract({
24507
24875
  ...this.toContractParams(),
24508
24876
  label: options?.label,
24509
24877
  metadata: options?.metadata
24510
24878
  });
24511
24879
  }
24880
+ /**
24881
+ * Report this contract's activity: `vtxo_received`/`vtxo_spent` for its script, and
24882
+ * `connection_reset` when the manager's event stream reconnects. Registers the contract
24883
+ * (idempotent) so it is persisted and watched; requires a contract manager (`storage` or
24884
+ * `contractManager` on {@link Arkade.connect}).
24885
+ *
24886
+ * Activity is reported from subscription on — coins that predate it are not replayed. After
24887
+ * subscribing, and on every `connection_reset` (the manager re-syncs silently), read stored
24888
+ * state with {@link getVtxos}.
24889
+ *
24890
+ * @example
24891
+ * ```typescript
24892
+ * const spent = await htlc.on("vtxo_spent", (e) => console.log(e.vtxos));
24893
+ * const reset = await htlc.on("connection_reset", async () => reconcile(await htlc.getVtxos()));
24894
+ * spent.off();
24895
+ * ```
24896
+ */
24897
+ async on(type, callback) {
24898
+ const manager = this.requireManager("on");
24899
+ const off = this.client.addEventHandle(manager, hex.encode(this.pkScript), {
24900
+ type,
24901
+ callback
24902
+ });
24903
+ try {
24904
+ await this.register();
24905
+ } catch (error) {
24906
+ off();
24907
+ throw error;
24908
+ }
24909
+ return { off };
24910
+ }
24911
+ /**
24912
+ * Every stored VTXO of this contract, spent ones included (`getUtxos` hides those), as the
24913
+ * contract manager last synced them. The recovery read for {@link on}. Empty when the
24914
+ * contract is not registered.
24915
+ */
24916
+ async getVtxos() {
24917
+ const manager = this.requireManager("getVtxos");
24918
+ const [withVtxos] = await manager.getContractsWithVtxos({
24919
+ script: hex.encode(this.pkScript)
24920
+ });
24921
+ return withVtxos?.vtxos ?? [];
24922
+ }
24923
+ requireManager(method) {
24924
+ const manager = this.client.contractManager;
24925
+ if (!manager) {
24926
+ throw new Error(
24927
+ `ArkadeContract.${method} requires a contract manager on the Arkade client \u2014 pass \`storage\` or \`contractManager\` to Arkade.connect`
24928
+ );
24929
+ }
24930
+ return manager;
24931
+ }
24512
24932
  /**
24513
24933
  * Spendable VTXOs locked by this contract: repository-backed when registered with a
24514
24934
  * `contractManager`, otherwise a direct indexer query. Both drop spent and unrolled outputs;
@@ -24598,6 +25018,11 @@ var ArkadeTransactionBuilder = class {
24598
25018
  }
24599
25019
  /** Assemble the unsigned ark transaction and its checkpoints. */
24600
25020
  async build() {
25021
+ if (this.fn.def.tapscript.csv) {
25022
+ throw new Error(
25023
+ `${this.fn.name}: CSV leaves require an aged onchain output; the offchain builder creates fresh checkpoints \u2014 use the wallet's unilateral exit flow`
25024
+ );
25025
+ }
24601
25026
  if (this.outputs.length === 0) {
24602
25027
  throw new Error("ArkadeTransactionBuilder: at least one output is required");
24603
25028
  }
@@ -24676,6 +25101,22 @@ var ArkadeTransactionBuilder = class {
24676
25101
  }
24677
25102
  /** Build, submit and return the finalized transaction. */
24678
25103
  async send() {
25104
+ if (this.fn.def.tapscript.signers.some(
25105
+ (signer) => typeof signer === "object" && "tweak" in signer
25106
+ )) {
25107
+ throw new Error(
25108
+ `${this.fn.name}: covenant-tweaked leaf signing is not supported by Identity; use a signer that supports the covenant tweak with build()`
25109
+ );
25110
+ }
25111
+ const { serverKey } = this.contract.keys;
25112
+ const userKey = this.contract.client.userKey;
25113
+ if (this.fn.signerKeys.some(
25114
+ (key) => !equalBytes(key, serverKey) && (!userKey || !equalBytes(key, userKey))
25115
+ )) {
25116
+ throw new Error(
25117
+ `${this.fn.name}: external leaf signatures are required; use build() and sign the PSBT with all required signers`
25118
+ );
25119
+ }
24679
25120
  const { arkTx, checkpoints } = await this.build();
24680
25121
  const client = this.contract.client;
24681
25122
  const userInputs = this.userInputIndexes();
@@ -24727,7 +25168,7 @@ var ArkadeTransactionBuilder = class {
24727
25168
  /** Indexes of inputs the client owns and must sign (contract input + funded inputs). */
24728
25169
  userInputIndexes() {
24729
25170
  const idxs = [];
24730
- const userKey = this.contract.keys.userKey;
25171
+ const userKey = this.contract.client.userKey;
24731
25172
  if (userKey && this.fn.signerKeys.some((k) => equalBytes(k, userKey))) {
24732
25173
  idxs.push(0);
24733
25174
  }
@@ -24761,12 +25202,19 @@ var ArkadeTransactionBuilder = class {
24761
25202
  }
24762
25203
  };
24763
25204
  function bindInputs(fn, callArgs) {
24764
- const names = (fn.def.inputs ?? []).map(inputName);
24765
- if (callArgs.length !== names.length) {
24766
- throw new Error(`${fn.name}: expected ${names.length} argument(s), got ${callArgs.length}`);
25205
+ const inputs = fn.def.inputs ?? [];
25206
+ if (callArgs.length !== inputs.length) {
25207
+ throw new Error(
25208
+ `${fn.name}: expected ${inputs.length} argument(s), got ${callArgs.length}`
25209
+ );
24767
25210
  }
24768
- const bound = {};
24769
- names.forEach((n, i) => bound[n] = callArgs[i]);
25211
+ const bound = /* @__PURE__ */ Object.create(null);
25212
+ inputs.forEach((def, i) => {
25213
+ if (Object.hasOwn(bound, def.name))
25214
+ throw new Error(`${fn.name}: duplicate input '${def.name}'`);
25215
+ validateParamValue(def, callArgs[i]);
25216
+ bound[def.name] = callArgs[i];
25217
+ });
24770
25218
  return bound;
24771
25219
  }
24772
25220
  function attachExtension(tx, newPackets) {
@@ -25251,6 +25699,6 @@ function createDefaultPaymentRouter(wallet) {
25251
25699
  }).use(arkRail()).use(arkAssetRail()).use(onchainRail({ feeInfo: walletFeeSource(wallet) }));
25252
25700
  }
25253
25701
 
25254
- export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, ArkadeCashOperatorMismatchError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractWatcher, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, 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, KeyringDescriptorProvider, MAX_PAGE_SIZE, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, OversizedChangeError, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, SendDeadlineExceededError, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, StaticDescriptorProvider, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, VtxoReservedError, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertHistoryPageFilter, assertIntentIdUnique, assertNoAssets, assertNoSelection, assertPageRequest, assertRecipientArkadeAddress, assertSendDeadline, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, captureSendDeadline, checkSaveVtxosForScript, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, collectContracts, collectIntents, collectPages, collectScriptVtxos, collectTransactionHistory, collectUtxos, collectVtxos, combineTapscriptSigs, compareHistoryCursors, contractFilterMatchesNothing, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, createdAtToIso, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, iteratePages, legacyVtxoFacts, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, pageResult, promisifyRequest, provisionClaimSecret, provisionRefundKey, registerWalletRestoreHook, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, selectionOf, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, submitViaEmulator, toXOnlySignerHex, tryResolveSendAmount, utxoEntries, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
25255
- //# sourceMappingURL=chunk-I4ZWKG7B.js.map
25256
- //# sourceMappingURL=chunk-I4ZWKG7B.js.map
25702
+ export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ASSET_CARRIER_SATS, AbortError, ActivityRegistry, AmbiguousRouteError, ArkNote, Arkade, ArkadeCash, ArkadeCashCreateError, ArkadeCashOperatorMismatchError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractWatcher, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, 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, KeyringDescriptorProvider, MAX_PAGE_SIZE, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, OversizedChangeError, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, SendDeadlineExceededError, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, StaticDescriptorProvider, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, VtxoReservedError, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WsElectrumChainSource, adoptContractDescriptor, arkAssetRail, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertHistoryPageFilter, assertIntentIdUnique, assertNoAssets, assertNoSelection, assertPageRequest, assertRecipientArkadeAddress, assertSendDeadline, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, assetsOf, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, captureSendDeadline, checkSaveVtxosForScript, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, collectContracts, collectIntents, collectPages, collectScriptVtxos, collectTransactionHistory, collectUtxos, collectVtxos, combineTapscriptSigs, compareHistoryCursors, contractFilterMatchesNothing, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, createdAtToIso, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isContractVtxoEvent, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, iteratePages, legacyVtxoFacts, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, pageResult, promisifyRequest, provisionClaimSecret, provisionRefundKey, registerWalletRestoreHook, resolveAssetAmount, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, selectionOf, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, submitViaEmulator, toXOnlySignerHex, tryResolveSendAmount, utxoEntries, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, walletFeeSource, warnAndFilterVtxosForScript, withPrevTxs };
25703
+ //# sourceMappingURL=chunk-34G7TDUS.js.map
25704
+ //# sourceMappingURL=chunk-34G7TDUS.js.map