@arkade-os/sdk 0.4.42 → 0.4.44

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 (70) hide show
  1. package/README.md +65 -0
  2. package/dist/adapters/expo.cjs +4 -4
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +2 -2
  6. package/dist/adapters/indexedDB.cjs +3 -3
  7. package/dist/adapters/indexedDB.js +2 -2
  8. package/dist/{ark-CKDWpuGL.d.cts → ark-I8jfe5VE.d.cts} +157 -2
  9. package/dist/{ark-CKDWpuGL.d.ts → ark-I8jfe5VE.d.ts} +157 -2
  10. package/dist/{asyncStorageTaskQueue-BB2MffwG.d.ts → asyncStorageTaskQueue-BtSOVZFn.d.ts} +2 -2
  11. package/dist/{asyncStorageTaskQueue-BpN8RRHR.d.cts → asyncStorageTaskQueue-Dap7qUlF.d.cts} +2 -2
  12. package/dist/{chunk-V3FCNVJE.js → chunk-FMI2GKYO.js} +2158 -502
  13. package/dist/chunk-FMI2GKYO.js.map +1 -0
  14. package/dist/{chunk-FLPPSRYH.js → chunk-LCQQPOIT.js} +3 -3
  15. package/dist/{chunk-FLPPSRYH.js.map → chunk-LCQQPOIT.js.map} +1 -1
  16. package/dist/{chunk-5X5YMZHB.js → chunk-LJ35HPJW.js} +39 -5
  17. package/dist/chunk-LJ35HPJW.js.map +1 -0
  18. package/dist/{chunk-HHPUCCB3.cjs → chunk-RJ6XVCX4.cjs} +7 -7
  19. package/dist/{chunk-HHPUCCB3.cjs.map → chunk-RJ6XVCX4.cjs.map} +1 -1
  20. package/dist/{chunk-YEF5FL6E.cjs → chunk-UBYY2TPK.cjs} +39 -4
  21. package/dist/chunk-UBYY2TPK.cjs.map +1 -0
  22. package/dist/{chunk-2V3BU2X6.js → chunk-V7WIZR2B.js} +3 -3
  23. package/dist/{chunk-2V3BU2X6.js.map → chunk-V7WIZR2B.js.map} +1 -1
  24. package/dist/{chunk-T2ACJD22.cjs → chunk-X37EKDIH.cjs} +11 -11
  25. package/dist/{chunk-T2ACJD22.cjs.map → chunk-X37EKDIH.cjs.map} +1 -1
  26. package/dist/{chunk-ED7JDFX4.cjs → chunk-XIVCG6EP.cjs} +2248 -578
  27. package/dist/chunk-XIVCG6EP.cjs.map +1 -0
  28. package/dist/contracts/handlers/index.d.cts +3 -3
  29. package/dist/contracts/handlers/index.d.ts +3 -3
  30. package/dist/{delegate-yjhcR9n4.d.cts → delegate-BRqbh2fW.d.cts} +1 -1
  31. package/dist/{delegate-DllAQ3Eu.d.ts → delegate-CgjVjYOP.d.ts} +1 -1
  32. package/dist/{index-D1te-ZR6.d.cts → index-DHuXlDEg.d.cts} +2 -2
  33. package/dist/{index-DVdCGyyH.d.ts → index-DvWNB_rl.d.ts} +2 -2
  34. package/dist/index.cjs +162 -118
  35. package/dist/index.d.cts +344 -9
  36. package/dist/index.d.ts +344 -9
  37. package/dist/index.js +2 -2
  38. package/dist/repositories/realm/index.cjs +268 -12
  39. package/dist/repositories/realm/index.cjs.map +1 -1
  40. package/dist/repositories/realm/index.d.cts +101 -3
  41. package/dist/repositories/realm/index.d.ts +101 -3
  42. package/dist/repositories/realm/index.js +254 -4
  43. package/dist/repositories/realm/index.js.map +1 -1
  44. package/dist/repositories/sqlite/index.cjs +366 -40
  45. package/dist/repositories/sqlite/index.cjs.map +1 -1
  46. package/dist/repositories/sqlite/index.d.cts +52 -6
  47. package/dist/repositories/sqlite/index.d.ts +52 -6
  48. package/dist/repositories/sqlite/index.js +353 -32
  49. package/dist/repositories/sqlite/index.js.map +1 -1
  50. package/dist/{taskRunner-BdiRy4iI.d.ts → taskRunner-Bptw9UMa.d.ts} +2 -2
  51. package/dist/{taskRunner-CuTk8RYg.d.cts → taskRunner-CeFxKhSH.d.cts} +2 -2
  52. package/dist/wallet/expo/background.cjs +12 -12
  53. package/dist/wallet/expo/background.d.cts +3 -3
  54. package/dist/wallet/expo/background.d.ts +3 -3
  55. package/dist/wallet/expo/background.js +4 -4
  56. package/dist/wallet/expo/index.cjs +12 -12
  57. package/dist/wallet/expo/index.d.cts +4 -4
  58. package/dist/wallet/expo/index.d.ts +4 -4
  59. package/dist/wallet/expo/index.js +3 -3
  60. package/dist/{wallet-CyPOQ0oM.d.cts → wallet-BsqRU8KM.d.cts} +29 -5
  61. package/dist/{wallet-CrIVUQMb.d.ts → wallet-D9z7wssl.d.ts} +29 -5
  62. package/dist/worker/expo/index.cjs +7 -7
  63. package/dist/worker/expo/index.d.cts +4 -4
  64. package/dist/worker/expo/index.d.ts +4 -4
  65. package/dist/worker/expo/index.js +3 -3
  66. package/package.json +2 -2
  67. package/dist/chunk-5X5YMZHB.js.map +0 -1
  68. package/dist/chunk-ED7JDFX4.cjs.map +0 -1
  69. package/dist/chunk-V3FCNVJE.js.map +0 -1
  70. package/dist/chunk-YEF5FL6E.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, baseFetch, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-5X5YMZHB.js';
2
- import { isMainnetDescriptor, descriptorIsOurs, contractHandlers, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-YNEDJTX2.js';
3
- import { VtxoScript, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-BE5MUB7T.js';
1
+ import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, baseFetch, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-LJ35HPJW.js';
2
+ import { contractHandlers, isMainnetDescriptor, descriptorIsOurs, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-YNEDJTX2.js';
3
+ import { VtxoScript, sequenceToTimelock, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-BE5MUB7T.js';
4
4
  import { sha256, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr, equalBytes as equalBytes$1 } from '@scure/btc-signer/utils.js';
5
5
  import { SigHash, Script, p2tr, RawWitness, Address, OutScript, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
6
6
  import { hex, base58, base64 } from '@scure/base';
@@ -1215,6 +1215,114 @@ var isExplorerTransaction = (tx) => {
1215
1215
  (vout) => typeof vout.scriptpubkey_address === "string" && typeof vout.value === "number"
1216
1216
  ) && typeof tx.status === "object" && typeof tx.status.confirmed === "boolean";
1217
1217
  };
1218
+ var getVarIntSize = (n) => {
1219
+ if (n < 253) return 1;
1220
+ if (n <= 65535) return 3;
1221
+ if (n <= 4294967295) return 5;
1222
+ return 9;
1223
+ };
1224
+ var TxWeightEstimator = class _TxWeightEstimator {
1225
+ static P2PKH_SCRIPT_SIG_SIZE = 1 + 73 + 1 + 33;
1226
+ static INPUT_SIZE = 32 + 4 + 1 + 4;
1227
+ static BASE_CONTROL_BLOCK_SIZE = 1 + 32;
1228
+ static OUTPUT_SIZE = 8 + 1;
1229
+ static P2WPKH_OUTPUT_SIZE = 1 + 1 + 20;
1230
+ static BASE_TX_SIZE = 8 + 2;
1231
+ // Version + LockTime
1232
+ static WITNESS_HEADER_SIZE = 2;
1233
+ // Flag + Marker
1234
+ static WITNESS_SCALE_FACTOR = 4;
1235
+ static P2TR_OUTPUT_SIZE = 1 + 1 + 32;
1236
+ hasWitness;
1237
+ inputCount;
1238
+ outputCount;
1239
+ inputSize;
1240
+ inputWitnessSize;
1241
+ outputSize;
1242
+ constructor(hasWitness, inputCount, outputCount, inputSize, inputWitnessSize, outputSize) {
1243
+ this.hasWitness = hasWitness;
1244
+ this.inputCount = inputCount;
1245
+ this.outputCount = outputCount;
1246
+ this.inputSize = inputSize;
1247
+ this.inputWitnessSize = inputWitnessSize;
1248
+ this.outputSize = outputSize;
1249
+ }
1250
+ static create() {
1251
+ return new _TxWeightEstimator(false, 0, 0, 0, 0, 0);
1252
+ }
1253
+ addP2AInput() {
1254
+ this.inputCount++;
1255
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1256
+ return this;
1257
+ }
1258
+ addKeySpendInput(isDefault = true) {
1259
+ this.inputCount++;
1260
+ this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
1261
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1262
+ this.hasWitness = true;
1263
+ return this;
1264
+ }
1265
+ addP2PKHInput() {
1266
+ this.inputCount++;
1267
+ this.inputWitnessSize++;
1268
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE + _TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE;
1269
+ return this;
1270
+ }
1271
+ addTapscriptInput(leafWitnessSize, leafScriptSize, leafControlBlockSize) {
1272
+ const controlBlockWitnessSize = 1 + _TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE + 1 + leafScriptSize + 1 + leafControlBlockSize;
1273
+ this.inputCount++;
1274
+ this.inputWitnessSize += leafWitnessSize + 1 + controlBlockWitnessSize;
1275
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1276
+ this.hasWitness = true;
1277
+ return this;
1278
+ }
1279
+ addP2WPKHOutput() {
1280
+ this.outputCount++;
1281
+ this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2WPKH_OUTPUT_SIZE;
1282
+ return this;
1283
+ }
1284
+ addP2TROutput() {
1285
+ this.outputCount++;
1286
+ this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2TR_OUTPUT_SIZE;
1287
+ return this;
1288
+ }
1289
+ /**
1290
+ * Adds an output given a raw script.
1291
+ * Cost = 8 bytes (amount) + varint(scriptLen) + scriptLen
1292
+ */
1293
+ addOutputScript(script) {
1294
+ this.outputCount++;
1295
+ this.outputSize += 8 + getVarIntSize(script.length) + script.length;
1296
+ return this;
1297
+ }
1298
+ /**
1299
+ * Adds an output by decoding the address to get the exact script size.
1300
+ */
1301
+ addOutputAddress(address, network) {
1302
+ const payment = Address(network).decode(address);
1303
+ const script = OutScript.encode(payment);
1304
+ return this.addOutputScript(script);
1305
+ }
1306
+ vsize() {
1307
+ const inputCount = getVarIntSize(this.inputCount);
1308
+ const outputCount = getVarIntSize(this.outputCount);
1309
+ const txSizeStripped = _TxWeightEstimator.BASE_TX_SIZE + inputCount + this.inputSize + outputCount + this.outputSize;
1310
+ let weight = txSizeStripped * _TxWeightEstimator.WITNESS_SCALE_FACTOR;
1311
+ if (this.hasWitness) {
1312
+ weight += _TxWeightEstimator.WITNESS_HEADER_SIZE + this.inputWitnessSize;
1313
+ }
1314
+ return vsize(weight);
1315
+ }
1316
+ };
1317
+ var vsize = (weight) => {
1318
+ const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
1319
+ return {
1320
+ value,
1321
+ fee: (feeRate) => feeRate * value
1322
+ };
1323
+ };
1324
+
1325
+ // src/utils/anchor.ts
1218
1326
  var ANCHOR_VALUE = 0n;
1219
1327
  var ANCHOR_PKSCRIPT = new Uint8Array([81, 2, 78, 115]);
1220
1328
  var P2A = {
@@ -1240,6 +1348,37 @@ function findP2AOutput(tx) {
1240
1348
  }
1241
1349
  throw new Error("P2A output not found");
1242
1350
  }
1351
+ var CHILD_DUST_AMOUNT = 546;
1352
+ function buildAnchorChild(params) {
1353
+ const { parent, feeRate, fundingCoins, changeAddress, tapInternalKey, network } = params;
1354
+ const child = new Transaction({ version: 3, allowLegacyWitnessUtxo: true });
1355
+ child.addInput(findP2AOutput(parent));
1356
+ const estimator = TxWeightEstimator.create().addP2AInput();
1357
+ for (const _ of fundingCoins) {
1358
+ estimator.addKeySpendInput(true);
1359
+ }
1360
+ estimator.addOutputAddress(changeAddress, network);
1361
+ const childVsize = Number(estimator.vsize().value);
1362
+ const fee = Math.ceil(feeRate * (parent.vsize + childVsize));
1363
+ let total = 0n;
1364
+ for (const coin of fundingCoins) {
1365
+ total += BigInt(coin.value);
1366
+ child.addInput({
1367
+ txid: coin.txid,
1368
+ index: coin.vout,
1369
+ witnessUtxo: { script: params.changeScript, amount: BigInt(coin.value) },
1370
+ tapInternalKey
1371
+ });
1372
+ }
1373
+ const change = total + P2A.amount - BigInt(fee);
1374
+ if (change < BigInt(CHILD_DUST_AMOUNT)) {
1375
+ throw new Error(
1376
+ `insufficient funding for anchor child: need change >= ${CHILD_DUST_AMOUNT}, got ${change}`
1377
+ );
1378
+ }
1379
+ child.addOutputAddress(changeAddress, change, network);
1380
+ return { child, fee };
1381
+ }
1243
1382
 
1244
1383
  // src/forfeit.ts
1245
1384
  function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
@@ -2154,112 +2293,6 @@ function isValidArkAddress(address) {
2154
2293
  return false;
2155
2294
  }
2156
2295
  }
2157
- var getVarIntSize = (n) => {
2158
- if (n < 253) return 1;
2159
- if (n <= 65535) return 3;
2160
- if (n <= 4294967295) return 5;
2161
- return 9;
2162
- };
2163
- var TxWeightEstimator = class _TxWeightEstimator {
2164
- static P2PKH_SCRIPT_SIG_SIZE = 1 + 73 + 1 + 33;
2165
- static INPUT_SIZE = 32 + 4 + 1 + 4;
2166
- static BASE_CONTROL_BLOCK_SIZE = 1 + 32;
2167
- static OUTPUT_SIZE = 8 + 1;
2168
- static P2WPKH_OUTPUT_SIZE = 1 + 1 + 20;
2169
- static BASE_TX_SIZE = 8 + 2;
2170
- // Version + LockTime
2171
- static WITNESS_HEADER_SIZE = 2;
2172
- // Flag + Marker
2173
- static WITNESS_SCALE_FACTOR = 4;
2174
- static P2TR_OUTPUT_SIZE = 1 + 1 + 32;
2175
- hasWitness;
2176
- inputCount;
2177
- outputCount;
2178
- inputSize;
2179
- inputWitnessSize;
2180
- outputSize;
2181
- constructor(hasWitness, inputCount, outputCount, inputSize, inputWitnessSize, outputSize) {
2182
- this.hasWitness = hasWitness;
2183
- this.inputCount = inputCount;
2184
- this.outputCount = outputCount;
2185
- this.inputSize = inputSize;
2186
- this.inputWitnessSize = inputWitnessSize;
2187
- this.outputSize = outputSize;
2188
- }
2189
- static create() {
2190
- return new _TxWeightEstimator(false, 0, 0, 0, 0, 0);
2191
- }
2192
- addP2AInput() {
2193
- this.inputCount++;
2194
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2195
- return this;
2196
- }
2197
- addKeySpendInput(isDefault = true) {
2198
- this.inputCount++;
2199
- this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
2200
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2201
- this.hasWitness = true;
2202
- return this;
2203
- }
2204
- addP2PKHInput() {
2205
- this.inputCount++;
2206
- this.inputWitnessSize++;
2207
- this.inputSize += _TxWeightEstimator.INPUT_SIZE + _TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE;
2208
- return this;
2209
- }
2210
- addTapscriptInput(leafWitnessSize, leafScriptSize, leafControlBlockSize) {
2211
- const controlBlockWitnessSize = 1 + _TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE + 1 + leafScriptSize + 1 + leafControlBlockSize;
2212
- this.inputCount++;
2213
- this.inputWitnessSize += leafWitnessSize + 1 + controlBlockWitnessSize;
2214
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2215
- this.hasWitness = true;
2216
- return this;
2217
- }
2218
- addP2WPKHOutput() {
2219
- this.outputCount++;
2220
- this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2WPKH_OUTPUT_SIZE;
2221
- return this;
2222
- }
2223
- addP2TROutput() {
2224
- this.outputCount++;
2225
- this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2TR_OUTPUT_SIZE;
2226
- return this;
2227
- }
2228
- /**
2229
- * Adds an output given a raw script.
2230
- * Cost = 8 bytes (amount) + varint(scriptLen) + scriptLen
2231
- */
2232
- addOutputScript(script) {
2233
- this.outputCount++;
2234
- this.outputSize += 8 + getVarIntSize(script.length) + script.length;
2235
- return this;
2236
- }
2237
- /**
2238
- * Adds an output by decoding the address to get the exact script size.
2239
- */
2240
- addOutputAddress(address, network) {
2241
- const payment = Address(network).decode(address);
2242
- const script = OutScript.encode(payment);
2243
- return this.addOutputScript(script);
2244
- }
2245
- vsize() {
2246
- const inputCount = getVarIntSize(this.inputCount);
2247
- const outputCount = getVarIntSize(this.outputCount);
2248
- const txSizeStripped = _TxWeightEstimator.BASE_TX_SIZE + inputCount + this.inputSize + outputCount + this.outputSize;
2249
- let weight = txSizeStripped * _TxWeightEstimator.WITNESS_SCALE_FACTOR;
2250
- if (this.hasWitness) {
2251
- weight += _TxWeightEstimator.WITNESS_HEADER_SIZE + this.inputWitnessSize;
2252
- }
2253
- return vsize(weight);
2254
- }
2255
- };
2256
- var vsize = (weight) => {
2257
- const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
2258
- return {
2259
- value,
2260
- fee: (feeRate) => feeRate * value
2261
- };
2262
- };
2263
2296
  var AmountVariableName = "amount";
2264
2297
  var ExpiryVariableName = "expiry";
2265
2298
  var BirthVariableName = "birth";
@@ -2644,7 +2677,7 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
2644
2677
  );
2645
2678
  }
2646
2679
  function isMigrationCapable(wallet) {
2647
- return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2680
+ return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.refreshDeprecatedSigners === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2648
2681
  }
2649
2682
  function classifiedToRef(c) {
2650
2683
  return {
@@ -3168,6 +3201,7 @@ var VtxoManager = class _VtxoManager {
3168
3201
  async migrateCore(options) {
3169
3202
  const wallet = this.requireMigrationCapableWallet();
3170
3203
  const info = await wallet.arkProvider.getInfo();
3204
+ wallet.refreshDeprecatedSigners(info);
3171
3205
  const signerSet = signerSetFromInfo(info);
3172
3206
  const nowSeconds = Math.floor(Date.now() / 1e3);
3173
3207
  const walletSignerHex = hex.encode(wallet.arkServerPublicKey);
@@ -4136,6 +4170,123 @@ function readUInt32BE(array, offset) {
4136
4170
  function noteTapscript(preimageHash) {
4137
4171
  return Script.encode(["SHA256", preimageHash, "EQUAL"]);
4138
4172
  }
4173
+
4174
+ // src/repositories/intentRepository.ts
4175
+ var ALL_INTENT_STATES = [
4176
+ "waiting_to_submit",
4177
+ "waiting_for_batch",
4178
+ "batch_in_progress",
4179
+ "batch_failed",
4180
+ "batch_succeeded",
4181
+ "cancelled"
4182
+ ];
4183
+ var INTENT_TERMINAL_STATES = /* @__PURE__ */ new Set([
4184
+ "batch_failed",
4185
+ "batch_succeeded",
4186
+ "cancelled"
4187
+ ]);
4188
+ function isTerminalIntentState(s) {
4189
+ return INTENT_TERMINAL_STATES.has(s);
4190
+ }
4191
+ function assertIntentIdUnique(intent, existing) {
4192
+ if (intent.intentId === void 0) return;
4193
+ for (const e of existing) {
4194
+ if (e.intentTxId !== intent.intentTxId && e.intentId === intent.intentId) {
4195
+ throw new Error(
4196
+ `intentId "${intent.intentId}" is already used by intent ${e.intentTxId}`
4197
+ );
4198
+ }
4199
+ }
4200
+ }
4201
+ function intentMatchesFilter(i, f) {
4202
+ if (f.intentTxIds && !f.intentTxIds.includes(i.intentTxId)) return false;
4203
+ if (f.intentIds && (!i.intentId || !f.intentIds.includes(i.intentId))) return false;
4204
+ if (f.states && !f.states.includes(i.state)) return false;
4205
+ if (f.containingInputs) {
4206
+ const keys = new Set(i.intentVtxos.map((o) => `${o.txid}:${o.vout}`));
4207
+ if (!f.containingInputs.some((o) => keys.has(`${o.txid}:${o.vout}`))) return false;
4208
+ }
4209
+ if (f.validAt !== void 0) {
4210
+ if (i.validFrom !== void 0 && f.validAt < i.validFrom) return false;
4211
+ if (i.validUntil !== void 0 && f.validAt > i.validUntil) return false;
4212
+ }
4213
+ if (f.searchText) {
4214
+ const hay = [i.intentId, i.batchId, i.commitmentTransactionId].filter(Boolean).join(" ");
4215
+ if (!hay.includes(f.searchText)) return false;
4216
+ }
4217
+ return true;
4218
+ }
4219
+ function intentPageBounds(filter, total) {
4220
+ const skip = Math.max(0, filter?.skip ?? 0);
4221
+ const take = filter?.take === void 0 ? total : Math.max(0, filter.take);
4222
+ return { skip, end: skip + take };
4223
+ }
4224
+
4225
+ // src/wallet/intentStateReducer.ts
4226
+ function reduceIntentState(current, event) {
4227
+ if (isTerminalIntentState(current)) return current;
4228
+ switch (event.type) {
4229
+ case "batch_started" /* BatchStarted */:
4230
+ return "batch_in_progress";
4231
+ case "batch_finalized" /* BatchFinalized */:
4232
+ return "batch_succeeded";
4233
+ case "batch_failed" /* BatchFailed */:
4234
+ return "batch_failed";
4235
+ default:
4236
+ return current;
4237
+ }
4238
+ }
4239
+ function applySettlementEventToIntent(intent, event) {
4240
+ const next = reduceIntentState(intent.state, event);
4241
+ if (next === intent.state) return void 0;
4242
+ const updated = { ...intent, state: next };
4243
+ if ("id" in event && typeof event.id === "string") {
4244
+ updated.batchId = event.id;
4245
+ }
4246
+ if (event.type === "batch_finalized" /* BatchFinalized */) {
4247
+ updated.commitmentTransactionId = event.commitmentTxid;
4248
+ }
4249
+ if (event.type === "batch_failed" /* BatchFailed */) {
4250
+ updated.cancellationReason = event.reason;
4251
+ }
4252
+ return updated;
4253
+ }
4254
+
4255
+ // src/wallet/intentPersistenceHandler.ts
4256
+ async function persistTransition(deps, event) {
4257
+ const repo = deps.intentRepository;
4258
+ if (!repo) return;
4259
+ try {
4260
+ const cur = (await repo.getIntents({ intentTxIds: [deps.intentTxId] }))[0];
4261
+ if (!cur) return;
4262
+ const next = applySettlementEventToIntent(cur, event);
4263
+ if (next) await repo.saveIntent(next);
4264
+ } catch (e) {
4265
+ console.error(`Failed to apply settlement event to intent ${deps.intentTxId}`, e);
4266
+ }
4267
+ }
4268
+ function wrapHandlerWithIntentPersistence(base, deps) {
4269
+ return {
4270
+ ...base,
4271
+ onBatchStarted: async (event) => {
4272
+ const result = await base.onBatchStarted(event);
4273
+ if (!result.skip) await persistTransition(deps, event);
4274
+ return result;
4275
+ },
4276
+ onBatchFinalized: async (event) => {
4277
+ if (base.onBatchFinalized) await base.onBatchFinalized(event);
4278
+ await persistTransition(deps, event);
4279
+ },
4280
+ onBatchFailed: async (event) => {
4281
+ await persistTransition(deps, event);
4282
+ if (base.onBatchFailed) {
4283
+ await base.onBatchFailed(event);
4284
+ return;
4285
+ }
4286
+ throw new Error(event.reason);
4287
+ }
4288
+ };
4289
+ }
4139
4290
  var TxTree = class {
4140
4291
  constructor(root, children = /* @__PURE__ */ new Map()) {
4141
4292
  this.root = root;
@@ -5245,201 +5396,199 @@ function isAnnotated(v) {
5245
5396
  return v.tapTree !== void 0 && v.forfeitTapLeafScript !== void 0 && v.intentTapLeafScript !== void 0;
5246
5397
  }
5247
5398
 
5248
- // src/contracts/vtxoOwnership.ts
5249
- function vtxoOutpoint(vtxo) {
5250
- return `${vtxo.txid}:${vtxo.vout}`;
5251
- }
5252
- function isVtxoForScript(vtxo, script) {
5253
- return !!vtxo.script && vtxo.script === script;
5254
- }
5255
- function filterVtxosForScript(vtxos, script) {
5256
- return vtxos.filter((v) => isVtxoForScript(v, script));
5257
- }
5258
- function warnAndFilterVtxosForScript(vtxos, script, context) {
5259
- const matches = [];
5260
- const rejected = [];
5261
- for (const v of vtxos) {
5262
- if (isVtxoForScript(v, script)) {
5263
- matches.push(v);
5264
- } else {
5265
- rejected.push(`${vtxoOutpoint(v)}(script=${v.script ?? ""})`);
5266
- }
5267
- }
5268
- if (rejected.length > 0) {
5269
- console.warn(
5270
- `${context}: dropped ${rejected.length} wrong-script VTXO(s) for script ${script}: ${rejected.join(", ")}`
5271
- );
5272
- }
5273
- return matches;
5274
- }
5275
- function validateVtxosForScript(vtxos, script, context) {
5276
- const mismatches = vtxos.filter((v) => !isVtxoForScript(v, script));
5277
- if (mismatches.length === 0) return;
5278
- const detail = mismatches.map((v) => `${vtxoOutpoint(v)}(script=${v.script ?? ""})`).join(", ");
5279
- throw new Error(
5280
- `${context}: refusing to persist ${mismatches.length} VTXO(s) whose script does not match ${script}: ${detail}`
5281
- );
5282
- }
5283
- async function getVtxosForContract(repo, contract) {
5284
- return repo.getVtxosForScript ? repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
5285
- }
5286
- async function saveVtxosForContract(repo, contract, vtxos) {
5287
- if (repo.saveVtxosForScript) {
5288
- return repo.saveVtxosForScript(
5289
- { script: contract.script, address: contract.address },
5290
- vtxos
5291
- );
5292
- }
5293
- validateVtxosForScript(vtxos, contract.script, "saveVtxosForContract");
5294
- return repo.saveVtxos(contract.address, vtxos);
5399
+ // src/repositories/virtualTxRepository.ts
5400
+ var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
5401
+ ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
5402
+ ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
5403
+ ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
5404
+ ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
5405
+ ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
5406
+ return ChainedTxType2;
5407
+ })(ChainedTxType || {});
5408
+ function mergeChainedTxType(incoming, prev) {
5409
+ if (incoming !== 0 /* Unspecified */) return incoming;
5410
+ return prev ?? 0 /* Unspecified */;
5295
5411
  }
5296
5412
 
5297
- // src/repositories/inMemory/walletRepository.ts
5298
- var InMemoryWalletRepository = class {
5413
+ // src/repositories/inMemory/intentRepository.ts
5414
+ var InMemoryIntentRepository = class {
5299
5415
  version = 1;
5300
- vtxosByAddress = /* @__PURE__ */ new Map();
5301
- utxosByAddress = /* @__PURE__ */ new Map();
5302
- txsByAddress = /* @__PURE__ */ new Map();
5303
- walletState = null;
5304
- async getVtxos(address) {
5305
- return this.vtxosByAddress.get(address) ?? [];
5306
- }
5307
- async saveVtxos(address, vtxos) {
5308
- const existing = this.vtxosByAddress.get(address) ?? [];
5309
- const next = mergeByKey(existing, vtxos, (item) => `${item.txid}:${item.vout}`);
5310
- this.vtxosByAddress.set(address, next);
5416
+ byId = /* @__PURE__ */ new Map();
5417
+ async clear() {
5418
+ this.byId.clear();
5419
+ }
5420
+ async saveIntent(intent) {
5421
+ assertIntentIdUnique(intent, this.byId.values());
5422
+ this.byId.set(intent.intentTxId, {
5423
+ ...intent,
5424
+ intentVtxos: [...intent.intentVtxos],
5425
+ partialForfeits: [...intent.partialForfeits],
5426
+ updatedAt: Date.now()
5427
+ });
5311
5428
  }
5312
- async deleteVtxos(address) {
5313
- this.vtxosByAddress.delete(address);
5429
+ async getIntents(filter) {
5430
+ let out = [...this.byId.values()];
5431
+ if (filter) out = out.filter((i) => intentMatchesFilter(i, filter));
5432
+ out.sort((a, b) => a.createdAt - b.createdAt || a.intentTxId.localeCompare(b.intentTxId));
5433
+ const { skip, end } = intentPageBounds(filter, out.length);
5434
+ return out.slice(skip, end).map(clone);
5314
5435
  }
5315
- async getVtxosForScript(script) {
5316
- const allMatches = [];
5317
- for (const bucket of this.vtxosByAddress.values()) {
5318
- for (const vtxo of bucket) {
5319
- if (isVtxoForScript(vtxo, script)) {
5320
- allMatches.push(vtxo);
5321
- }
5322
- }
5323
- }
5324
- return mergeByKey([], allMatches, (item) => `${item.txid}:${item.vout}`);
5436
+ async getLockedVtxoOutpoints() {
5437
+ const out = [];
5438
+ for (const i of this.byId.values())
5439
+ if (!isTerminalIntentState(i.state)) for (const o of i.intentVtxos) out.push({ ...o });
5440
+ return out;
5325
5441
  }
5326
- async saveVtxosForScript(key, vtxos) {
5327
- if (!key.address) {
5328
- throw new Error("InMemoryWalletRepository requires an address");
5329
- }
5330
- for (const vtxo of vtxos) {
5331
- if (!isVtxoForScript(vtxo, key.script)) {
5332
- throw new Error(
5333
- `VTXO ${vtxo.txid}:${vtxo.vout} script mismatch: expected ${key.script}, got ${vtxo.script}`
5334
- );
5335
- }
5336
- }
5337
- return this.saveVtxos(key.address, vtxos);
5442
+ async [Symbol.asyncDispose]() {
5338
5443
  }
5339
- async deleteVtxosForScript(script) {
5340
- for (const [address, bucket] of this.vtxosByAddress.entries()) {
5341
- const next = bucket.filter((v) => !isVtxoForScript(v, script));
5342
- if (next.length === 0) {
5343
- this.vtxosByAddress.delete(address);
5344
- } else {
5345
- this.vtxosByAddress.set(address, next);
5444
+ };
5445
+ var clone = (i) => ({
5446
+ ...i,
5447
+ intentVtxos: i.intentVtxos.map((o) => ({ ...o })),
5448
+ partialForfeits: [...i.partialForfeits]
5449
+ });
5450
+
5451
+ // src/repositories/inMemory/virtualTxRepository.ts
5452
+ var opKey = (o) => `${o.txid}:${o.vout}`;
5453
+ var InMemoryVirtualTxRepository = class {
5454
+ version = 1;
5455
+ txs = /* @__PURE__ */ new Map();
5456
+ branches = /* @__PURE__ */ new Map();
5457
+ async clear() {
5458
+ this.txs.clear();
5459
+ this.branches.clear();
5460
+ }
5461
+ async upsertVirtualTxs(txs) {
5462
+ for (const t of txs) {
5463
+ const prev = this.txs.get(t.txid);
5464
+ if (!prev) {
5465
+ this.txs.set(t.txid, { ...t });
5466
+ continue;
5346
5467
  }
5468
+ this.txs.set(t.txid, {
5469
+ txid: t.txid,
5470
+ psbt: t.psbt ?? prev.psbt,
5471
+ expiresAt: t.expiresAt ?? prev.expiresAt,
5472
+ type: mergeChainedTxType(t.type, prev.type)
5473
+ });
5347
5474
  }
5348
5475
  }
5349
- async getUtxos(address) {
5350
- return this.utxosByAddress.get(address) ?? [];
5351
- }
5352
- async saveUtxos(address, utxos) {
5353
- const existing = this.utxosByAddress.get(address) ?? [];
5354
- const next = mergeByKey(existing, utxos, (item) => `${item.txid}:${item.vout}`);
5355
- this.utxosByAddress.set(address, next);
5356
- }
5357
- async deleteUtxos(address) {
5358
- this.utxosByAddress.delete(address);
5359
- }
5360
- async getTransactionHistory(address) {
5361
- return this.txsByAddress.get(address) ?? [];
5362
- }
5363
- async saveTransactions(address, txs) {
5364
- const existing = this.txsByAddress.get(address) ?? [];
5365
- const next = mergeByKey(existing, txs, serializeTxKey);
5366
- this.txsByAddress.set(address, next);
5476
+ async getVirtualTx(txid) {
5477
+ const t = this.txs.get(txid);
5478
+ return t ? { ...t } : null;
5367
5479
  }
5368
- async deleteTransactions(address) {
5369
- this.txsByAddress.delete(address);
5480
+ async setBranch(vtxo, branch) {
5481
+ this.branches.set(
5482
+ opKey(vtxo),
5483
+ [...branch].sort((a, b) => a.position - b.position)
5484
+ );
5370
5485
  }
5371
- async getWalletState() {
5372
- return this.walletState;
5486
+ async getBranch(vtxo) {
5487
+ const b = this.branches.get(opKey(vtxo)) ?? [];
5488
+ return b.map((x) => this.txs.get(x.virtualTxid)).filter((t) => !!t).map((t) => ({ ...t }));
5373
5489
  }
5374
- async saveWalletState(state) {
5375
- this.walletState = state;
5490
+ async hasBranch(vtxo) {
5491
+ return (this.branches.get(opKey(vtxo)) ?? []).length > 0;
5376
5492
  }
5377
- async clear() {
5378
- this.vtxosByAddress.clear();
5379
- this.utxosByAddress.clear();
5380
- this.txsByAddress.clear();
5381
- this.walletState = null;
5493
+ async pruneForSpentVtxo(vtxo) {
5494
+ const removed = this.branches.get(opKey(vtxo)) ?? [];
5495
+ this.branches.delete(opKey(vtxo));
5496
+ const stillReferenced = /* @__PURE__ */ new Set();
5497
+ for (const b of this.branches.values())
5498
+ for (const e of b) stillReferenced.add(e.virtualTxid);
5499
+ for (const e of removed)
5500
+ if (!stillReferenced.has(e.virtualTxid)) this.txs.delete(e.virtualTxid);
5382
5501
  }
5383
5502
  async [Symbol.asyncDispose]() {
5384
- return;
5385
5503
  }
5386
5504
  };
5387
- function serializeTxKey(tx) {
5388
- const key = tx.key;
5389
- return `${key.boardingTxid}:${key.commitmentTxid}:${key.arkTxid}`;
5390
- }
5391
- function mergeByKey(existing, incoming, toKey) {
5392
- const next = /* @__PURE__ */ new Map();
5393
- existing.forEach((item) => {
5394
- next.set(toKey(item), item);
5395
- });
5396
- incoming.forEach((item) => {
5397
- next.set(toKey(item), item);
5398
- });
5399
- return Array.from(next.values());
5400
- }
5401
5505
 
5402
- // src/repositories/inMemory/contractRepository.ts
5403
- var InMemoryContractRepository = class {
5404
- version = 1;
5405
- contractData = /* @__PURE__ */ new Map();
5406
- collections = /* @__PURE__ */ new Map();
5407
- contractsByScript = /* @__PURE__ */ new Map();
5408
- async clear() {
5409
- this.contractData.clear();
5410
- this.collections.clear();
5411
- this.contractsByScript.clear();
5412
- }
5413
- // Contract entity management methods
5414
- async getContracts(filter) {
5415
- const contracts = this.contractsByScript.values();
5416
- if (!filter) {
5417
- return [...contracts];
5506
+ // src/repositories/indexedDB/idbUtils.ts
5507
+ var promisifyRequest = (request) => new Promise((resolve, reject) => {
5508
+ request.onsuccess = () => resolve(request.result);
5509
+ request.onerror = () => reject(request.error);
5510
+ });
5511
+ var awaitTransaction = (transaction) => new Promise((resolve, reject) => {
5512
+ transaction.oncomplete = () => resolve();
5513
+ transaction.onerror = () => reject(transaction.error);
5514
+ transaction.onabort = () => reject(transaction.error ?? new Error("transaction aborted"));
5515
+ });
5516
+
5517
+ // src/repositories/indexedDB/manager.ts
5518
+ function getGlobalObject() {
5519
+ if (typeof globalThis !== "undefined") {
5520
+ if (typeof globalThis.self === "object" && globalThis.self !== null) {
5521
+ return { globalObject: globalThis.self };
5418
5522
  }
5419
- const matches = (value, criterion) => {
5420
- if (criterion === void 0) {
5421
- return true;
5422
- }
5423
- return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
5424
- };
5425
- const results = [];
5426
- for (const contract of contracts) {
5427
- if (matches(contract.script, filter.script) && matches(contract.state, filter.state) && matches(contract.type, filter.type)) {
5428
- results.push(contract);
5429
- }
5523
+ if (typeof globalThis.window === "object" && globalThis.window !== null) {
5524
+ return { globalObject: globalThis.window };
5430
5525
  }
5431
- return results;
5526
+ return { globalObject: globalThis };
5432
5527
  }
5433
- async saveContract(contract) {
5434
- this.contractsByScript.set(contract.script, contract);
5528
+ throw new Error("Global object not found");
5529
+ }
5530
+ var dbCache = /* @__PURE__ */ new Map();
5531
+ var refCounts = /* @__PURE__ */ new Map();
5532
+ async function openDatabase(dbName, dbVersion, initDatabase2) {
5533
+ const { globalObject } = getGlobalObject();
5534
+ if (!globalObject.indexedDB) {
5535
+ throw new Error("IndexedDB is not available in this environment");
5435
5536
  }
5436
- async deleteContract(script) {
5437
- this.contractsByScript.delete(script);
5537
+ const cached = dbCache.get(dbName);
5538
+ if (cached) {
5539
+ if (cached.version !== dbVersion) {
5540
+ throw new Error(
5541
+ `Database "${dbName}" already opened with version ${cached.version}; requested ${dbVersion}`
5542
+ );
5543
+ }
5544
+ refCounts.set(dbName, (refCounts.get(dbName) ?? 0) + 1);
5545
+ return cached.promise;
5438
5546
  }
5439
- async [Symbol.asyncDispose]() {
5440
- return;
5547
+ const dbPromise = new Promise((resolve, reject) => {
5548
+ const request = globalObject.indexedDB.open(dbName, dbVersion);
5549
+ request.onerror = () => {
5550
+ dbCache.delete(dbName);
5551
+ refCounts.delete(dbName);
5552
+ reject(request.error);
5553
+ };
5554
+ request.onsuccess = () => {
5555
+ const db = request.result;
5556
+ db.onversionchange = () => {
5557
+ db.close();
5558
+ dbCache.delete(dbName);
5559
+ refCounts.delete(dbName);
5560
+ };
5561
+ resolve(db);
5562
+ };
5563
+ request.onupgradeneeded = (event) => {
5564
+ const db = request.result;
5565
+ initDatabase2(db, event.oldVersion, request.transaction);
5566
+ };
5567
+ request.onblocked = () => {
5568
+ console.warn("Database upgrade blocked - close other tabs/connections");
5569
+ };
5570
+ });
5571
+ dbCache.set(dbName, { version: dbVersion, promise: dbPromise });
5572
+ refCounts.set(dbName, 1);
5573
+ return dbPromise;
5574
+ }
5575
+ async function closeDatabase(dbName) {
5576
+ const cachedEntry = dbCache.get(dbName);
5577
+ if (!cachedEntry) return false;
5578
+ const count = (refCounts.get(dbName) ?? 1) - 1;
5579
+ if (count > 0) {
5580
+ refCounts.set(dbName, count);
5581
+ return false;
5441
5582
  }
5442
- };
5583
+ refCounts.delete(dbName);
5584
+ dbCache.delete(dbName);
5585
+ try {
5586
+ const db = await cachedEntry.promise;
5587
+ db.close();
5588
+ } catch {
5589
+ }
5590
+ return true;
5591
+ }
5443
5592
  function scriptFromArkAddress(address) {
5444
5593
  return hex.encode(ArkAddress.decode(address).pkScript);
5445
5594
  }
@@ -5450,8 +5599,12 @@ var STORE_UTXOS = "utxos";
5450
5599
  var STORE_TRANSACTIONS = "transactions";
5451
5600
  var STORE_WALLET_STATE = "walletState";
5452
5601
  var STORE_CONTRACTS = "contracts";
5602
+ var STORE_INTENTS = "intents";
5603
+ var STORE_VIRTUAL_TXS = "virtualTxs";
5604
+ var STORE_VTXO_BRANCHES = "vtxoBranches";
5453
5605
  var LEGACY_STORE_CONTRACT_COLLECTIONS = "contractsCollections";
5454
5606
  var DB_VERSION = 3;
5607
+ var INTENT_DB_VERSION = 5;
5455
5608
  function initDatabase(db, oldVersion, transaction) {
5456
5609
  if (!db.objectStoreNames.contains(STORE_VTXOS)) {
5457
5610
  const vtxosStore = db.createObjectStore(STORE_VTXOS, {
@@ -5603,20 +5756,493 @@ function initDatabase(db, oldVersion, transaction) {
5603
5756
  backfillVtxoScripts(transaction);
5604
5757
  }
5605
5758
  }
5606
- function backfillVtxoScripts(transaction) {
5607
- const store = transaction.objectStore(STORE_VTXOS);
5608
- const cursorRequest = store.openCursor();
5609
- cursorRequest.onsuccess = () => {
5610
- const cursor = cursorRequest.result;
5611
- if (!cursor) return;
5759
+ function initDatabaseWithIntents(db, oldVersion, transaction) {
5760
+ initDatabase(db, oldVersion, transaction);
5761
+ if (!db.objectStoreNames.contains(STORE_INTENTS)) {
5762
+ const intentsStore = db.createObjectStore(STORE_INTENTS, {
5763
+ keyPath: "intentTxId"
5764
+ });
5765
+ intentsStore.createIndex("intentId", "intentId", { unique: true });
5766
+ intentsStore.createIndex("state", "state", { unique: false });
5767
+ }
5768
+ if (!db.objectStoreNames.contains(STORE_VIRTUAL_TXS)) {
5769
+ db.createObjectStore(STORE_VIRTUAL_TXS, { keyPath: "txid" });
5770
+ }
5771
+ if (!db.objectStoreNames.contains(STORE_VTXO_BRANCHES)) {
5772
+ const branchesStore = db.createObjectStore(STORE_VTXO_BRANCHES, {
5773
+ keyPath: ["vtxoTxid", "vtxoVout", "position"]
5774
+ });
5775
+ branchesStore.createIndex("vtxo", ["vtxoTxid", "vtxoVout"], { unique: false });
5776
+ branchesStore.createIndex("virtualTxid", "virtualTxid", { unique: false });
5777
+ }
5778
+ if (oldVersion === 4 && transaction) {
5779
+ const intentsStore = transaction.objectStore(STORE_INTENTS);
5780
+ if (intentsStore.indexNames.contains("intentId")) {
5781
+ intentsStore.deleteIndex("intentId");
5782
+ }
5783
+ dedupeIntentIds(intentsStore, () => {
5784
+ intentsStore.createIndex("intentId", "intentId", { unique: true });
5785
+ });
5786
+ }
5787
+ }
5788
+ function dedupeIntentIds(store, onComplete) {
5789
+ const seen = /* @__PURE__ */ new Set();
5790
+ const cursorRequest = store.openCursor();
5791
+ cursorRequest.onsuccess = () => {
5792
+ const cursor = cursorRequest.result;
5793
+ if (!cursor) {
5794
+ onComplete();
5795
+ return;
5796
+ }
5797
+ const id = cursor.value.intentId;
5798
+ if (id != null) {
5799
+ if (seen.has(id)) {
5800
+ cursor.delete();
5801
+ } else {
5802
+ seen.add(id);
5803
+ }
5804
+ }
5805
+ cursor.continue();
5806
+ };
5807
+ }
5808
+ function backfillVtxoScripts(transaction) {
5809
+ const store = transaction.objectStore(STORE_VTXOS);
5810
+ const cursorRequest = store.openCursor();
5811
+ cursorRequest.onsuccess = () => {
5812
+ const cursor = cursorRequest.result;
5813
+ if (!cursor) return;
5612
5814
  const value = cursor.value;
5613
5815
  if (!value.script) {
5614
5816
  value.script = scriptFromArkAddress(value.address);
5615
5817
  cursor.update(value);
5616
5818
  }
5617
- cursor.continue();
5618
- };
5619
- }
5819
+ cursor.continue();
5820
+ };
5821
+ }
5822
+
5823
+ // src/worker/browser/utils.ts
5824
+ var DEFAULT_DB_NAME = "arkade-service-worker";
5825
+ var DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 1e4;
5826
+ function normalizeOptions2(pathOrOptions) {
5827
+ if (typeof pathOrOptions === "string") {
5828
+ return {
5829
+ path: pathOrOptions,
5830
+ activationTimeoutMs: DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5831
+ };
5832
+ }
5833
+ return {
5834
+ path: pathOrOptions.path,
5835
+ activationTimeoutMs: pathOrOptions.activationTimeoutMs ?? DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5836
+ };
5837
+ }
5838
+ function waitForServiceWorkerReady(timeoutMs) {
5839
+ return new Promise((resolve, reject) => {
5840
+ const timeoutId = setTimeout(() => {
5841
+ reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
5842
+ }, timeoutMs);
5843
+ navigator.serviceWorker.ready.then((registration) => {
5844
+ clearTimeout(timeoutId);
5845
+ resolve(registration);
5846
+ }).catch((error) => {
5847
+ clearTimeout(timeoutId);
5848
+ reject(error);
5849
+ });
5850
+ });
5851
+ }
5852
+ async function setupServiceWorker(pathOrOptions) {
5853
+ if (!("serviceWorker" in navigator)) {
5854
+ throw new Error("Service workers are not supported in this browser");
5855
+ }
5856
+ const { path, activationTimeoutMs } = normalizeOptions2(pathOrOptions);
5857
+ const registration = await navigator.serviceWorker.register(path);
5858
+ await registration.update();
5859
+ const serviceWorker = registration.active || registration.waiting || registration.installing;
5860
+ if (!serviceWorker) {
5861
+ throw new Error("Failed to get service worker instance");
5862
+ }
5863
+ if (serviceWorker.state === "activated") {
5864
+ return serviceWorker;
5865
+ }
5866
+ const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
5867
+ if (!readyRegistration.active) {
5868
+ throw new Error("Service worker registration is ready but has no active worker");
5869
+ }
5870
+ return readyRegistration.active;
5871
+ }
5872
+
5873
+ // src/repositories/indexedDB/virtualTxRepository.ts
5874
+ var IndexedDBVirtualTxRepository = class {
5875
+ constructor(dbName = DEFAULT_DB_NAME) {
5876
+ this.dbName = dbName;
5877
+ }
5878
+ version = 1;
5879
+ db = null;
5880
+ async getDB() {
5881
+ if (!this.db)
5882
+ this.db = await openDatabase(this.dbName, INTENT_DB_VERSION, initDatabaseWithIntents);
5883
+ return this.db;
5884
+ }
5885
+ async clear() {
5886
+ const db = await this.getDB();
5887
+ const transaction = db.transaction([STORE_VIRTUAL_TXS, STORE_VTXO_BRANCHES], "readwrite");
5888
+ transaction.objectStore(STORE_VIRTUAL_TXS).clear();
5889
+ transaction.objectStore(STORE_VTXO_BRANCHES).clear();
5890
+ await awaitTransaction(transaction);
5891
+ }
5892
+ async upsertVirtualTxs(txs) {
5893
+ if (txs.length === 0) return;
5894
+ const db = await this.getDB();
5895
+ const merged = /* @__PURE__ */ new Map();
5896
+ for (const tx of txs) {
5897
+ const acc = merged.get(tx.txid);
5898
+ merged.set(
5899
+ tx.txid,
5900
+ acc ? {
5901
+ txid: tx.txid,
5902
+ psbt: tx.psbt ?? acc.psbt,
5903
+ expiresAt: tx.expiresAt ?? acc.expiresAt,
5904
+ type: mergeChainedTxType(tx.type, acc.type)
5905
+ } : tx
5906
+ );
5907
+ }
5908
+ const transaction = db.transaction([STORE_VIRTUAL_TXS], "readwrite");
5909
+ const store = transaction.objectStore(STORE_VIRTUAL_TXS);
5910
+ for (const tx of merged.values()) {
5911
+ const getReq = store.get(tx.txid);
5912
+ getReq.onsuccess = () => {
5913
+ const prev = getReq.result;
5914
+ store.put({
5915
+ txid: tx.txid,
5916
+ psbt: tx.psbt ?? prev?.psbt ?? null,
5917
+ expiresAt: tx.expiresAt ?? prev?.expiresAt ?? null,
5918
+ type: mergeChainedTxType(tx.type, prev?.type)
5919
+ });
5920
+ };
5921
+ }
5922
+ await awaitTransaction(transaction);
5923
+ }
5924
+ async getVirtualTx(txid) {
5925
+ const db = await this.getDB();
5926
+ const store = db.transaction([STORE_VIRTUAL_TXS], "readonly").objectStore(STORE_VIRTUAL_TXS);
5927
+ const r = await promisifyRequest(store.get(txid));
5928
+ return r ?? null;
5929
+ }
5930
+ async setBranch(vtxo, branch) {
5931
+ const db = await this.getDB();
5932
+ const transaction = db.transaction([STORE_VTXO_BRANCHES], "readwrite");
5933
+ const store = transaction.objectStore(STORE_VTXO_BRANCHES);
5934
+ const getAllReq = store.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5935
+ getAllReq.onsuccess = () => {
5936
+ const existing = getAllReq.result;
5937
+ for (const e of existing) store.delete([e.vtxoTxid, e.vtxoVout, e.position]);
5938
+ for (const b of branch) store.put(b);
5939
+ };
5940
+ await awaitTransaction(transaction);
5941
+ }
5942
+ async getBranch(vtxo) {
5943
+ const db = await this.getDB();
5944
+ const transaction = db.transaction([STORE_VTXO_BRANCHES, STORE_VIRTUAL_TXS], "readonly");
5945
+ const branchStore = transaction.objectStore(STORE_VTXO_BRANCHES);
5946
+ const txStore = transaction.objectStore(STORE_VIRTUAL_TXS);
5947
+ const getAllReq = branchStore.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5948
+ const byTxid = /* @__PURE__ */ new Map();
5949
+ let ordered = [];
5950
+ getAllReq.onsuccess = () => {
5951
+ ordered = getAllReq.result.slice().sort((a, b) => a.position - b.position);
5952
+ for (const b of ordered) {
5953
+ const txReq = txStore.get(b.virtualTxid);
5954
+ txReq.onsuccess = () => {
5955
+ const r = txReq.result;
5956
+ if (r) byTxid.set(b.virtualTxid, r);
5957
+ };
5958
+ }
5959
+ };
5960
+ await awaitTransaction(transaction);
5961
+ const out = [];
5962
+ for (const b of ordered) {
5963
+ const tx = byTxid.get(b.virtualTxid);
5964
+ if (tx) out.push(tx);
5965
+ }
5966
+ return out;
5967
+ }
5968
+ async hasBranch(vtxo) {
5969
+ const db = await this.getDB();
5970
+ const vtxoIndex = db.transaction([STORE_VTXO_BRANCHES], "readonly").objectStore(STORE_VTXO_BRANCHES).index("vtxo");
5971
+ const count = await promisifyRequest(
5972
+ vtxoIndex.count(IDBKeyRange.only([vtxo.txid, vtxo.vout]))
5973
+ );
5974
+ return count > 0;
5975
+ }
5976
+ async pruneForSpentVtxo(vtxo) {
5977
+ const db = await this.getDB();
5978
+ const transaction = db.transaction([STORE_VTXO_BRANCHES, STORE_VIRTUAL_TXS], "readwrite");
5979
+ const branches = transaction.objectStore(STORE_VTXO_BRANCHES);
5980
+ const txStore = transaction.objectStore(STORE_VIRTUAL_TXS);
5981
+ const getAllReq = branches.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5982
+ getAllReq.onsuccess = () => {
5983
+ const removed = getAllReq.result;
5984
+ if (removed.length === 0) return;
5985
+ for (const e of removed) branches.delete([e.vtxoTxid, e.vtxoVout, e.position]);
5986
+ for (const e of removed) {
5987
+ const countReq = branches.index("virtualTxid").count(IDBKeyRange.only(e.virtualTxid));
5988
+ countReq.onsuccess = () => {
5989
+ if (countReq.result === 0) txStore.delete(e.virtualTxid);
5990
+ };
5991
+ }
5992
+ };
5993
+ await awaitTransaction(transaction);
5994
+ }
5995
+ async [Symbol.asyncDispose]() {
5996
+ if (!this.db) return;
5997
+ await closeDatabase(this.dbName);
5998
+ this.db = null;
5999
+ }
6000
+ };
6001
+
6002
+ // src/repositories/indexedDB/intentRepository.ts
6003
+ var IndexedDBIntentRepository = class {
6004
+ constructor(dbName = DEFAULT_DB_NAME) {
6005
+ this.dbName = dbName;
6006
+ }
6007
+ version = 1;
6008
+ db = null;
6009
+ async getDB() {
6010
+ if (!this.db)
6011
+ this.db = await openDatabase(this.dbName, INTENT_DB_VERSION, initDatabaseWithIntents);
6012
+ return this.db;
6013
+ }
6014
+ async clear() {
6015
+ const db = await this.getDB();
6016
+ const transaction = db.transaction([STORE_INTENTS], "readwrite");
6017
+ transaction.objectStore(STORE_INTENTS).clear();
6018
+ await awaitTransaction(transaction);
6019
+ }
6020
+ async saveIntent(intent) {
6021
+ const db = await this.getDB();
6022
+ const transaction = db.transaction([STORE_INTENTS], "readwrite");
6023
+ transaction.objectStore(STORE_INTENTS).put({ ...intent, updatedAt: Date.now() });
6024
+ await awaitTransaction(transaction);
6025
+ }
6026
+ async getIntents(filter) {
6027
+ const db = await this.getDB();
6028
+ const store = db.transaction([STORE_INTENTS], "readonly").objectStore(STORE_INTENTS);
6029
+ const all = await promisifyRequest(store.getAll());
6030
+ all.sort((a, b) => a.createdAt - b.createdAt || a.intentTxId.localeCompare(b.intentTxId));
6031
+ const out = filter ? all.filter((i) => intentMatchesFilter(i, filter)) : all;
6032
+ const { skip, end } = intentPageBounds(filter, out.length);
6033
+ return out.slice(skip, end);
6034
+ }
6035
+ async getLockedVtxoOutpoints() {
6036
+ const db = await this.getDB();
6037
+ const store = db.transaction([STORE_INTENTS], "readonly").objectStore(STORE_INTENTS);
6038
+ const all = await promisifyRequest(store.getAll());
6039
+ const out = [];
6040
+ for (const i of all)
6041
+ if (!isTerminalIntentState(i.state)) for (const o of i.intentVtxos) out.push(o);
6042
+ return out;
6043
+ }
6044
+ async [Symbol.asyncDispose]() {
6045
+ if (!this.db) return;
6046
+ await closeDatabase(this.dbName);
6047
+ this.db = null;
6048
+ }
6049
+ };
6050
+
6051
+ // src/contracts/vtxoOwnership.ts
6052
+ function vtxoOutpoint(vtxo) {
6053
+ return `${vtxo.txid}:${vtxo.vout}`;
6054
+ }
6055
+ function isVtxoForScript(vtxo, script) {
6056
+ return !!vtxo.script && vtxo.script === script;
6057
+ }
6058
+ function filterVtxosForScript(vtxos, script) {
6059
+ return vtxos.filter((v) => isVtxoForScript(v, script));
6060
+ }
6061
+ function warnAndFilterVtxosForScript(vtxos, script, context) {
6062
+ const matches = [];
6063
+ const rejected = [];
6064
+ for (const v of vtxos) {
6065
+ if (isVtxoForScript(v, script)) {
6066
+ matches.push(v);
6067
+ } else {
6068
+ rejected.push(`${vtxoOutpoint(v)}(script=${v.script ?? ""})`);
6069
+ }
6070
+ }
6071
+ if (rejected.length > 0) {
6072
+ console.warn(
6073
+ `${context}: dropped ${rejected.length} wrong-script VTXO(s) for script ${script}: ${rejected.join(", ")}`
6074
+ );
6075
+ }
6076
+ return matches;
6077
+ }
6078
+ function validateVtxosForScript(vtxos, script, context) {
6079
+ const mismatches = vtxos.filter((v) => !isVtxoForScript(v, script));
6080
+ if (mismatches.length === 0) return;
6081
+ const detail = mismatches.map((v) => `${vtxoOutpoint(v)}(script=${v.script ?? ""})`).join(", ");
6082
+ throw new Error(
6083
+ `${context}: refusing to persist ${mismatches.length} VTXO(s) whose script does not match ${script}: ${detail}`
6084
+ );
6085
+ }
6086
+ async function getVtxosForContract(repo, contract) {
6087
+ return repo.getVtxosForScript ? repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
6088
+ }
6089
+ async function saveVtxosForContract(repo, contract, vtxos) {
6090
+ if (repo.saveVtxosForScript) {
6091
+ return repo.saveVtxosForScript(
6092
+ { script: contract.script, address: contract.address },
6093
+ vtxos
6094
+ );
6095
+ }
6096
+ validateVtxosForScript(vtxos, contract.script, "saveVtxosForContract");
6097
+ return repo.saveVtxos(contract.address, vtxos);
6098
+ }
6099
+
6100
+ // src/repositories/inMemory/walletRepository.ts
6101
+ var InMemoryWalletRepository = class {
6102
+ version = 1;
6103
+ vtxosByAddress = /* @__PURE__ */ new Map();
6104
+ utxosByAddress = /* @__PURE__ */ new Map();
6105
+ txsByAddress = /* @__PURE__ */ new Map();
6106
+ walletState = null;
6107
+ async getVtxos(address) {
6108
+ return this.vtxosByAddress.get(address) ?? [];
6109
+ }
6110
+ async saveVtxos(address, vtxos) {
6111
+ const existing = this.vtxosByAddress.get(address) ?? [];
6112
+ const next = mergeByKey(existing, vtxos, (item) => `${item.txid}:${item.vout}`);
6113
+ this.vtxosByAddress.set(address, next);
6114
+ }
6115
+ async deleteVtxos(address) {
6116
+ this.vtxosByAddress.delete(address);
6117
+ }
6118
+ async getVtxosForScript(script) {
6119
+ const allMatches = [];
6120
+ for (const bucket of this.vtxosByAddress.values()) {
6121
+ for (const vtxo of bucket) {
6122
+ if (isVtxoForScript(vtxo, script)) {
6123
+ allMatches.push(vtxo);
6124
+ }
6125
+ }
6126
+ }
6127
+ return mergeByKey([], allMatches, (item) => `${item.txid}:${item.vout}`);
6128
+ }
6129
+ async saveVtxosForScript(key, vtxos) {
6130
+ if (!key.address) {
6131
+ throw new Error("InMemoryWalletRepository requires an address");
6132
+ }
6133
+ for (const vtxo of vtxos) {
6134
+ if (!isVtxoForScript(vtxo, key.script)) {
6135
+ throw new Error(
6136
+ `VTXO ${vtxo.txid}:${vtxo.vout} script mismatch: expected ${key.script}, got ${vtxo.script}`
6137
+ );
6138
+ }
6139
+ }
6140
+ return this.saveVtxos(key.address, vtxos);
6141
+ }
6142
+ async deleteVtxosForScript(script) {
6143
+ for (const [address, bucket] of this.vtxosByAddress.entries()) {
6144
+ const next = bucket.filter((v) => !isVtxoForScript(v, script));
6145
+ if (next.length === 0) {
6146
+ this.vtxosByAddress.delete(address);
6147
+ } else {
6148
+ this.vtxosByAddress.set(address, next);
6149
+ }
6150
+ }
6151
+ }
6152
+ async getUtxos(address) {
6153
+ return this.utxosByAddress.get(address) ?? [];
6154
+ }
6155
+ async saveUtxos(address, utxos) {
6156
+ const existing = this.utxosByAddress.get(address) ?? [];
6157
+ const next = mergeByKey(existing, utxos, (item) => `${item.txid}:${item.vout}`);
6158
+ this.utxosByAddress.set(address, next);
6159
+ }
6160
+ async deleteUtxos(address) {
6161
+ this.utxosByAddress.delete(address);
6162
+ }
6163
+ async getTransactionHistory(address) {
6164
+ return this.txsByAddress.get(address) ?? [];
6165
+ }
6166
+ async saveTransactions(address, txs) {
6167
+ const existing = this.txsByAddress.get(address) ?? [];
6168
+ const next = mergeByKey(existing, txs, serializeTxKey);
6169
+ this.txsByAddress.set(address, next);
6170
+ }
6171
+ async deleteTransactions(address) {
6172
+ this.txsByAddress.delete(address);
6173
+ }
6174
+ async getWalletState() {
6175
+ return this.walletState;
6176
+ }
6177
+ async saveWalletState(state) {
6178
+ this.walletState = state;
6179
+ }
6180
+ async clear() {
6181
+ this.vtxosByAddress.clear();
6182
+ this.utxosByAddress.clear();
6183
+ this.txsByAddress.clear();
6184
+ this.walletState = null;
6185
+ }
6186
+ async [Symbol.asyncDispose]() {
6187
+ return;
6188
+ }
6189
+ };
6190
+ function serializeTxKey(tx) {
6191
+ const key = tx.key;
6192
+ return `${key.boardingTxid}:${key.commitmentTxid}:${key.arkTxid}`;
6193
+ }
6194
+ function mergeByKey(existing, incoming, toKey) {
6195
+ const next = /* @__PURE__ */ new Map();
6196
+ existing.forEach((item) => {
6197
+ next.set(toKey(item), item);
6198
+ });
6199
+ incoming.forEach((item) => {
6200
+ next.set(toKey(item), item);
6201
+ });
6202
+ return Array.from(next.values());
6203
+ }
6204
+
6205
+ // src/repositories/inMemory/contractRepository.ts
6206
+ var InMemoryContractRepository = class {
6207
+ version = 1;
6208
+ contractData = /* @__PURE__ */ new Map();
6209
+ collections = /* @__PURE__ */ new Map();
6210
+ contractsByScript = /* @__PURE__ */ new Map();
6211
+ async clear() {
6212
+ this.contractData.clear();
6213
+ this.collections.clear();
6214
+ this.contractsByScript.clear();
6215
+ }
6216
+ // Contract entity management methods
6217
+ async getContracts(filter) {
6218
+ const contracts = this.contractsByScript.values();
6219
+ if (!filter) {
6220
+ return [...contracts];
6221
+ }
6222
+ const matches = (value, criterion) => {
6223
+ if (criterion === void 0) {
6224
+ return true;
6225
+ }
6226
+ return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
6227
+ };
6228
+ const results = [];
6229
+ for (const contract of contracts) {
6230
+ if (matches(contract.script, filter.script) && matches(contract.state, filter.state) && matches(contract.type, filter.type)) {
6231
+ results.push(contract);
6232
+ }
6233
+ }
6234
+ return results;
6235
+ }
6236
+ async saveContract(contract) {
6237
+ this.contractsByScript.set(contract.script, contract);
6238
+ }
6239
+ async deleteContract(script) {
6240
+ this.contractsByScript.delete(script);
6241
+ }
6242
+ async [Symbol.asyncDispose]() {
6243
+ return;
6244
+ }
6245
+ };
5620
6246
  var serializeTapLeaf = ([cb, s]) => ({
5621
6247
  cb: hex.encode(TaprootControlBlock.encode(cb)),
5622
6248
  s: hex.encode(s)
@@ -5683,132 +6309,6 @@ var deserializeTransaction = (o) => ({
5683
6309
  assets: deserializeAssets(o.assets)
5684
6310
  });
5685
6311
 
5686
- // src/repositories/indexedDB/manager.ts
5687
- function getGlobalObject() {
5688
- if (typeof globalThis !== "undefined") {
5689
- if (typeof globalThis.self === "object" && globalThis.self !== null) {
5690
- return { globalObject: globalThis.self };
5691
- }
5692
- if (typeof globalThis.window === "object" && globalThis.window !== null) {
5693
- return { globalObject: globalThis.window };
5694
- }
5695
- return { globalObject: globalThis };
5696
- }
5697
- throw new Error("Global object not found");
5698
- }
5699
- var dbCache = /* @__PURE__ */ new Map();
5700
- var refCounts = /* @__PURE__ */ new Map();
5701
- async function openDatabase(dbName, dbVersion, initDatabase2) {
5702
- const { globalObject } = getGlobalObject();
5703
- if (!globalObject.indexedDB) {
5704
- throw new Error("IndexedDB is not available in this environment");
5705
- }
5706
- const cached = dbCache.get(dbName);
5707
- if (cached) {
5708
- if (cached.version !== dbVersion) {
5709
- throw new Error(
5710
- `Database "${dbName}" already opened with version ${cached.version}; requested ${dbVersion}`
5711
- );
5712
- }
5713
- refCounts.set(dbName, (refCounts.get(dbName) ?? 0) + 1);
5714
- return cached.promise;
5715
- }
5716
- const dbPromise = new Promise((resolve, reject) => {
5717
- const request = globalObject.indexedDB.open(dbName, dbVersion);
5718
- request.onerror = () => {
5719
- dbCache.delete(dbName);
5720
- refCounts.delete(dbName);
5721
- reject(request.error);
5722
- };
5723
- request.onsuccess = () => {
5724
- const db = request.result;
5725
- db.onversionchange = () => {
5726
- db.close();
5727
- dbCache.delete(dbName);
5728
- refCounts.delete(dbName);
5729
- };
5730
- resolve(db);
5731
- };
5732
- request.onupgradeneeded = (event) => {
5733
- const db = request.result;
5734
- initDatabase2(db, event.oldVersion, request.transaction);
5735
- };
5736
- request.onblocked = () => {
5737
- console.warn("Database upgrade blocked - close other tabs/connections");
5738
- };
5739
- });
5740
- dbCache.set(dbName, { version: dbVersion, promise: dbPromise });
5741
- refCounts.set(dbName, 1);
5742
- return dbPromise;
5743
- }
5744
- async function closeDatabase(dbName) {
5745
- const cachedEntry = dbCache.get(dbName);
5746
- if (!cachedEntry) return false;
5747
- const count = (refCounts.get(dbName) ?? 1) - 1;
5748
- if (count > 0) {
5749
- refCounts.set(dbName, count);
5750
- return false;
5751
- }
5752
- refCounts.delete(dbName);
5753
- dbCache.delete(dbName);
5754
- try {
5755
- const db = await cachedEntry.promise;
5756
- db.close();
5757
- } catch {
5758
- }
5759
- return true;
5760
- }
5761
-
5762
- // src/worker/browser/utils.ts
5763
- var DEFAULT_DB_NAME = "arkade-service-worker";
5764
- var DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 1e4;
5765
- function normalizeOptions2(pathOrOptions) {
5766
- if (typeof pathOrOptions === "string") {
5767
- return {
5768
- path: pathOrOptions,
5769
- activationTimeoutMs: DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5770
- };
5771
- }
5772
- return {
5773
- path: pathOrOptions.path,
5774
- activationTimeoutMs: pathOrOptions.activationTimeoutMs ?? DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5775
- };
5776
- }
5777
- function waitForServiceWorkerReady(timeoutMs) {
5778
- return new Promise((resolve, reject) => {
5779
- const timeoutId = setTimeout(() => {
5780
- reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
5781
- }, timeoutMs);
5782
- navigator.serviceWorker.ready.then((registration) => {
5783
- clearTimeout(timeoutId);
5784
- resolve(registration);
5785
- }).catch((error) => {
5786
- clearTimeout(timeoutId);
5787
- reject(error);
5788
- });
5789
- });
5790
- }
5791
- async function setupServiceWorker(pathOrOptions) {
5792
- if (!("serviceWorker" in navigator)) {
5793
- throw new Error("Service workers are not supported in this browser");
5794
- }
5795
- const { path, activationTimeoutMs } = normalizeOptions2(pathOrOptions);
5796
- const registration = await navigator.serviceWorker.register(path);
5797
- await registration.update();
5798
- const serviceWorker = registration.active || registration.waiting || registration.installing;
5799
- if (!serviceWorker) {
5800
- throw new Error("Failed to get service worker instance");
5801
- }
5802
- if (serviceWorker.state === "activated") {
5803
- return serviceWorker;
5804
- }
5805
- const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
5806
- if (!readyRegistration.active) {
5807
- throw new Error("Service worker registration is ready but has no active worker");
5808
- }
5809
- return readyRegistration.active;
5810
- }
5811
-
5812
6312
  // src/repositories/indexedDB/contractRepository.ts
5813
6313
  var IndexedDBContractRepository = class {
5814
6314
  constructor(dbName = DEFAULT_DB_NAME) {
@@ -7246,6 +7746,89 @@ var ContractWatcher = class {
7246
7746
  }
7247
7747
  };
7248
7748
 
7749
+ // src/wallet/intentReconciliation.ts
7750
+ var NON_TERMINAL_INTENT_STATES = ALL_INTENT_STATES.filter(
7751
+ (s) => !isTerminalIntentState(s)
7752
+ );
7753
+ function isConsumed(vtxo) {
7754
+ return vtxo !== void 0 && (vtxo.isSpent === true || vtxo.settledBy !== void 0);
7755
+ }
7756
+ async function classifyIntent(intent, vtxosByOutpoint, now) {
7757
+ if (isTerminalIntentState(intent.state)) return void 0;
7758
+ const inputs = intent.intentVtxos;
7759
+ const consumed = inputs.map((o) => vtxosByOutpoint.get(outpointKey(o)));
7760
+ if (inputs.length > 0 && consumed.every(isConsumed)) {
7761
+ const commitmentTransactionId = intent.commitmentTransactionId ?? consumed.map((v) => v?.settledBy).find((s) => !!s);
7762
+ return terminal(intent, "batch_succeeded", now, { commitmentTransactionId });
7763
+ }
7764
+ if (intent.state === "waiting_to_submit") {
7765
+ return terminal(intent, "cancelled", now, {
7766
+ cancellationReason: "reconciliation: intent was never submitted before restart"
7767
+ });
7768
+ }
7769
+ if (intent.validUntil !== void 0 && intent.validUntil < now) {
7770
+ return terminal(intent, "cancelled", now, {
7771
+ cancellationReason: "reconciliation: intent expired"
7772
+ });
7773
+ }
7774
+ return void 0;
7775
+ }
7776
+ async function reconcileIntents(deps) {
7777
+ const now = deps.now ?? Date.now;
7778
+ let intents;
7779
+ try {
7780
+ intents = await deps.intentRepository.getIntents({
7781
+ states: [...NON_TERMINAL_INTENT_STATES]
7782
+ });
7783
+ } catch (e) {
7784
+ console.error("reconcileIntents: failed to read non-terminal intents", e);
7785
+ return;
7786
+ }
7787
+ if (intents.length === 0) return;
7788
+ const outpoints = dedupeOutpoints(intents.flatMap((i) => i.intentVtxos));
7789
+ let vtxosByOutpoint = /* @__PURE__ */ new Map();
7790
+ if (outpoints.length > 0) {
7791
+ try {
7792
+ const { vtxos } = await deps.indexerProvider.getVtxos({ outpoints });
7793
+ vtxosByOutpoint = new Map(vtxos.map((v) => [outpointKey(v), v]));
7794
+ } catch (e) {
7795
+ console.error("reconcileIntents: failed to fetch input VTXO state", e);
7796
+ return;
7797
+ }
7798
+ }
7799
+ const nowMs = now();
7800
+ for (const intent of intents) {
7801
+ try {
7802
+ const next = await classifyIntent(intent, vtxosByOutpoint, nowMs);
7803
+ if (!next) continue;
7804
+ const [fresh] = await deps.intentRepository.getIntents({
7805
+ intentTxIds: [intent.intentTxId]
7806
+ });
7807
+ if (!fresh || fresh.state !== intent.state) continue;
7808
+ await deps.intentRepository.saveIntent(next);
7809
+ } catch (e) {
7810
+ console.error(`reconcileIntents: failed to reconcile intent ${intent.intentTxId}`, e);
7811
+ }
7812
+ }
7813
+ }
7814
+ function terminal(intent, state, now, patch) {
7815
+ return { ...intent, ...patch, state, updatedAt: now };
7816
+ }
7817
+ function outpointKey(o) {
7818
+ return `${o.txid}:${o.vout}`;
7819
+ }
7820
+ function dedupeOutpoints(outpoints) {
7821
+ const seen = /* @__PURE__ */ new Set();
7822
+ const out = [];
7823
+ for (const o of outpoints) {
7824
+ const k = outpointKey(o);
7825
+ if (seen.has(k)) continue;
7826
+ seen.add(k);
7827
+ out.push(o);
7828
+ }
7829
+ return out;
7830
+ }
7831
+
7249
7832
  // src/utils/syncCursors.ts
7250
7833
  var SAFETY_LAG_MS = 3e4;
7251
7834
  var OVERLAP_MS = 24 * 60 * 60 * 1e3;
@@ -7369,6 +7952,26 @@ var ContractManager = class _ContractManager {
7369
7952
  if (watched.length > 0) {
7370
7953
  await this.reconcilePendingFrontier(watched);
7371
7954
  }
7955
+ await this.reconcileStaleIntents();
7956
+ }
7957
+ /**
7958
+ * Crash-recovery for persisted settlement intents: reconcile any
7959
+ * non-terminal intent left behind by a mid-settle crash against
7960
+ * authoritative indexer state (see {@link reconcileIntents}). Runs on the
7961
+ * online sync path only — boot and subscription reconnect — never from a
7962
+ * wallet read API. Best-effort: intent recovery is not a sync invariant, so
7963
+ * a failure is logged and sync continues. No-op without an intent store.
7964
+ */
7965
+ async reconcileStaleIntents() {
7966
+ if (!this.config.intentRepository) return;
7967
+ try {
7968
+ await reconcileIntents({
7969
+ intentRepository: this.config.intentRepository,
7970
+ indexerProvider: this.config.indexerProvider
7971
+ });
7972
+ } catch (e) {
7973
+ console.error("ContractManager: intent reconciliation failed", e);
7974
+ }
7372
7975
  }
7373
7976
  /**
7374
7977
  * Create and register a new contract.
@@ -8739,6 +9342,22 @@ async function resolveBoardingBootTapscript(contractRepository, serverPubKey, ba
8739
9342
  function hasToReadonly(identity) {
8740
9343
  return typeof identity === "object" && identity !== null && "toReadonly" in identity && typeof identity.toReadonly === "function";
8741
9344
  }
9345
+ function excludeLockedOutpoints(vtxos, locked) {
9346
+ if (locked.length === 0) return vtxos;
9347
+ const lockedKeys = new Set(locked.map((o) => `${o.txid}:${o.vout}`));
9348
+ return vtxos.filter((v) => !lockedKeys.has(`${v.txid}:${v.vout}`));
9349
+ }
9350
+ async function spendableVtxosExcludingLocked(vtxos, intentRepository) {
9351
+ if (!intentRepository) return vtxos;
9352
+ let locked;
9353
+ try {
9354
+ locked = await intentRepository.getLockedVtxoOutpoints();
9355
+ } catch (e) {
9356
+ console.error("getLockedVtxoOutpoints failed; reporting unfiltered balance", e);
9357
+ return vtxos;
9358
+ }
9359
+ return excludeLockedOutpoints(vtxos, locked);
9360
+ }
8742
9361
  var ReadonlyWallet = class _ReadonlyWallet {
8743
9362
  constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegateProvider, watcherConfig, walletContractTimelocks) {
8744
9363
  this.identity = identity;
@@ -8769,6 +9388,20 @@ var ReadonlyWallet = class _ReadonlyWallet {
8769
9388
  _contractManager;
8770
9389
  _contractManagerInitializing;
8771
9390
  watcherConfig;
9391
+ /**
9392
+ * Opt-in intent-lifecycle repository. Assigned by the `create()`
9393
+ * factories from `config.storage.intentRepository`; `undefined` ⇒ all
9394
+ * intent-persistence code paths are no-ops (default behaviour unchanged).
9395
+ */
9396
+ intentRepository;
9397
+ /**
9398
+ * **Experimental / inert.** Opt-in virtual-tx / exit-branch repository,
9399
+ * exposed so callers can pass it to {@link Unroll.Session.create} as a
9400
+ * best-effort raw-tx cache. Assigned by `create()` from
9401
+ * `config.storage.virtualTxRepository`. Normal sync never writes it
9402
+ * (ContractManager isn't given it); `undefined` ⇒ no-op.
9403
+ */
9404
+ virtualTxRepository;
8772
9405
  _assetManager;
8773
9406
  walletContractTimelocks;
8774
9407
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
@@ -8826,9 +9459,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
8826
9459
  _deprecatedSigners = /* @__PURE__ */ new Map();
8827
9460
  /**
8828
9461
  * Refresh the cached deprecated-signer set from a fresh server-info
8829
- * snapshot. Called by the create() factories at construction and by the
8830
- * server-info-change handler mid-session. Lenient: a malformed deprecated
8831
- * entry is skipped, never fatal to wallet creation.
9462
+ * snapshot. Called by the create() factories at construction, by the
9463
+ * server-info-change handler mid-session, and by the deprecated-signer
9464
+ * migration pass (`VtxoManager.migrateDeprecatedSignerVtxos`). This set feeds
9465
+ * {@link pendingRecoveryOutpoints}, which drops EXPIRED (past-cutoff)
9466
+ * deprecated-signer VTXOs from the wallet's own coin selection. Lenient: a
9467
+ * malformed deprecated entry is skipped, never fatal to wallet creation.
8832
9468
  */
8833
9469
  refreshDeprecatedSigners(info) {
8834
9470
  const next = /* @__PURE__ */ new Map();
@@ -9044,6 +9680,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9044
9680
  config.watcherConfig,
9045
9681
  setup.walletContractTimelocks
9046
9682
  );
9683
+ wallet.intentRepository = config.storage?.intentRepository;
9684
+ wallet.virtualTxRepository = config.storage?.virtualTxRepository;
9047
9685
  wallet.refreshDeprecatedSigners(setup.info);
9048
9686
  return wallet;
9049
9687
  }
@@ -9075,6 +9713,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9075
9713
  this.pendingRecoveryOutpoints()
9076
9714
  ]);
9077
9715
  const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
9716
+ const unlockedVtxos = await spendableVtxosExcludingLocked(vtxos, this.intentRepository);
9078
9717
  let confirmed = 0;
9079
9718
  let unconfirmed = 0;
9080
9719
  for (const utxo of boardingUtxos) {
@@ -9084,14 +9723,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
9084
9723
  unconfirmed += utxo.value;
9085
9724
  }
9086
9725
  }
9087
- let settled = 0;
9088
- let preconfirmed = 0;
9089
9726
  let recoverable = 0;
9090
9727
  let pendingRecovery = 0;
9091
- settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
9092
- preconfirmed = vtxos.filter(
9728
+ const settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
9729
+ const preconfirmed = vtxos.filter(
9093
9730
  (coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
9094
9731
  ).reduce((sum, coin) => sum + coin.value, 0);
9732
+ const available = unlockedVtxos.filter(
9733
+ (coin) => (coin.virtualStatus.state === "settled" || coin.virtualStatus.state === "preconfirmed") && !isPendingRecovery(coin)
9734
+ ).reduce((sum, coin) => sum + coin.value, 0);
9095
9735
  recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
9096
9736
  pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
9097
9737
  const totalBoarding = confirmed + unconfirmed;
@@ -9118,7 +9758,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9118
9758
  },
9119
9759
  settled,
9120
9760
  preconfirmed,
9121
- available: settled + preconfirmed,
9761
+ available,
9122
9762
  recoverable,
9123
9763
  pendingRecovery,
9124
9764
  total: totalBoarding + totalOffchain,
@@ -9595,6 +10235,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9595
10235
  indexerProvider: this.indexerProvider,
9596
10236
  contractRepository: this.contractRepository,
9597
10237
  walletRepository: this.walletRepository,
10238
+ intentRepository: this.intentRepository,
9598
10239
  watcherConfig: this.watcherConfig
9599
10240
  });
9600
10241
  const baselinePubkey = await this.identity.xOnlyPublicKey();
@@ -10288,6 +10929,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10288
10929
  wallet.setBoardingTapscriptForRotation(resolvedBoarding);
10289
10930
  }
10290
10931
  }
10932
+ wallet.intentRepository = config.storage?.intentRepository;
10933
+ wallet.virtualTxRepository = config.storage?.virtualTxRepository;
10291
10934
  await wallet.getVtxoManager();
10292
10935
  return wallet;
10293
10936
  }
@@ -10567,12 +11210,26 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10567
11210
  ),
10568
11211
  this.makeDeleteIntentSignature(params.inputs)
10569
11212
  ]);
11213
+ let intentTxId;
11214
+ try {
11215
+ intentTxId = Transaction$2.fromPSBT(base64.decode(intent.proof)).id;
11216
+ } catch {
11217
+ intentTxId = params.inputs.map((i) => `${i.txid}:${i.vout}`).sort().join("|");
11218
+ }
11219
+ await this.persistIntentSnapshot(
11220
+ intentTxId,
11221
+ "waiting_to_submit",
11222
+ intent,
11223
+ deleteIntent,
11224
+ params.inputs
11225
+ );
10570
11226
  const topics = [
10571
11227
  ...signingPublicKeys,
10572
11228
  ...params.inputs.map((input) => `${input.txid}:${input.vout}`)
10573
11229
  ];
10574
11230
  const abortController = new AbortController();
10575
11231
  let stream;
11232
+ let committedTxid;
10576
11233
  this._addPendingSpends(params.inputs);
10577
11234
  try {
10578
11235
  stream = this.arkProvider.getEventStream(abortController.signal, topics);
@@ -10587,16 +11244,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10587
11244
  yield* stream;
10588
11245
  })();
10589
11246
  const intentId = await this.safeRegisterIntent(intent, params.inputs);
10590
- const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
11247
+ await this.persistIntentSnapshot(
11248
+ intentTxId,
11249
+ "waiting_for_batch",
11250
+ intent,
11251
+ deleteIntent,
11252
+ params.inputs,
11253
+ { intentId }
11254
+ );
11255
+ const handler = wrapHandlerWithIntentPersistence(
11256
+ this.createBatchHandler(intentId, params.inputs, recipients, session),
11257
+ { intentRepository: this.intentRepository, intentTxId }
11258
+ );
10591
11259
  const commitmentTxid = await Batch.join(primedStream, handler, {
10592
11260
  abortController,
10593
11261
  skipVtxoTreeSigning: !hasOffchainOutputs,
10594
- eventCallback: eventCallback ? (event) => Promise.resolve(eventCallback(event)) : void 0
11262
+ // async so a synchronous throw becomes a rejection Batch.join
11263
+ // swallows, keeping the observational callback off the stream path.
11264
+ eventCallback: eventCallback && (async (event) => {
11265
+ await eventCallback(event);
11266
+ })
10595
11267
  });
11268
+ committedTxid = commitmentTxid;
10596
11269
  await this.updateDbAfterSettle(params.inputs, commitmentTxid);
10597
11270
  await this.maybeRotateBoardingAfterBoard(params.inputs);
10598
11271
  return commitmentTxid;
10599
11272
  } catch (error) {
11273
+ if (committedTxid !== void 0) {
11274
+ await this.persistIntentSnapshot(
11275
+ intentTxId,
11276
+ "batch_succeeded",
11277
+ intent,
11278
+ deleteIntent,
11279
+ params.inputs,
11280
+ { commitmentTransactionId: committedTxid }
11281
+ );
11282
+ throw error;
11283
+ }
10600
11284
  const inputIds = params.inputs.map((i) => `${i.txid}:${i.vout}`).join(",");
10601
11285
  await this.arkProvider.deleteIntent(deleteIntent).catch((e) => {
10602
11286
  console.warn(
@@ -10604,6 +11288,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10604
11288
  e
10605
11289
  );
10606
11290
  });
11291
+ await this.persistIntentSnapshot(
11292
+ intentTxId,
11293
+ "cancelled",
11294
+ intent,
11295
+ deleteIntent,
11296
+ params.inputs,
11297
+ {
11298
+ cancellationReason: error instanceof Error ? error.message : String(error)
11299
+ }
11300
+ );
10607
11301
  throw error;
10608
11302
  } finally {
10609
11303
  this._removePendingSpends(params.inputs);
@@ -10878,6 +11572,42 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10878
11572
  }
10879
11573
  return jobs;
10880
11574
  }
11575
+ /**
11576
+ * Best-effort upsert of the current settlement intent into the optional
11577
+ * {@link intentRepository}. Never throws into the settle path — intent
11578
+ * persistence is observational and must not break money flow. Preserves
11579
+ * fields already written by the event reducer (state-event ordering).
11580
+ */
11581
+ async persistIntentSnapshot(intentTxId, state, intent, deleteIntent, inputs, patch) {
11582
+ const repo = this.intentRepository;
11583
+ if (!repo) return;
11584
+ try {
11585
+ const now = Date.now();
11586
+ const existing = (await repo.getIntents({ intentTxIds: [intentTxId] }))[0];
11587
+ if (existing && isTerminalIntentState(existing.state)) {
11588
+ return;
11589
+ }
11590
+ await repo.saveIntent({
11591
+ ...existing ?? {},
11592
+ intentTxId,
11593
+ registerProof: intent.proof,
11594
+ registerProofMessage: Intent.encodeMessage(intent.message),
11595
+ deleteProof: deleteIntent.proof,
11596
+ deleteProofMessage: Intent.encodeMessage(deleteIntent.message),
11597
+ intentVtxos: inputs.map((i) => ({
11598
+ txid: i.txid,
11599
+ vout: i.vout
11600
+ })),
11601
+ partialForfeits: existing?.partialForfeits ?? [],
11602
+ createdAt: existing?.createdAt ?? now,
11603
+ updatedAt: now,
11604
+ state,
11605
+ ...patch
11606
+ });
11607
+ } catch (e) {
11608
+ console.error(`Failed to persist intent ${intentTxId} (state=${state})`, e);
11609
+ }
11610
+ }
10881
11611
  /**
10882
11612
  * @internal Sign an on-chain boarding exit / sweep transaction, routing
10883
11613
  * each input to the correct key by its `witnessUtxo.script`: the identity
@@ -14887,42 +15617,11 @@ var OnchainWallet = class _OnchainWallet {
14887
15617
  * @see send
14888
15618
  */
14889
15619
  async bumpP2A(parent) {
14890
- const parentVsize = parent.vsize;
14891
- let child = new Transaction({
14892
- version: 3,
14893
- allowLegacyWitnessUtxo: true
14894
- });
14895
- child.addInput(findP2AOutput(parent));
14896
- const childVsize = TxWeightEstimator.create().addKeySpendInput(true).addP2AInput().addOutputAddress(this.address, this.network).vsize().value;
14897
- const packageVSize = parentVsize + Number(childVsize);
14898
15620
  let feeRate = await this.provider.getFeeRate();
14899
15621
  if (!feeRate || feeRate < _OnchainWallet.MIN_FEE_RATE) {
14900
15622
  feeRate = _OnchainWallet.MIN_FEE_RATE;
14901
15623
  }
14902
- const fee = Math.ceil(feeRate * packageVSize);
14903
- if (!fee) {
14904
- throw new Error(
14905
- `invalid fee, got ${fee} with vsize ${packageVSize}, feeRate ${feeRate}`
14906
- );
14907
- }
14908
- const coins = await this.getCoins();
14909
- const selected = selectCoins(coins, fee, true);
14910
- for (const input of selected.inputs) {
14911
- child.addInput({
14912
- txid: input.txid,
14913
- index: input.vout,
14914
- witnessUtxo: {
14915
- script: this.onchainP2TR.script,
14916
- amount: BigInt(input.value)
14917
- },
14918
- tapInternalKey: this.onchainP2TR.tapInternalKey
14919
- });
14920
- }
14921
- child.addOutputAddress(this.address, P2A.amount + selected.changeAmount, this.network);
14922
- child = await this.identity.sign(child);
14923
- for (let i = 1; i < child.inputsLength; i++) {
14924
- child.finalizeIdx(i);
14925
- }
15624
+ const child = await this.buildBumpPackage(parent, feeRate, await this.getCoins());
14926
15625
  try {
14927
15626
  await this.provider.broadcastTransaction(parent.hex, child.hex);
14928
15627
  } catch (error) {
@@ -14931,6 +15630,59 @@ var OnchainWallet = class _OnchainWallet {
14931
15630
  return [parent.hex, child.hex];
14932
15631
  }
14933
15632
  }
15633
+ /**
15634
+ * Build and sign a CPFP fee child for a parent tx (given as raw hex)
15635
+ * carrying a P2A anchor, funding it from this wallet's **confirmed**
15636
+ * coins, and return the 1P1C package hexes WITHOUT broadcasting.
15637
+ *
15638
+ * This is the graph-mode fee source ({@link ExitFeeWallet}): the exit
15639
+ * executor calls it to bump each transported virtual tx at execution
15640
+ * time, so funding can be deferred rather than pre-signed.
15641
+ *
15642
+ * @param parentHex - Finalized parent transaction, raw network hex
15643
+ * @param feeRate - sat/vB floor for the package (raised to MIN_FEE_RATE)
15644
+ * @returns Tuple of parent hex (unchanged) and signed child hex
15645
+ * @throws If the parent has no anchor, or funding cannot be selected/signed
15646
+ */
15647
+ async bumpAnchor(parentHex, feeRate) {
15648
+ const parent = Transaction.fromRaw(hex.decode(parentHex));
15649
+ const coins = (await this.getCoins()).filter((c) => c.status.confirmed);
15650
+ const child = await this.buildBumpPackage(parent, feeRate, coins);
15651
+ return [parent.hex, child.hex];
15652
+ }
15653
+ /**
15654
+ * Shared core of {@link bumpP2A} and {@link bumpAnchor}: probe the package
15655
+ * fee with a single-input child, select coins for it, then build and sign
15656
+ * with the actual selection (the fee grows per extra input).
15657
+ */
15658
+ async buildBumpPackage(parent, feeRate, coins) {
15659
+ let rate = feeRate;
15660
+ if (!rate || rate < _OnchainWallet.MIN_FEE_RATE) {
15661
+ rate = _OnchainWallet.MIN_FEE_RATE;
15662
+ }
15663
+ const probeVsize = TxWeightEstimator.create().addKeySpendInput(true).addP2AInput().addOutputAddress(this.address, this.network).vsize().value;
15664
+ const probeFee = Math.ceil(rate * (parent.vsize + Number(probeVsize)));
15665
+ if (!probeFee) {
15666
+ throw new Error(
15667
+ `invalid fee, got ${probeFee} with vsize ${parent.vsize + Number(probeVsize)}, feeRate ${rate}`
15668
+ );
15669
+ }
15670
+ const selected = selectCoins(coins, probeFee, true);
15671
+ const { child } = buildAnchorChild({
15672
+ parent,
15673
+ feeRate: rate,
15674
+ fundingCoins: selected.inputs,
15675
+ changeAddress: this.address,
15676
+ changeScript: this.onchainP2TR.script,
15677
+ tapInternalKey: this.onchainP2TR.tapInternalKey,
15678
+ network: this.network
15679
+ });
15680
+ const signed = await this.identity.sign(child);
15681
+ for (let i = 1; i < signed.inputsLength; i++) {
15682
+ signed.finalizeIdx(i);
15683
+ }
15684
+ return signed;
15685
+ }
14934
15686
  };
14935
15687
  function selectCoins(coins, targetAmount, forceChange = false) {
14936
15688
  if (isNaN(targetAmount)) {
@@ -15821,6 +16573,98 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
15821
16573
  });
15822
16574
  return tx;
15823
16575
  }
16576
+ function finalizeVirtualTx(type, psbtBase64) {
16577
+ const tx = Transaction.fromPSBT(base64.decode(psbtBase64));
16578
+ if (type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
16579
+ const input = tx.getInput(0);
16580
+ if (!input) {
16581
+ throw new Error("Input not found");
16582
+ }
16583
+ const tapKeySig = input.tapKeySig;
16584
+ if (!tapKeySig) {
16585
+ throw new Error("Tap key sig not found");
16586
+ }
16587
+ tx.updateInput(0, {
16588
+ finalScriptWitness: [tapKeySig]
16589
+ });
16590
+ return tx;
16591
+ }
16592
+ tx.finalize();
16593
+ return tx;
16594
+ }
16595
+ var ExitPathError = class extends Error {
16596
+ constructor(reason, message) {
16597
+ super(message);
16598
+ this.reason = reason;
16599
+ this.name = "ExitPathError";
16600
+ }
16601
+ };
16602
+ function delayWeight(sequence) {
16603
+ const t = sequenceToTimelock(sequence);
16604
+ return t.type === "blocks" ? Number(t.value) : Number(t.value) / 600;
16605
+ }
16606
+ async function resolveUnilateralPath(params) {
16607
+ const { vtxo, scriptHex, contractRepository } = params;
16608
+ const contract = contractRepository ? (await contractRepository.getContracts({ script: scriptHex }))[0] : void 0;
16609
+ if (contract) {
16610
+ const handler = contractHandlers.get(contract.type);
16611
+ if (!handler) {
16612
+ throw new ExitPathError(
16613
+ "no-handler",
16614
+ `no contract handler registered for type '${contract.type}'`
16615
+ );
16616
+ }
16617
+ const script = handler.createScript(contract.params);
16618
+ const paths = handler.getAllSpendingPaths(script, contract, {
16619
+ collaborative: false,
16620
+ currentTime: params.currentTime,
16621
+ walletDescriptor: params.walletDescriptor,
16622
+ walletPubKey: params.walletPubKeyHex
16623
+ }).filter((p) => p.sequence !== void 0);
16624
+ if (paths.length === 0) {
16625
+ throw new ExitPathError(
16626
+ "no-unilateral-path",
16627
+ `no unilateral path for vtxo ${vtxo.txid}:${vtxo.vout} (type '${contract.type}')`
16628
+ );
16629
+ }
16630
+ paths.sort((a, b) => delayWeight(a.sequence) - delayWeight(b.sequence));
16631
+ return { selection: paths[0], label: `${contract.type}:unilateral` };
16632
+ }
16633
+ const decoded = VtxoScript.decode(vtxo.tapTree);
16634
+ let best;
16635
+ for (const exit of decoded.exitPaths()) {
16636
+ const leaf = decoded.findLeaf(hex.encode(exit.script));
16637
+ if (!leaf) continue;
16638
+ const sequence = timelockToSequence(exit.params.timelock);
16639
+ const weight = delayWeight(sequence);
16640
+ if (!best || weight < best.weight) {
16641
+ best = { selection: { leaf, sequence }, weight };
16642
+ }
16643
+ }
16644
+ if (!best) {
16645
+ throw new ExitPathError(
16646
+ "no-unilateral-path",
16647
+ `no exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`
16648
+ );
16649
+ }
16650
+ return { selection: best.selection, label: "default:exit" };
16651
+ }
16652
+
16653
+ // src/wallet/unroll.ts
16654
+ function chainTxTypeToChainedExit(t) {
16655
+ switch (t) {
16656
+ case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
16657
+ return 1 /* Commitment */;
16658
+ case "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */:
16659
+ return 2 /* Ark */;
16660
+ case "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */:
16661
+ return 3 /* Tree */;
16662
+ case "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */:
16663
+ return 4 /* Checkpoint */;
16664
+ default:
16665
+ return 0 /* Unspecified */;
16666
+ }
16667
+ }
15824
16668
  var Unroll;
15825
16669
  ((Unroll2) => {
15826
16670
  ((StepType2) => {
@@ -15830,16 +16674,54 @@ var Unroll;
15830
16674
  })(Unroll2.StepType || (Unroll2.StepType = {}));
15831
16675
  class Session2 {
15832
16676
  /** Create an unroll session from a virtual output outpoint and its dependency chain. */
15833
- constructor(toUnroll, bumper, explorer, indexer) {
16677
+ constructor(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
15834
16678
  this.toUnroll = toUnroll;
15835
16679
  this.bumper = bumper;
15836
16680
  this.explorer = explorer;
15837
16681
  this.indexer = indexer;
16682
+ this.virtualTxRepository = virtualTxRepository;
15838
16683
  }
15839
16684
  /** Create an unroll session by loading the virtual output chain from the indexer. */
15840
- static async create(toUnroll, bumper, explorer, indexer) {
16685
+ static async create(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
15841
16686
  const { chain } = await indexer.getVtxoChain(toUnroll);
15842
- return new Session2({ ...toUnroll, chain }, bumper, explorer, indexer);
16687
+ return new Session2(
16688
+ { ...toUnroll, chain },
16689
+ bumper,
16690
+ explorer,
16691
+ indexer,
16692
+ virtualTxRepository
16693
+ );
16694
+ }
16695
+ /**
16696
+ * Resolve a chain tx's raw PSBT (base64). Repo first when configured;
16697
+ * indexer on a miss, then best-effort cached so a later exit doesn't
16698
+ * re-fetch. Never throws from the cache
16699
+ * write — exit correctness must not depend on persistence.
16700
+ */
16701
+ async resolveVirtualTxBase64(next) {
16702
+ const repo = this.virtualTxRepository;
16703
+ if (repo) {
16704
+ try {
16705
+ const stored = await repo.getVirtualTx(next.txid);
16706
+ if (stored?.psbt) return stored.psbt;
16707
+ } catch {
16708
+ }
16709
+ }
16710
+ const fetched = await this.indexer.getVirtualTxs([next.txid]);
16711
+ const psbt = fetched.txs[0];
16712
+ if (psbt && repo) {
16713
+ const cached = {
16714
+ txid: next.txid,
16715
+ psbt,
16716
+ expiresAt: null,
16717
+ type: chainTxTypeToChainedExit(next.type)
16718
+ };
16719
+ try {
16720
+ await repo.upsertVirtualTxs([cached]);
16721
+ } catch {
16722
+ }
16723
+ }
16724
+ return psbt;
15843
16725
  }
15844
16726
  /**
15845
16727
  * Get the next step to be executed
@@ -15874,26 +16756,11 @@ var Unroll;
15874
16756
  do: () => Promise.resolve()
15875
16757
  };
15876
16758
  }
15877
- const virtualTxs = await this.indexer.getVirtualTxs([nextTxToBroadcast.txid]);
15878
- if (virtualTxs.txs.length === 0) {
16759
+ const virtualTxBase64 = await this.resolveVirtualTxBase64(nextTxToBroadcast);
16760
+ if (!virtualTxBase64) {
15879
16761
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
15880
16762
  }
15881
- const tx = Transaction.fromPSBT(base64.decode(virtualTxs.txs[0]));
15882
- if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
15883
- const input = tx.getInput(0);
15884
- if (!input) {
15885
- throw new Error("Input not found");
15886
- }
15887
- const tapKeySig = input.tapKeySig;
15888
- if (!tapKeySig) {
15889
- throw new Error("Tap key sig not found");
15890
- }
15891
- tx.updateInput(0, {
15892
- finalScriptWitness: [tapKeySig]
15893
- });
15894
- } else {
15895
- tx.finalize();
15896
- }
16763
+ const tx = finalizeVirtualTx(nextTxToBroadcast.type, virtualTxBase64);
15897
16764
  const pkg = await this.bumper.bumpP2A(tx);
15898
16765
  return {
15899
16766
  type: 0 /* UNROLL */,
@@ -15947,20 +16814,21 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
15947
16814
  if (!txStatus.confirmed) {
15948
16815
  throw new Error(`tx ${vtxo.txid} is not confirmed`);
15949
16816
  }
15950
- const exit = availableExitPath(
15951
- { height: txStatus.blockHeight, time: txStatus.blockTime },
15952
- chainTip,
15953
- vtxo
15954
- );
15955
- if (!exit) {
16817
+ const resolved = await resolveUnilateralPath({
16818
+ vtxo,
16819
+ scriptHex: hex.encode(VtxoScript.decode(vtxo.tapTree).pkScript),
16820
+ contractRepository: wallet.contractRepository,
16821
+ walletPubKeyHex: hex.encode(await wallet.identity.xOnlyPublicKey()),
16822
+ currentTime: Date.now()
16823
+ });
16824
+ const spendingLeaf = resolved.selection.leaf;
16825
+ const sequence = resolved.selection.sequence;
16826
+ const timelock = sequenceToTimelock(sequence);
16827
+ const elapsed = timelock.type === "blocks" ? chainTip.height >= txStatus.blockHeight + Number(timelock.value) : chainTip.time >= txStatus.blockTime + Number(timelock.value);
16828
+ if (!elapsed) {
15956
16829
  throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
15957
16830
  }
15958
- const spendingLeaf = VtxoScript.decode(vtxo.tapTree).findLeaf(hex.encode(exit.script));
15959
- if (!spendingLeaf) {
15960
- throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
15961
- }
15962
16831
  totalAmount += BigInt(vtxo.value);
15963
- const sequence = timelockToSequence(exit.params.timelock);
15964
16832
  inputs.push({
15965
16833
  txid: vtxo.txid,
15966
16834
  index: vtxo.vout,
@@ -16024,21 +16892,809 @@ function doWait(onchainProvider, txid) {
16024
16892
  });
16025
16893
  };
16026
16894
  }
16027
- function availableExitPath(confirmedAt, current, vtxo) {
16028
- const exits = VtxoScript.decode(vtxo.tapTree).exitPaths();
16029
- for (const exit of exits) {
16030
- if (exit.params.timelock.type === "blocks") {
16031
- if (current.height >= confirmedAt.height + Number(exit.params.timelock.value)) {
16032
- return exit;
16895
+
16896
+ // src/wallet/exit/chain.ts
16897
+ var isCommitment = (t) => t === "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */ || t === "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
16898
+ async function buildExitDag(params) {
16899
+ const byTxid = /* @__PURE__ */ new Map();
16900
+ for (const vtxo of params.vtxos) {
16901
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
16902
+ let pageIndex = 0;
16903
+ let hasMore = true;
16904
+ while (hasMore) {
16905
+ const { chain, page } = await params.indexer.getVtxoChain(
16906
+ { txid: vtxo.txid, vout: vtxo.vout },
16907
+ { pageIndex, pageSize: DEFAULT_PAGE_SIZE }
16908
+ );
16909
+ for (const chainTx of chain) {
16910
+ const existing = byTxid.get(chainTx.txid);
16911
+ if (existing) {
16912
+ existing.forVtxos.add(outpoint);
16913
+ } else {
16914
+ byTxid.set(chainTx.txid, { ...chainTx, forVtxos: /* @__PURE__ */ new Set([outpoint]) });
16915
+ }
16033
16916
  }
16034
- } else {
16035
- if (current.time >= confirmedAt.time + Number(exit.params.timelock.value)) {
16036
- return exit;
16917
+ hasMore = page ? chain.length === DEFAULT_PAGE_SIZE : false;
16918
+ pageIndex++;
16919
+ }
16920
+ }
16921
+ const pending = new Map(
16922
+ [...byTxid.values()].filter((tx) => !isCommitment(tx.type)).map((tx) => [tx.txid, tx])
16923
+ );
16924
+ const inDegree = /* @__PURE__ */ new Map();
16925
+ for (const tx of pending.values()) {
16926
+ const parents = tx.spends.filter((p) => pending.has(p));
16927
+ inDegree.set(tx.txid, parents.length);
16928
+ }
16929
+ const order = [];
16930
+ const queue = [...pending.values()].filter((tx) => inDegree.get(tx.txid) === 0).sort((a, b) => a.txid.localeCompare(b.txid));
16931
+ while (queue.length > 0) {
16932
+ const tx = queue.shift();
16933
+ order.push(tx);
16934
+ const children = [...pending.values()].filter((c) => c.spends.includes(tx.txid)).sort((a, b) => a.txid.localeCompare(b.txid));
16935
+ for (const child of children) {
16936
+ const deg = inDegree.get(child.txid) - 1;
16937
+ inDegree.set(child.txid, deg);
16938
+ if (deg === 0) queue.push(child);
16939
+ }
16940
+ }
16941
+ if (order.length !== pending.size) {
16942
+ throw new Error("inconsistent vtxo chain from indexer (cycle detected)");
16943
+ }
16944
+ const nodes = [];
16945
+ for (const tx of order) {
16946
+ let confirmed = false;
16947
+ try {
16948
+ const status = await params.onchain.getTxStatus(tx.txid);
16949
+ confirmed = status.confirmed;
16950
+ } catch {
16951
+ confirmed = false;
16952
+ }
16953
+ const expires = Number(tx.expiresAt);
16954
+ nodes.push({
16955
+ txid: tx.txid,
16956
+ type: tx.type,
16957
+ expiresAt: Number.isFinite(expires) && expires > 0 ? expires : void 0,
16958
+ forVtxos: [...tx.forVtxos].sort(),
16959
+ confirmed
16960
+ });
16961
+ }
16962
+ return nodes;
16963
+ }
16964
+ function topoSortByDeps(items, idOf, depsOf) {
16965
+ const produced = new Set(items.map(idOf));
16966
+ const emitted = /* @__PURE__ */ new Set();
16967
+ const ordered = [];
16968
+ let remaining = items;
16969
+ while (remaining.length > 0) {
16970
+ const next = [];
16971
+ let progress = false;
16972
+ for (const item of remaining) {
16973
+ if (depsOf(item).every((d) => !produced.has(d) || emitted.has(d))) {
16974
+ ordered.push(item);
16975
+ emitted.add(idOf(item));
16976
+ progress = true;
16977
+ } else {
16978
+ next.push(item);
16037
16979
  }
16038
16980
  }
16981
+ if (!progress) {
16982
+ throw new Error("topological sort: cycle or unsatisfiable dependency");
16983
+ }
16984
+ remaining = next;
16039
16985
  }
16040
- return void 0;
16986
+ return ordered;
16987
+ }
16988
+ function extraWitnessSize(path) {
16989
+ return (path.extraWitness ?? []).reduce((sum, item) => sum + 1 + item.length, 0);
16990
+ }
16991
+ function sweepFeeFor(path, outputAddress, network, feeRate) {
16992
+ const [controlBlock, scriptWithVersion] = path.leaf;
16993
+ const estimator = TxWeightEstimator.create().addTapscriptInput(
16994
+ 64 + extraWitnessSize(path),
16995
+ scriptWithVersion.length,
16996
+ TaprootControlBlock.encode(controlBlock).length
16997
+ ).addOutputAddress(outputAddress, network);
16998
+ return Number(estimator.vsize().fee(BigInt(Math.ceil(feeRate))));
16999
+ }
17000
+ async function buildSignedSweep(params) {
17001
+ const { vtxo, path, outputAddress, feeRate, network, identity } = params;
17002
+ const fee = sweepFeeFor(path, outputAddress, network, feeRate);
17003
+ const sendAmount = BigInt(vtxo.value) - BigInt(fee);
17004
+ if (sendAmount < BigInt(DUST_AMOUNT)) {
17005
+ throw new Error(
17006
+ `uneconomic vtxo ${vtxo.txid}:${vtxo.vout}: value ${vtxo.value} - fee ${fee} < dust`
17007
+ );
17008
+ }
17009
+ const tx = new Transaction({ version: 2 });
17010
+ tx.addInput({
17011
+ txid: vtxo.txid,
17012
+ index: vtxo.vout,
17013
+ tapLeafScript: [path.leaf],
17014
+ sequence: path.sequence,
17015
+ witnessUtxo: { amount: BigInt(vtxo.value), script: vtxo.pkScript },
17016
+ sighashType: SigHash.DEFAULT
17017
+ });
17018
+ tx.addOutputAddress(outputAddress, sendAmount, network);
17019
+ const signed = await identity.sign(tx);
17020
+ if (!path.extraWitness || path.extraWitness.length === 0) {
17021
+ signed.finalize();
17022
+ return { tx: signed, fee };
17023
+ }
17024
+ const input = signed.getInput(0);
17025
+ const tapScriptSig = input.tapScriptSig;
17026
+ if (!tapScriptSig || tapScriptSig.length === 0) {
17027
+ throw new Error("exit path requires additional signers");
17028
+ }
17029
+ const [controlBlock] = path.leaf;
17030
+ signed.updateInput(0, {
17031
+ finalScriptWitness: [
17032
+ tapScriptSig[0][1],
17033
+ ...path.extraWitness,
17034
+ scriptFromTapLeafScript(path.leaf),
17035
+ TaprootControlBlock.encode(controlBlock)
17036
+ ]
17037
+ });
17038
+ return { tx: signed, fee };
17039
+ }
17040
+
17041
+ // src/wallet/exit/estimate.ts
17042
+ var CHILD_OUTPUT_DUST = 546;
17043
+ function stepFundingAmount(stepFee) {
17044
+ return stepFee + CHILD_OUTPUT_DUST;
17045
+ }
17046
+ function resolveMode(opts) {
17047
+ return opts.mode ?? "funded";
17048
+ }
17049
+ function resolveNetworkName(opts) {
17050
+ if (opts.networkName) return opts.networkName;
17051
+ const { bech32 } = opts.wallet.network;
17052
+ if (bech32 === "bc") return "bitcoin";
17053
+ if (bech32 === "bcrt") return "regtest";
17054
+ return "testnet";
17055
+ }
17056
+ async function resolveFeeRate(opts) {
17057
+ if (opts.feeRate) return Math.ceil(opts.feeRate);
17058
+ const feeRate = await opts.wallet.onchainProvider.getFeeRate();
17059
+ if (!feeRate || feeRate < OnchainWallet.MIN_FEE_RATE) return OnchainWallet.MIN_FEE_RATE;
17060
+ return Math.ceil(feeRate);
17061
+ }
17062
+ async function selectExitVtxos(opts) {
17063
+ if (!opts.vtxos) return opts.wallet.getVtxos();
17064
+ const res = await opts.wallet.indexerProvider.getVtxos({ outpoints: opts.vtxos });
17065
+ const tapTrees = /* @__PURE__ */ new Map();
17066
+ const out = [];
17067
+ for (const vtxo of res.vtxos) {
17068
+ if (vtxo.isSpent) continue;
17069
+ let tapTree = tapTrees.get(vtxo.script);
17070
+ if (!tapTree) {
17071
+ const [contract] = await opts.wallet.contractRepository.getContracts({
17072
+ script: vtxo.script
17073
+ });
17074
+ if (!contract) {
17075
+ throw new Error(
17076
+ `no contract registered for vtxo script ${vtxo.script} \u2014 register the contract before exiting`
17077
+ );
17078
+ }
17079
+ const handler = contractHandlers.get(contract.type);
17080
+ if (!handler) {
17081
+ throw new Error(`no contract handler registered for type '${contract.type}'`);
17082
+ }
17083
+ tapTree = handler.createScript(contract.params).encode();
17084
+ tapTrees.set(vtxo.script, tapTree);
17085
+ }
17086
+ out.push({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value, tapTree });
17087
+ }
17088
+ return out;
17089
+ }
17090
+ async function computeExitLayout(opts, feeRate) {
17091
+ const { wallet, onchainWallet } = opts;
17092
+ const vtxos = await selectExitVtxos(opts);
17093
+ if (vtxos.length === 0) throw new Error("no vtxos to exit");
17094
+ const dag = await buildExitDag({
17095
+ vtxos,
17096
+ indexer: wallet.indexerProvider,
17097
+ onchain: wallet.onchainProvider
17098
+ });
17099
+ const pendingNodes = dag.filter((n) => !n.confirmed);
17100
+ const psbts = /* @__PURE__ */ new Map();
17101
+ if (pendingNodes.length > 0) {
17102
+ const txids = pendingNodes.map((n) => n.txid);
17103
+ let pageIndex = 0;
17104
+ let hasMore = true;
17105
+ while (hasMore) {
17106
+ const { txs, page } = await wallet.indexerProvider.getVirtualTxs(txids, {
17107
+ pageIndex,
17108
+ pageSize: DEFAULT_PAGE_SIZE
17109
+ });
17110
+ for (const psbt of txs) {
17111
+ psbts.set(Transaction.fromPSBT(base64.decode(psbt)).id, psbt);
17112
+ }
17113
+ hasMore = page ? txs.length === DEFAULT_PAGE_SIZE : false;
17114
+ pageIndex++;
17115
+ }
17116
+ }
17117
+ const childVsize = Number(
17118
+ TxWeightEstimator.create().addP2AInput().addKeySpendInput(true).addOutputAddress(onchainWallet.address, wallet.network).vsize().value
17119
+ );
17120
+ const rawSteps = pendingNodes.map((node) => {
17121
+ const psbt = psbts.get(node.txid);
17122
+ if (!psbt) {
17123
+ throw new Error(`indexer did not return virtual tx ${node.txid}`);
17124
+ }
17125
+ const parent = finalizeVirtualTx(node.type, psbt);
17126
+ const stepFee = Math.ceil(feeRate * (parent.vsize + childVsize));
17127
+ return { node, parent, stepFee, funding: stepFundingAmount(stepFee) };
17128
+ });
17129
+ const parentInputTxids = (tx) => {
17130
+ const ids = [];
17131
+ for (let i = 0; i < tx.inputsLength; i++) {
17132
+ const txid = tx.getInput(i).txid;
17133
+ if (txid) ids.push(hex.encode(txid));
17134
+ }
17135
+ return ids;
17136
+ };
17137
+ const steps = topoSortByDeps(
17138
+ rawSteps,
17139
+ (s) => s.parent.id,
17140
+ (s) => parentInputTxids(s.parent)
17141
+ );
17142
+ const walletPubKeyHex = hex.encode(await wallet.identity.xOnlyPublicKey());
17143
+ const infos = [];
17144
+ const sweeps = [];
17145
+ for (const vtxo of vtxos) {
17146
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
17147
+ try {
17148
+ const resolved = await resolveUnilateralPath({
17149
+ vtxo,
17150
+ scriptHex: hex.encode(VtxoScript.decode(vtxo.tapTree).pkScript),
17151
+ contractRepository: wallet.contractRepository,
17152
+ walletPubKeyHex,
17153
+ currentTime: Date.now()
17154
+ });
17155
+ const sweepFee = sweepFeeFor(
17156
+ resolved.selection,
17157
+ opts.sweepAddress,
17158
+ wallet.network,
17159
+ feeRate
17160
+ );
17161
+ if (vtxo.value - sweepFee < CHILD_OUTPUT_DUST) {
17162
+ infos.push({
17163
+ outpoint,
17164
+ value: vtxo.value,
17165
+ skipped: `uneconomic: value ${vtxo.value} <= sweep fee + dust`
17166
+ });
17167
+ continue;
17168
+ }
17169
+ const t = sequenceToTimelock(resolved.selection.sequence);
17170
+ const delay = { type: t.type, value: Number(t.value) };
17171
+ infos.push({ outpoint, value: vtxo.value, sweepFee, path: resolved.label, delay });
17172
+ sweeps.push({ vtxo, resolved, sweepFee, delay });
17173
+ } catch (e) {
17174
+ if (e instanceof ExitPathError) {
17175
+ infos.push({ outpoint, value: vtxo.value, skipped: e.message });
17176
+ continue;
17177
+ }
17178
+ throw e;
17179
+ }
17180
+ }
17181
+ const coins = (await onchainWallet.getCoins()).filter((c) => c.status.confirmed);
17182
+ const balance = coins.reduce((sum, c) => sum + c.value, 0);
17183
+ const fundingTotal = steps.reduce((sum, s) => sum + s.funding, 0);
17184
+ const splitterFeeFor = (inputCount) => {
17185
+ if (steps.length === 0) return 0;
17186
+ const est = TxWeightEstimator.create();
17187
+ for (let i = 0; i < inputCount; i++) est.addKeySpendInput(true);
17188
+ for (let i = 0; i < steps.length + 1; i++) {
17189
+ est.addOutputAddress(onchainWallet.address, wallet.network);
17190
+ }
17191
+ return Number(est.vsize().fee(BigInt(feeRate)));
17192
+ };
17193
+ const graph = resolveMode(opts) === "graph";
17194
+ let splitterFee = 0;
17195
+ if (!graph) {
17196
+ splitterFee = splitterFeeFor(Math.max(1, coins.length));
17197
+ if (steps.length > 0 && balance < fundingTotal + splitterFee) {
17198
+ splitterFee = splitterFeeFor(coins.length + 1);
17199
+ }
17200
+ }
17201
+ const stepFees = steps.reduce((s, x) => s + x.stepFee, 0);
17202
+ const sweepFees = sweeps.reduce((s, x) => s + x.sweepFee, 0);
17203
+ const totals = {
17204
+ // graph: each unroll step is parent + a live-built child (2 txs), no
17205
+ // splitter. funded: + 1 splitter tx and pre-signed children.
17206
+ txCount: (graph ? 0 : steps.length > 0 ? 1 : 0) + steps.length * 2 + sweeps.length,
17207
+ totalFeeSats: splitterFee + stepFees + sweepFees,
17208
+ // graph funding is what the executor sends to its own fee address:
17209
+ // just the CPFP fees (change recycles); funded also locks the
17210
+ // per-child dust into the splitter.
17211
+ fundingRequiredSats: graph ? stepFees : splitterFee + fundingTotal,
17212
+ recoveredSats: sweeps.reduce((s, x) => s + (x.vtxo.value - x.sweepFee), 0)
17213
+ };
17214
+ const expiries = pendingNodes.map((n) => n.expiresAt).filter((x) => x !== void 0);
17215
+ const validUntil = expiries.length > 0 ? Math.min(...expiries) : void 0;
17216
+ return {
17217
+ vtxos,
17218
+ dag,
17219
+ steps,
17220
+ sweeps,
17221
+ infos,
17222
+ totals,
17223
+ splitterFee,
17224
+ validUntil,
17225
+ balance,
17226
+ coins
17227
+ };
17228
+ }
17229
+ async function estimate(opts) {
17230
+ const feeRate = await resolveFeeRate(opts);
17231
+ const layout = await computeExitLayout(opts, feeRate);
17232
+ if (resolveMode(opts) === "graph") {
17233
+ return {
17234
+ feeRate,
17235
+ fundingAddress: "",
17236
+ currentBalanceSats: 0,
17237
+ shortfallSats: layout.totals.fundingRequiredSats,
17238
+ validUntil: layout.validUntil,
17239
+ totals: layout.totals,
17240
+ vtxos: layout.infos
17241
+ };
17242
+ }
17243
+ return {
17244
+ feeRate,
17245
+ fundingAddress: opts.onchainWallet.address,
17246
+ currentBalanceSats: layout.balance,
17247
+ shortfallSats: Math.max(0, layout.totals.fundingRequiredSats - layout.balance),
17248
+ validUntil: layout.validUntil,
17249
+ totals: layout.totals,
17250
+ vtxos: layout.infos
17251
+ };
17252
+ }
17253
+
17254
+ // src/wallet/exit/executor.ts
17255
+ var Executor = class {
17256
+ constructor(pkg, provider, opts) {
17257
+ this.pkg = pkg;
17258
+ this.provider = provider;
17259
+ this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
17260
+ this.feeWallet = opts?.feeWallet;
17261
+ }
17262
+ pollIntervalMs;
17263
+ feeWallet;
17264
+ sleep() {
17265
+ return new Promise((r) => setTimeout(r, this.pollIntervalMs));
17266
+ }
17267
+ async status(txid) {
17268
+ try {
17269
+ return await this.provider.getTxStatus(txid);
17270
+ } catch {
17271
+ return void 0;
17272
+ }
17273
+ }
17274
+ async waitConfirmed(txid) {
17275
+ for (; ; ) {
17276
+ const s = await this.status(txid);
17277
+ if (s?.confirmed) return s;
17278
+ await this.sleep();
17279
+ }
17280
+ }
17281
+ async *[Symbol.asyncIterator]() {
17282
+ const dead = /* @__PURE__ */ new Set();
17283
+ if (this.pkg.validUntil && Date.now() / 1e3 > this.pkg.validUntil) {
17284
+ yield {
17285
+ stepIndex: -1,
17286
+ kind: "broadcast",
17287
+ status: "warning",
17288
+ reason: `package validUntil ${this.pkg.validUntil} has passed; the operator may have swept \u2014 attempting anyway`
17289
+ };
17290
+ }
17291
+ const sweeps = [];
17292
+ for (let i = 0; i < this.pkg.steps.length; i++) {
17293
+ const step = this.pkg.steps[i];
17294
+ if (step.kind === "sweep") {
17295
+ sweeps.push({ index: i, step });
17296
+ continue;
17297
+ }
17298
+ const forVtxos = step.kind === "package" || step.kind === "bump" ? step.forVtxos : void 0;
17299
+ if (forVtxos && forVtxos.every((v) => dead.has(v))) {
17300
+ yield {
17301
+ stepIndex: i,
17302
+ kind: step.kind,
17303
+ status: "skipped",
17304
+ forVtxos,
17305
+ reason: "branch failed earlier"
17306
+ };
17307
+ continue;
17308
+ }
17309
+ const anchorTxid = step.kind === "package" || step.kind === "bump" ? step.parentTxid : step.txid;
17310
+ const existing = await this.status(anchorTxid);
17311
+ if (existing?.confirmed) {
17312
+ yield {
17313
+ stepIndex: i,
17314
+ kind: step.kind,
17315
+ status: "skipped",
17316
+ txid: anchorTxid,
17317
+ forVtxos
17318
+ };
17319
+ continue;
17320
+ }
17321
+ if (!existing) {
17322
+ try {
17323
+ if (step.kind === "package") {
17324
+ await this.provider.broadcastTransaction(step.parentHex, step.childHex);
17325
+ } else if (step.kind === "bump") {
17326
+ if (!this.feeWallet) {
17327
+ throw new Error(
17328
+ "graph package requires a fee wallet (opts.feeWallet) to fund CPFP bumps"
17329
+ );
17330
+ }
17331
+ const [parentHex, childHex] = await this.feeWallet.bumpAnchor(
17332
+ step.parentHex,
17333
+ this.pkg.feeRate
17334
+ );
17335
+ await this.provider.broadcastTransaction(parentHex, childHex);
17336
+ } else {
17337
+ await this.provider.broadcastTransaction(step.hex);
17338
+ }
17339
+ yield {
17340
+ stepIndex: i,
17341
+ kind: step.kind,
17342
+ status: "broadcast",
17343
+ txid: anchorTxid,
17344
+ forVtxos
17345
+ };
17346
+ } catch (e) {
17347
+ const reason = e instanceof Error ? e.message : String(e);
17348
+ if (step.kind === "broadcast") {
17349
+ for (const s of this.pkg.steps) {
17350
+ if (s.kind === "package") s.forVtxos.forEach((v) => dead.add(v));
17351
+ }
17352
+ } else if (step.kind === "package" || step.kind === "bump") {
17353
+ step.forVtxos.forEach((v) => dead.add(v));
17354
+ }
17355
+ yield {
17356
+ stepIndex: i,
17357
+ kind: step.kind,
17358
+ status: "failed",
17359
+ txid: anchorTxid,
17360
+ forVtxos,
17361
+ reason
17362
+ };
17363
+ continue;
17364
+ }
17365
+ }
17366
+ await this.waitConfirmed(anchorTxid);
17367
+ yield {
17368
+ stepIndex: i,
17369
+ kind: step.kind,
17370
+ status: "confirmed",
17371
+ txid: anchorTxid,
17372
+ forVtxos
17373
+ };
17374
+ }
17375
+ for (const { index, step } of sweeps) {
17376
+ if (dead.has(step.vtxo)) {
17377
+ yield {
17378
+ stepIndex: index,
17379
+ kind: "sweep",
17380
+ status: "failed",
17381
+ txid: step.txid,
17382
+ forVtxos: [step.vtxo],
17383
+ reason: "branch failed earlier"
17384
+ };
17385
+ }
17386
+ }
17387
+ const pending = sweeps.filter(({ step }) => !dead.has(step.vtxo));
17388
+ const waitingAnnounced = /* @__PURE__ */ new Set();
17389
+ const done = /* @__PURE__ */ new Set();
17390
+ while (done.size < pending.length) {
17391
+ for (const { index, step } of pending) {
17392
+ if (done.has(index)) continue;
17393
+ const swept = await this.status(step.txid);
17394
+ if (swept?.confirmed) {
17395
+ done.add(index);
17396
+ yield {
17397
+ stepIndex: index,
17398
+ kind: "sweep",
17399
+ status: "confirmed",
17400
+ txid: step.txid,
17401
+ forVtxos: [step.vtxo]
17402
+ };
17403
+ continue;
17404
+ }
17405
+ if (swept) continue;
17406
+ const dep = await this.status(step.dependsOnTxid);
17407
+ if (!dep?.confirmed) continue;
17408
+ const tip = await this.provider.getChainTip();
17409
+ const mature = step.delay.type === "blocks" ? tip.height >= (dep.blockHeight ?? 0) + step.delay.value : tip.time >= (dep.blockTime ?? 0) + step.delay.value;
17410
+ if (!mature) {
17411
+ if (!waitingAnnounced.has(index)) {
17412
+ waitingAnnounced.add(index);
17413
+ yield {
17414
+ stepIndex: index,
17415
+ kind: "sweep",
17416
+ status: "waiting_csv",
17417
+ txid: step.txid,
17418
+ forVtxos: [step.vtxo],
17419
+ ...step.delay.type === "blocks" ? { maturesAtHeight: (dep.blockHeight ?? 0) + step.delay.value } : { maturesAtTime: (dep.blockTime ?? 0) + step.delay.value }
17420
+ };
17421
+ }
17422
+ continue;
17423
+ }
17424
+ try {
17425
+ await this.provider.broadcastTransaction(step.hex);
17426
+ yield {
17427
+ stepIndex: index,
17428
+ kind: "sweep",
17429
+ status: "broadcast",
17430
+ txid: step.txid,
17431
+ forVtxos: [step.vtxo]
17432
+ };
17433
+ } catch (e) {
17434
+ const reason = e instanceof Error ? e.message : String(e);
17435
+ if (/non-?bip68|non-?final|premature/i.test(reason)) {
17436
+ continue;
17437
+ }
17438
+ done.add(index);
17439
+ yield {
17440
+ stepIndex: index,
17441
+ kind: "sweep",
17442
+ status: "failed",
17443
+ txid: step.txid,
17444
+ forVtxos: [step.vtxo],
17445
+ reason
17446
+ };
17447
+ }
17448
+ }
17449
+ if (done.size < pending.length) await this.sleep();
17450
+ }
17451
+ }
17452
+ };
17453
+ async function prepare(opts) {
17454
+ const { wallet, onchainWallet } = opts;
17455
+ const feeRate = await resolveFeeRate(opts);
17456
+ const mode = resolveMode(opts);
17457
+ if (mode === "funded") {
17458
+ const walletKey = await wallet.identity.xOnlyPublicKey();
17459
+ if (hex.encode(onchainWallet.onchainP2TR.tapInternalKey) !== hex.encode(walletKey)) {
17460
+ throw new Error("onchainWallet must share the wallet identity");
17461
+ }
17462
+ }
17463
+ const layout = await computeExitLayout(opts, feeRate);
17464
+ const sweepSteps = [];
17465
+ const activeOutpoints = /* @__PURE__ */ new Set();
17466
+ for (const sweep of layout.sweeps) {
17467
+ const outpoint = `${sweep.vtxo.txid}:${sweep.vtxo.vout}`;
17468
+ try {
17469
+ const { tx } = await buildSignedSweep({
17470
+ vtxo: {
17471
+ txid: sweep.vtxo.txid,
17472
+ vout: sweep.vtxo.vout,
17473
+ value: sweep.vtxo.value,
17474
+ pkScript: VtxoScript.decode(sweep.vtxo.tapTree).pkScript
17475
+ },
17476
+ path: sweep.resolved.selection,
17477
+ outputAddress: opts.sweepAddress,
17478
+ feeRate,
17479
+ network: wallet.network,
17480
+ identity: wallet.identity
17481
+ });
17482
+ activeOutpoints.add(outpoint);
17483
+ sweepSteps.push({
17484
+ kind: "sweep",
17485
+ vtxo: outpoint,
17486
+ txid: tx.id,
17487
+ hex: tx.hex,
17488
+ dependsOnTxid: sweep.vtxo.txid,
17489
+ delay: sweep.delay
17490
+ });
17491
+ } catch (e) {
17492
+ const info = layout.infos.find((i) => i.outpoint === outpoint);
17493
+ if (info) {
17494
+ info.skipped = e instanceof Error ? e.message : String(e);
17495
+ delete info.sweepFee;
17496
+ delete info.path;
17497
+ delete info.delay;
17498
+ }
17499
+ }
17500
+ }
17501
+ const steps = layout.steps.filter((s) => s.node.forVtxos.some((v) => activeOutpoints.has(v)));
17502
+ if (steps.length === 0 && sweepSteps.length === 0) {
17503
+ throw new Error("no exitable vtxos (all skipped)");
17504
+ }
17505
+ if (mode === "graph") {
17506
+ const bumpSteps = steps.map((step) => ({
17507
+ kind: "bump",
17508
+ parentTxid: step.parent.id,
17509
+ parentHex: step.parent.hex,
17510
+ forVtxos: step.node.forVtxos.filter((v) => activeOutpoints.has(v))
17511
+ }));
17512
+ const stepFees2 = steps.reduce((s, x) => s + x.stepFee, 0);
17513
+ const activeInfos2 = layout.infos.filter((i) => !i.skipped);
17514
+ const sweepFees2 = activeInfos2.reduce((s, i) => s + (i.sweepFee ?? 0), 0);
17515
+ const recovered2 = activeInfos2.reduce((s, i) => s + (i.value ?? 0) - (i.sweepFee ?? 0), 0);
17516
+ return {
17517
+ version: 1,
17518
+ mode: "graph",
17519
+ network: resolveNetworkName(opts),
17520
+ createdAt: Math.floor(Date.now() / 1e3),
17521
+ validUntil: layout.validUntil,
17522
+ feeRate,
17523
+ sweepAddress: opts.sweepAddress,
17524
+ totals: {
17525
+ txCount: bumpSteps.length * 2 + sweepSteps.length,
17526
+ totalFeeSats: stepFees2 + sweepFees2,
17527
+ fundingRequiredSats: stepFees2,
17528
+ recoveredSats: recovered2
17529
+ },
17530
+ vtxos: layout.infos,
17531
+ steps: [...bumpSteps, ...sweepSteps]
17532
+ };
17533
+ }
17534
+ const packageSteps = [];
17535
+ let broadcastStep;
17536
+ let splitterFee = 0;
17537
+ if (steps.length > 0) {
17538
+ const fundingTotal = steps.reduce((sum, s) => sum + s.funding, 0);
17539
+ let selected = { inputs: [], changeAmount: 0n };
17540
+ for (let i = 0; i < 10; i++) {
17541
+ const target = fundingTotal + splitterFee;
17542
+ try {
17543
+ selected = selectCoins(layout.coins, target);
17544
+ } catch {
17545
+ throw new Error(
17546
+ `insufficient confirmed onchain funds: need ${target} sats, have ${layout.balance} (deposit the shortfall to ${onchainWallet.address})`
17547
+ );
17548
+ }
17549
+ const est = TxWeightEstimator.create();
17550
+ for (const _ of selected.inputs) est.addKeySpendInput(true);
17551
+ for (const _ of steps) est.addOutputAddress(onchainWallet.address, wallet.network);
17552
+ if (selected.changeAmount >= BigInt(DUST_AMOUNT)) {
17553
+ est.addOutputAddress(onchainWallet.address, wallet.network);
17554
+ }
17555
+ const newFee = Number(est.vsize().fee(BigInt(feeRate)));
17556
+ if (newFee <= splitterFee) break;
17557
+ splitterFee = newFee;
17558
+ }
17559
+ const splitter = new Transaction({ version: 2 });
17560
+ for (const coin of selected.inputs) {
17561
+ splitter.addInput({
17562
+ txid: coin.txid,
17563
+ index: coin.vout,
17564
+ witnessUtxo: {
17565
+ script: onchainWallet.onchainP2TR.script,
17566
+ amount: BigInt(coin.value)
17567
+ },
17568
+ tapInternalKey: onchainWallet.onchainP2TR.tapInternalKey
17569
+ });
17570
+ }
17571
+ for (const step of steps) {
17572
+ splitter.addOutputAddress(onchainWallet.address, BigInt(step.funding), wallet.network);
17573
+ }
17574
+ const inputSum = selected.inputs.reduce((s, c) => s + c.value, 0);
17575
+ const change = BigInt(inputSum - fundingTotal - splitterFee);
17576
+ if (change < 0n) {
17577
+ throw new Error(
17578
+ `insufficient confirmed onchain funds for the splitter fee: need ${fundingTotal + splitterFee} sats, have ${inputSum} (deposit the shortfall to ${onchainWallet.address})`
17579
+ );
17580
+ }
17581
+ if (change >= BigInt(DUST_AMOUNT)) {
17582
+ splitter.addOutputAddress(onchainWallet.address, change, wallet.network);
17583
+ } else if (change > 0n) {
17584
+ splitterFee += Number(change);
17585
+ }
17586
+ const signedSplitter = await wallet.identity.sign(splitter);
17587
+ signedSplitter.finalize();
17588
+ for (let k = 0; k < steps.length; k++) {
17589
+ const step = steps[k];
17590
+ const { child } = buildAnchorChild({
17591
+ parent: step.parent,
17592
+ feeRate,
17593
+ fundingCoins: [{ txid: signedSplitter.id, vout: k, value: step.funding }],
17594
+ changeAddress: onchainWallet.address,
17595
+ changeScript: onchainWallet.onchainP2TR.script,
17596
+ tapInternalKey: onchainWallet.onchainP2TR.tapInternalKey,
17597
+ network: wallet.network
17598
+ });
17599
+ const signedChild = await wallet.identity.sign(child);
17600
+ for (let i = 1; i < signedChild.inputsLength; i++) {
17601
+ signedChild.finalizeIdx(i);
17602
+ }
17603
+ packageSteps.push({
17604
+ kind: "package",
17605
+ parentTxid: step.parent.id,
17606
+ parentHex: step.parent.hex,
17607
+ childTxid: signedChild.id,
17608
+ childHex: signedChild.hex,
17609
+ forVtxos: step.node.forVtxos.filter((v) => activeOutpoints.has(v))
17610
+ });
17611
+ }
17612
+ broadcastStep = { kind: "broadcast", txid: signedSplitter.id, hex: signedSplitter.hex };
17613
+ await onchainWallet.provider.broadcastTransaction(signedSplitter.hex);
17614
+ }
17615
+ const stepFees = steps.reduce((s, x) => s + x.stepFee, 0);
17616
+ const activeInfos = layout.infos.filter((i) => !i.skipped);
17617
+ const sweepFees = activeInfos.reduce((s, i) => s + (i.sweepFee ?? 0), 0);
17618
+ const recovered = activeInfos.reduce((s, i) => s + (i.value ?? 0) - (i.sweepFee ?? 0), 0);
17619
+ const fundingRequired = splitterFee + steps.reduce((s, x) => s + x.funding, 0);
17620
+ return {
17621
+ version: 1,
17622
+ mode: "funded",
17623
+ network: resolveNetworkName(opts),
17624
+ createdAt: Math.floor(Date.now() / 1e3),
17625
+ validUntil: layout.validUntil,
17626
+ feeRate,
17627
+ sweepAddress: opts.sweepAddress,
17628
+ totals: {
17629
+ txCount: (broadcastStep ? 1 : 0) + packageSteps.length * 2 + sweepSteps.length,
17630
+ totalFeeSats: splitterFee + stepFees + sweepFees,
17631
+ fundingRequiredSats: fundingRequired,
17632
+ recoveredSats: recovered
17633
+ },
17634
+ vtxos: layout.infos,
17635
+ steps: [...broadcastStep ? [broadcastStep] : [], ...packageSteps, ...sweepSteps]
17636
+ };
17637
+ }
17638
+
17639
+ // src/wallet/exit/types.ts
17640
+ function serializeExitPackage(pkg) {
17641
+ return JSON.stringify(pkg);
16041
17642
  }
17643
+ function deserializeExitPackage(json) {
17644
+ const data = JSON.parse(json);
17645
+ if (data?.version !== 1) {
17646
+ throw new Error(`unsupported exit package version: ${data?.version}`);
17647
+ }
17648
+ if (typeof data.network !== "string" || typeof data.sweepAddress !== "string") {
17649
+ throw new Error("invalid exit package: missing network or sweepAddress");
17650
+ }
17651
+ if (typeof data.feeRate !== "number" || typeof data.createdAt !== "number") {
17652
+ throw new Error("invalid exit package: missing feeRate or createdAt");
17653
+ }
17654
+ if (!Array.isArray(data.steps) || !Array.isArray(data.vtxos)) {
17655
+ throw new Error("invalid exit package: steps and vtxos must be arrays");
17656
+ }
17657
+ if (!isValidTotals(data.totals)) {
17658
+ throw new Error("invalid exit package: malformed totals");
17659
+ }
17660
+ for (const step of data.steps) {
17661
+ if (!isValidStep(step)) {
17662
+ throw new Error(`invalid step: ${JSON.stringify(step)}`);
17663
+ }
17664
+ }
17665
+ return data;
17666
+ }
17667
+ function isValidDelay(d) {
17668
+ const delay = d;
17669
+ return !!delay && (delay.type === "blocks" || delay.type === "seconds") && typeof delay.value === "number";
17670
+ }
17671
+ function isValidTotals(t) {
17672
+ const totals = t;
17673
+ return !!totals && typeof totals === "object" && typeof totals.txCount === "number" && typeof totals.totalFeeSats === "number" && typeof totals.fundingRequiredSats === "number" && typeof totals.recoveredSats === "number";
17674
+ }
17675
+ function isValidStep(step) {
17676
+ const s = step;
17677
+ if (!s || typeof s !== "object") return false;
17678
+ switch (s.kind) {
17679
+ case "broadcast":
17680
+ return typeof s.txid === "string" && typeof s.hex === "string";
17681
+ case "package":
17682
+ return typeof s.parentTxid === "string" && typeof s.parentHex === "string" && typeof s.childTxid === "string" && typeof s.childHex === "string" && Array.isArray(s.forVtxos);
17683
+ case "bump":
17684
+ return typeof s.parentTxid === "string" && typeof s.parentHex === "string" && Array.isArray(s.forVtxos);
17685
+ case "sweep":
17686
+ return typeof s.vtxo === "string" && typeof s.txid === "string" && typeof s.hex === "string" && typeof s.dependsOnTxid === "string" && isValidDelay(s.delay);
17687
+ default:
17688
+ return false;
17689
+ }
17690
+ }
17691
+
17692
+ // src/wallet/exit/index.ts
17693
+ var UnilateralExit = {
17694
+ estimate,
17695
+ prepare,
17696
+ Executor
17697
+ };
16042
17698
  var ARKCONTRACT_PREFIX = "arkcontract";
16043
17699
  function encodeArkContract(contract) {
16044
17700
  const params = new URLSearchParams();
@@ -16098,6 +17754,6 @@ function isArkContract(str) {
16098
17754
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
16099
17755
  }
16100
17756
 
16101
- export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, InMemoryContractRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, decodeArkContract, deserializeAssets, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isSpendable, isSubdust, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
16102
- //# sourceMappingURL=chunk-V3FCNVJE.js.map
16103
- //# sourceMappingURL=chunk-V3FCNVJE.js.map
17757
+ export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
17758
+ //# sourceMappingURL=chunk-FMI2GKYO.js.map
17759
+ //# sourceMappingURL=chunk-FMI2GKYO.js.map