@arkade-os/sdk 0.4.33 → 0.4.35

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 (84) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/adapters/indexedDB.cjs +5 -5
  7. package/dist/adapters/indexedDB.js +4 -4
  8. package/dist/{ark-DEsDMYGv.d.cts → ark-D6sau_6-.d.cts} +522 -9
  9. package/dist/{ark-DEsDMYGv.d.ts → ark-D6sau_6-.d.ts} +522 -9
  10. package/dist/{asyncStorageTaskQueue-D8T1VXEx.d.cts → asyncStorageTaskQueue-CpC027t_.d.cts} +2 -2
  11. package/dist/{asyncStorageTaskQueue-CMrTYlKG.d.ts → asyncStorageTaskQueue-GT8fmPUG.d.ts} +2 -2
  12. package/dist/{chunk-E22HEKLN.js → chunk-3JR77WQ4.js} +140 -42
  13. package/dist/chunk-3JR77WQ4.js.map +1 -0
  14. package/dist/{chunk-WMIPYZSB.cjs → chunk-CMPJR3HS.cjs} +42 -9
  15. package/dist/chunk-CMPJR3HS.cjs.map +1 -0
  16. package/dist/{chunk-AOJUURHM.js → chunk-CUSABEUQ.js} +141 -37
  17. package/dist/chunk-CUSABEUQ.js.map +1 -0
  18. package/dist/{chunk-HAVA4XB7.cjs → chunk-FM7T7JVL.cjs} +7 -7
  19. package/dist/{chunk-HAVA4XB7.cjs.map → chunk-FM7T7JVL.cjs.map} +1 -1
  20. package/dist/{chunk-GYSK5R57.cjs → chunk-GUTKJMSF.cjs} +164 -59
  21. package/dist/chunk-GUTKJMSF.cjs.map +1 -0
  22. package/dist/{chunk-7K3ROJF6.cjs → chunk-H2LX2KKY.cjs} +2161 -466
  23. package/dist/chunk-H2LX2KKY.cjs.map +1 -0
  24. package/dist/{chunk-DSS2GQUG.js → chunk-NOR7XOKN.js} +2021 -331
  25. package/dist/chunk-NOR7XOKN.js.map +1 -0
  26. package/dist/{chunk-BU3BU6XK.js → chunk-OURFR4UR.js} +3 -3
  27. package/dist/{chunk-BU3BU6XK.js.map → chunk-OURFR4UR.js.map} +1 -1
  28. package/dist/{chunk-TU3LVAPX.js → chunk-OUVTG72A.js} +43 -11
  29. package/dist/chunk-OUVTG72A.js.map +1 -0
  30. package/dist/{chunk-5CCRRL5S.cjs → chunk-VYS3KGRI.cjs} +19 -13
  31. package/dist/chunk-VYS3KGRI.cjs.map +1 -0
  32. package/dist/{chunk-SPDNHPM4.cjs → chunk-X2EQLK4O.cjs} +149 -46
  33. package/dist/chunk-X2EQLK4O.cjs.map +1 -0
  34. package/dist/{chunk-L6ZETTX3.js → chunk-XQS2HW4Q.js} +11 -5
  35. package/dist/chunk-XQS2HW4Q.js.map +1 -0
  36. package/dist/contracts/handlers/index.cjs +7 -7
  37. package/dist/contracts/handlers/index.d.cts +3 -3
  38. package/dist/contracts/handlers/index.d.ts +3 -3
  39. package/dist/contracts/handlers/index.js +2 -2
  40. package/dist/{delegate-BJeBNP5a.d.cts → delegate-C-L6gSZx.d.cts} +1 -1
  41. package/dist/{delegate-EXN2mfkb.d.ts → delegate-De5__fpZ.d.ts} +1 -1
  42. package/dist/{index-BG2ooYKO.d.ts → index-BETdjE_o.d.ts} +22 -16
  43. package/dist/{index-DHjEeHEp.d.cts → index-jwQfHP6D.d.cts} +22 -16
  44. package/dist/index.cjs +158 -130
  45. package/dist/index.d.cts +125 -16
  46. package/dist/index.d.ts +125 -16
  47. package/dist/index.js +4 -4
  48. package/dist/repositories/realm/index.cjs +14 -14
  49. package/dist/repositories/realm/index.cjs.map +1 -1
  50. package/dist/repositories/realm/index.d.cts +2 -2
  51. package/dist/repositories/realm/index.d.ts +2 -2
  52. package/dist/repositories/realm/index.js +5 -5
  53. package/dist/repositories/realm/index.js.map +1 -1
  54. package/dist/repositories/sqlite/index.cjs +13 -13
  55. package/dist/repositories/sqlite/index.d.cts +1 -1
  56. package/dist/repositories/sqlite/index.d.ts +1 -1
  57. package/dist/repositories/sqlite/index.js +4 -4
  58. package/dist/{taskRunner-pIGyarFG.d.cts → taskRunner-DCyp6Gea.d.cts} +2 -2
  59. package/dist/{taskRunner-B7lBU45X.d.ts → taskRunner-DnxtObeq.d.ts} +2 -2
  60. package/dist/wallet/expo/background.cjs +14 -14
  61. package/dist/wallet/expo/background.d.cts +3 -3
  62. package/dist/wallet/expo/background.d.ts +3 -3
  63. package/dist/wallet/expo/background.js +6 -6
  64. package/dist/wallet/expo/index.cjs +13 -13
  65. package/dist/wallet/expo/index.d.cts +5 -5
  66. package/dist/wallet/expo/index.d.ts +5 -5
  67. package/dist/wallet/expo/index.js +5 -5
  68. package/dist/{wallet-D4Dll5Gu.d.cts → wallet-BWHbd5b1.d.cts} +388 -10
  69. package/dist/{wallet-C4L_X0i6.d.ts → wallet-Bth5uucA.d.ts} +388 -10
  70. package/dist/worker/expo/index.cjs +9 -9
  71. package/dist/worker/expo/index.d.cts +4 -4
  72. package/dist/worker/expo/index.d.ts +4 -4
  73. package/dist/worker/expo/index.js +5 -5
  74. package/package.json +5 -5
  75. package/dist/chunk-5CCRRL5S.cjs.map +0 -1
  76. package/dist/chunk-7K3ROJF6.cjs.map +0 -1
  77. package/dist/chunk-AOJUURHM.js.map +0 -1
  78. package/dist/chunk-DSS2GQUG.js.map +0 -1
  79. package/dist/chunk-E22HEKLN.js.map +0 -1
  80. package/dist/chunk-GYSK5R57.cjs.map +0 -1
  81. package/dist/chunk-L6ZETTX3.js.map +0 -1
  82. package/dist/chunk-SPDNHPM4.cjs.map +0 -1
  83. package/dist/chunk-TU3LVAPX.js.map +0 -1
  84. package/dist/chunk-WMIPYZSB.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkSPDNHPM4_cjs = require('./chunk-SPDNHPM4.cjs');
4
- var chunkGYSK5R57_cjs = require('./chunk-GYSK5R57.cjs');
5
- var chunkWMIPYZSB_cjs = require('./chunk-WMIPYZSB.cjs');
3
+ var chunkX2EQLK4O_cjs = require('./chunk-X2EQLK4O.cjs');
4
+ var chunkGUTKJMSF_cjs = require('./chunk-GUTKJMSF.cjs');
5
+ var chunkCMPJR3HS_cjs = require('./chunk-CMPJR3HS.cjs');
6
6
  var utils_js = require('@scure/btc-signer/utils.js');
7
7
  var btcSigner = require('@scure/btc-signer');
8
8
  var base = require('@scure/base');
@@ -181,7 +181,7 @@ var TreeSignerSession = class _TreeSignerSession {
181
181
  noncesByPubkey.set(base.hex.encode(myPublicKey.subarray(1)), myNonce);
182
182
  const tx = this.graph.find(txid);
183
183
  if (!tx) throw new Error(`missing tx for txid ${txid}`);
184
- const cosigners = chunkSPDNHPM4_cjs.getArkPsbtFields(tx.root, 0, chunkSPDNHPM4_cjs.CosignerPublicKey).map(
184
+ const cosigners = chunkX2EQLK4O_cjs.getArkPsbtFields(tx.root, 0, chunkX2EQLK4O_cjs.CosignerPublicKey).map(
185
185
  (c) => base.hex.encode(c.key.subarray(1))
186
186
  // xonly pubkey
187
187
  );
@@ -233,7 +233,7 @@ var TreeSignerSession = class _TreeSignerSession {
233
233
  if (!aggNonce) throw new Error("missing aggregate nonce");
234
234
  const prevoutAmounts = [];
235
235
  const prevoutScripts = [];
236
- const cosigners = chunkSPDNHPM4_cjs.getArkPsbtFields(g.root, 0, chunkSPDNHPM4_cjs.CosignerPublicKey).map((c) => c.key);
236
+ const cosigners = chunkX2EQLK4O_cjs.getArkPsbtFields(g.root, 0, chunkX2EQLK4O_cjs.CosignerPublicKey).map((c) => c.key);
237
237
  const { finalKey } = aggregateKeys(cosigners, true, {
238
238
  taprootTweak: this.scriptRoot
239
239
  });
@@ -411,7 +411,7 @@ var SeedIdentity = class _SeedIdentity {
411
411
  let network;
412
412
  if ("descriptor" in opts && typeof opts.descriptor === "string") {
413
413
  descriptor = opts.descriptor;
414
- network = chunkGYSK5R57_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
414
+ network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
415
415
  } else {
416
416
  network = opts.isMainnet ?? true ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
417
417
  descriptor = descriptorsScure.scriptExpressions.trBIP32({
@@ -496,7 +496,7 @@ var SeedIdentity = class _SeedIdentity {
496
496
  * `StaticDescriptorProvider` for legacy single-key wallets.
497
497
  */
498
498
  isOurs(descriptor) {
499
- return chunkGYSK5R57_cjs.descriptorIsOurs(descriptor, this.descriptor, utils_js.pubSchnorr(this.derivedKey));
499
+ return chunkGUTKJMSF_cjs.descriptorIsOurs(descriptor, this.descriptor, utils_js.pubSchnorr(this.derivedKey));
500
500
  }
501
501
  /**
502
502
  * Signs each request with the key derived from its descriptor.
@@ -533,7 +533,7 @@ var SeedIdentity = class _SeedIdentity {
533
533
  }
534
534
  // ── internal helpers ─────────────────────────────────────────────
535
535
  derivePrivateKeyForDescriptor(descriptor) {
536
- const network = chunkGYSK5R57_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
536
+ const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
537
537
  const expansion = descriptorsScure.expand({ descriptor, network });
538
538
  if (expansion.isRanged) {
539
539
  throw new Error(
@@ -619,7 +619,7 @@ var ReadonlyDescriptorIdentity = class _ReadonlyDescriptorIdentity {
619
619
  */
620
620
  descriptor;
621
621
  constructor(descriptor) {
622
- const network = chunkGYSK5R57_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
622
+ const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
623
623
  let expansion;
624
624
  try {
625
625
  expansion = descriptorsScure.expand({ descriptor, network, index: 0 });
@@ -669,7 +669,7 @@ var ReadonlyDescriptorIdentity = class _ReadonlyDescriptorIdentity {
669
669
  * `StaticDescriptorProvider` for legacy single-key wallets.
670
670
  */
671
671
  isOurs(descriptor) {
672
- return chunkGYSK5R57_cjs.descriptorIsOurs(descriptor, this.descriptor, this.indexZero.pubkey);
672
+ return chunkGUTKJMSF_cjs.descriptorIsOurs(descriptor, this.descriptor, this.indexZero.pubkey);
673
673
  }
674
674
  };
675
675
  function serializeSeedOwnedSigningIdentity(identity) {
@@ -980,14 +980,14 @@ var RestDelegateProvider = class {
980
980
  */
981
981
  async delegate(intent, forfeitTxs, options) {
982
982
  const url = `${this.url}/v1/delegate`;
983
- const response = await fetch(url, {
983
+ const response = await chunkX2EQLK4O_cjs.baseFetch(url, {
984
984
  method: "POST",
985
985
  headers: {
986
986
  "Content-Type": "application/json"
987
987
  },
988
988
  body: JSON.stringify({
989
989
  intent: {
990
- message: chunkSPDNHPM4_cjs.Intent.encodeMessage(intent.message),
990
+ message: chunkX2EQLK4O_cjs.Intent.encodeMessage(intent.message),
991
991
  proof: intent.proof
992
992
  },
993
993
  forfeit_txs: forfeitTxs,
@@ -995,8 +995,8 @@ var RestDelegateProvider = class {
995
995
  })
996
996
  });
997
997
  if (!response.ok) {
998
- const errorText = await response.text();
999
- throw new Error(`Failed to delegate: ${errorText}`);
998
+ const errorText2 = await response.text();
999
+ throw new Error(`Failed to delegate: ${errorText2}`);
1000
1000
  }
1001
1001
  }
1002
1002
  /**
@@ -1007,10 +1007,10 @@ var RestDelegateProvider = class {
1007
1007
  */
1008
1008
  async getDelegateInfo() {
1009
1009
  const url = `${this.url}/v1/delegator/info`;
1010
- const response = await fetch(url);
1010
+ const response = await chunkX2EQLK4O_cjs.baseFetch(url);
1011
1011
  if (!response.ok) {
1012
- const errorText = await response.text();
1013
- throw new Error(`Failed to get delegate info: ${errorText}`);
1012
+ const errorText2 = await response.text();
1013
+ throw new Error(`Failed to get delegate info: ${errorText2}`);
1014
1014
  }
1015
1015
  const data = await response.json();
1016
1016
  if (!isDelegateInfo(data)) {
@@ -1031,10 +1031,10 @@ var ESPLORA_URL = {
1031
1031
  testnet: "https://mempool.space/testnet/api",
1032
1032
  signet: "https://mempool.signet.arkade.sh/api",
1033
1033
  mutinynet: "https://mempool.mutinynet.arkade.sh/api",
1034
- regtest: "http://localhost:3000"
1034
+ regtest: "http://localhost:3000/api"
1035
1035
  };
1036
1036
  var EsploraProvider = class {
1037
- constructor(baseUrl = ESPLORA_URL[chunkWMIPYZSB_cjs.DEFAULT_NETWORK_NAME], opts) {
1037
+ constructor(baseUrl = ESPLORA_URL[chunkCMPJR3HS_cjs.DEFAULT_NETWORK_NAME], opts) {
1038
1038
  this.baseUrl = baseUrl;
1039
1039
  this.pollingInterval = opts?.pollingInterval ?? 15e3;
1040
1040
  this.forcePolling = opts?.forcePolling ?? false;
@@ -1042,14 +1042,17 @@ var EsploraProvider = class {
1042
1042
  pollingInterval;
1043
1043
  forcePolling;
1044
1044
  async getCoins(address) {
1045
- const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
1045
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/address/${address}/utxo`);
1046
1046
  if (!response.ok) {
1047
1047
  throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
1048
1048
  }
1049
1049
  return response.json();
1050
1050
  }
1051
1051
  async getFeeRate() {
1052
- const response = await fetch(`${this.baseUrl}/fee-estimates`);
1052
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/fee-estimates`);
1053
+ if (response.status === 404) {
1054
+ return void 0;
1055
+ }
1053
1056
  if (!response.ok) {
1054
1057
  throw new Error(`Failed to fetch fee rate: ${response.statusText}`);
1055
1058
  }
@@ -1067,7 +1070,7 @@ var EsploraProvider = class {
1067
1070
  }
1068
1071
  }
1069
1072
  async getTxOutspends(txid) {
1070
- const response = await fetch(`${this.baseUrl}/tx/${txid}/outspends`);
1073
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/outspends`);
1071
1074
  if (!response.ok) {
1072
1075
  const error = await response.text();
1073
1076
  throw new Error(`Failed to get transaction outspends: ${error}`);
@@ -1075,7 +1078,7 @@ var EsploraProvider = class {
1075
1078
  return response.json();
1076
1079
  }
1077
1080
  async getTransactions(address) {
1078
- const response = await fetch(`${this.baseUrl}/address/${address}/txs`);
1081
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/address/${address}/txs`);
1079
1082
  if (!response.ok) {
1080
1083
  const error = await response.text();
1081
1084
  throw new Error(`Failed to get transactions: ${error}`);
@@ -1083,7 +1086,7 @@ var EsploraProvider = class {
1083
1086
  return response.json();
1084
1087
  }
1085
1088
  async getTxStatus(txid) {
1086
- const txresponse = await fetch(`${this.baseUrl}/tx/${txid}`);
1089
+ const txresponse = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}`);
1087
1090
  if (!txresponse.ok) {
1088
1091
  throw new Error(txresponse.statusText);
1089
1092
  }
@@ -1091,7 +1094,7 @@ var EsploraProvider = class {
1091
1094
  if (!tx.status.confirmed) {
1092
1095
  return { confirmed: false };
1093
1096
  }
1094
- const response = await fetch(`${this.baseUrl}/tx/${txid}/status`);
1097
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/status`);
1095
1098
  if (!response.ok) {
1096
1099
  throw new Error(`Failed to get transaction status: ${response.statusText}`);
1097
1100
  }
@@ -1175,7 +1178,7 @@ var EsploraProvider = class {
1175
1178
  return stopFunc;
1176
1179
  }
1177
1180
  async getChainTip() {
1178
- const tipBlocks = await fetch(`${this.baseUrl}/blocks/tip`);
1181
+ const tipBlocks = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/blocks`);
1179
1182
  if (!tipBlocks.ok) {
1180
1183
  throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
1181
1184
  }
@@ -1194,7 +1197,7 @@ var EsploraProvider = class {
1194
1197
  };
1195
1198
  }
1196
1199
  async broadcastPackage(parent, child) {
1197
- const response = await fetch(`${this.baseUrl}/txs/package`, {
1200
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/txs/package`, {
1198
1201
  method: "POST",
1199
1202
  headers: {
1200
1203
  "Content-Type": "application/json"
@@ -1208,7 +1211,7 @@ var EsploraProvider = class {
1208
1211
  return response.json();
1209
1212
  }
1210
1213
  async broadcastTx(tx) {
1211
- const response = await fetch(`${this.baseUrl}/tx`, {
1214
+ const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx`, {
1212
1215
  method: "POST",
1213
1216
  headers: {
1214
1217
  "Content-Type": "text/plain"
@@ -1277,7 +1280,7 @@ function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
1277
1280
  );
1278
1281
  }
1279
1282
  function buildForfeitTxWithOutput(inputs, output, txLocktime) {
1280
- const tx = new chunkSPDNHPM4_cjs.Transaction({
1283
+ const tx = new chunkX2EQLK4O_cjs.Transaction({
1281
1284
  version: 3,
1282
1285
  lockTime: txLocktime
1283
1286
  });
@@ -1353,7 +1356,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
1353
1356
  if (previousScriptKey.length !== 32) {
1354
1357
  throw new Error(`parent output ${childIndex} has invalid script`);
1355
1358
  }
1356
- const cosigners = chunkSPDNHPM4_cjs.getArkPsbtFields(child.root, 0, chunkSPDNHPM4_cjs.CosignerPublicKey);
1359
+ const cosigners = chunkX2EQLK4O_cjs.getArkPsbtFields(child.root, 0, chunkX2EQLK4O_cjs.CosignerPublicKey);
1357
1360
  if (cosigners.length === 0) {
1358
1361
  throw ErrMissingCosignersPublicKeys;
1359
1362
  }
@@ -1453,7 +1456,7 @@ var Extension = class _Extension {
1453
1456
  `expected magic prefix ${base.hex.encode(ARKADE_MAGIC)}, got ${base.hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
1454
1457
  );
1455
1458
  }
1456
- const reader = new chunkSPDNHPM4_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1459
+ const reader = new chunkX2EQLK4O_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1457
1460
  const packets = [];
1458
1461
  while (reader.remaining() > 0) {
1459
1462
  const packetType = reader.readByte();
@@ -1527,7 +1530,7 @@ var Extension = class _Extension {
1527
1530
  */
1528
1531
  getAssetPacket() {
1529
1532
  for (const p of this.packets) {
1530
- if (p instanceof chunkSPDNHPM4_cjs.Packet) {
1533
+ if (p instanceof chunkX2EQLK4O_cjs.Packet) {
1531
1534
  return p;
1532
1535
  }
1533
1536
  }
@@ -1535,8 +1538,8 @@ var Extension = class _Extension {
1535
1538
  }
1536
1539
  };
1537
1540
  function parsePacket(packetType, data) {
1538
- if (packetType === chunkSPDNHPM4_cjs.Packet.PACKET_TYPE) {
1539
- return chunkSPDNHPM4_cjs.Packet.fromBytes(data);
1541
+ if (packetType === chunkX2EQLK4O_cjs.Packet.PACKET_TYPE) {
1542
+ return chunkX2EQLK4O_cjs.Packet.fromBytes(data);
1540
1543
  }
1541
1544
  return new UnknownPacket(packetType, data);
1542
1545
  }
@@ -1594,7 +1597,7 @@ function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, netwo
1594
1597
  for (const recipient of recipients) {
1595
1598
  let arkAddress;
1596
1599
  try {
1597
- arkAddress = chunkWMIPYZSB_cjs.ArkAddress.decode(recipient.address);
1600
+ arkAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(recipient.address);
1598
1601
  } catch {
1599
1602
  validateOnchainRecipient(commitmentTx, recipient, network, usedOnchainOutputs);
1600
1603
  continue;
@@ -1727,7 +1730,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1727
1730
  const existing = inputsByAssetId.get(asset.assetId);
1728
1731
  inputsByAssetId.set(asset.assetId, [
1729
1732
  ...existing ?? [],
1730
- chunkSPDNHPM4_cjs.AssetInput.create(inputIndex, asset.amount)
1733
+ chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount)
1731
1734
  ]);
1732
1735
  }
1733
1736
  }
@@ -1739,7 +1742,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1739
1742
  const existing = outputsByAssetId.get(asset.assetId);
1740
1743
  outputsByAssetId.set(asset.assetId, [
1741
1744
  ...existing ?? [],
1742
- chunkSPDNHPM4_cjs.AssetOutput.create(outputIndex, asset.amount)
1745
+ chunkX2EQLK4O_cjs.AssetOutput.create(outputIndex, asset.amount)
1743
1746
  ]);
1744
1747
  }
1745
1748
  }
@@ -1750,7 +1753,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1750
1753
  const existing = outputsByAssetId.get(asset.assetId);
1751
1754
  outputsByAssetId.set(asset.assetId, [
1752
1755
  ...existing ?? [],
1753
- chunkSPDNHPM4_cjs.AssetOutput.create(outputIndex, asset.amount)
1756
+ chunkX2EQLK4O_cjs.AssetOutput.create(outputIndex, asset.amount)
1754
1757
  ]);
1755
1758
  }
1756
1759
  }
@@ -1759,11 +1762,11 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1759
1762
  for (const assetIdStr of allAssetIds) {
1760
1763
  const inputs = inputsByAssetId.get(assetIdStr);
1761
1764
  const outputs = outputsByAssetId.get(assetIdStr);
1762
- const assetId = chunkSPDNHPM4_cjs.AssetId.fromString(assetIdStr);
1763
- const group = chunkSPDNHPM4_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
1765
+ const assetId = chunkX2EQLK4O_cjs.AssetId.fromString(assetIdStr);
1766
+ const group = chunkX2EQLK4O_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
1764
1767
  groups.push(group);
1765
1768
  }
1766
- return chunkSPDNHPM4_cjs.Packet.create(groups);
1769
+ return chunkX2EQLK4O_cjs.Packet.create(groups);
1767
1770
  }
1768
1771
  function selectCoinsWithAsset(coins, assetId, requiredAmount) {
1769
1772
  const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
@@ -1798,6 +1801,45 @@ function selectedCoinsToAssetInputs(selectedCoins) {
1798
1801
  }
1799
1802
  return assetInputs;
1800
1803
  }
1804
+ function toXOnlySignerHex(pubkeyHex) {
1805
+ const bytes = base.hex.decode(pubkeyHex);
1806
+ if (bytes.length === 33) return base.hex.encode(bytes.slice(1));
1807
+ if (bytes.length === 32) return base.hex.encode(bytes);
1808
+ throw new Error(`invalid signer pubkey length: expected 32 or 33 bytes, got ${bytes.length}`);
1809
+ }
1810
+ function signerSetFromInfo(info) {
1811
+ const active = toXOnlySignerHex(info.signerPubkey);
1812
+ const deprecated = /* @__PURE__ */ new Map();
1813
+ for (const signer of info.deprecatedSigners) {
1814
+ if (!signer.pubkey) continue;
1815
+ deprecated.set(toXOnlySignerHex(signer.pubkey), signer.cutoffDate);
1816
+ }
1817
+ return { active, deprecated };
1818
+ }
1819
+ function classifyAgainstSignerSet(contractServerPubKeyHex, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
1820
+ const signerPubKey = toXOnlySignerHex(contractServerPubKeyHex);
1821
+ if (signerPubKey === signerSet.active) {
1822
+ return { status: "CURRENT", signerPubKey };
1823
+ }
1824
+ if (!signerSet.deprecated.has(signerPubKey)) {
1825
+ return { status: "UNKNOWN_SIGNER", signerPubKey };
1826
+ }
1827
+ const cutoffDate = signerSet.deprecated.get(signerPubKey);
1828
+ if (cutoffDate === 0n) {
1829
+ return { status: "DUE_NOW", signerPubKey };
1830
+ }
1831
+ const secondsUntilCutoff = Number(cutoffDate) - nowSeconds;
1832
+ if (secondsUntilCutoff <= 0) {
1833
+ return { status: "EXPIRED", signerPubKey, cutoffDate, secondsUntilCutoff };
1834
+ }
1835
+ return { status: "MIGRATABLE", signerPubKey, cutoffDate, secondsUntilCutoff };
1836
+ }
1837
+ function classifyContractSigner(contractServerPubKeyHex, info, nowSeconds = Math.floor(Date.now() / 1e3)) {
1838
+ return classifyAgainstSignerSet(contractServerPubKeyHex, signerSetFromInfo(info), nowSeconds);
1839
+ }
1840
+ function isCooperativelyMigratable(status) {
1841
+ return status === "MIGRATABLE" || status === "DUE_NOW";
1842
+ }
1801
1843
  function buildOffchainTx(inputs, outputs, serverUnrollScript) {
1802
1844
  const MAX_OP_RETURN = 2;
1803
1845
  let countOpReturn = 0;
@@ -1829,8 +1871,8 @@ function buildOffchainTx(inputs, outputs, serverUnrollScript) {
1829
1871
  function buildVirtualTx(inputs, outputs) {
1830
1872
  let lockTime = 0n;
1831
1873
  for (const input of inputs) {
1832
- const tapscript = chunkWMIPYZSB_cjs.decodeTapscript(chunkWMIPYZSB_cjs.scriptFromTapLeafScript(input.tapLeafScript));
1833
- if (chunkWMIPYZSB_cjs.CLTVMultisigTapscript.is(tapscript)) {
1874
+ const tapscript = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(input.tapLeafScript));
1875
+ if (chunkCMPJR3HS_cjs.CLTVMultisigTapscript.is(tapscript)) {
1834
1876
  if (lockTime !== 0n) {
1835
1877
  if (isSeconds(lockTime) !== isSeconds(tapscript.params.absoluteTimelock)) {
1836
1878
  throw new Error("cannot mix seconds and blocks locktime");
@@ -1841,7 +1883,7 @@ function buildVirtualTx(inputs, outputs) {
1841
1883
  }
1842
1884
  }
1843
1885
  }
1844
- const tx = new chunkSPDNHPM4_cjs.Transaction({
1886
+ const tx = new chunkX2EQLK4O_cjs.Transaction({
1845
1887
  version: 3,
1846
1888
  lockTime: Number(lockTime)
1847
1889
  });
@@ -1851,12 +1893,12 @@ function buildVirtualTx(inputs, outputs) {
1851
1893
  index: input.vout,
1852
1894
  sequence: lockTime ? btcSigner.DEFAULT_SEQUENCE - 1 : void 0,
1853
1895
  witnessUtxo: {
1854
- script: chunkWMIPYZSB_cjs.VtxoScript.decode(input.tapTree).pkScript,
1896
+ script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript,
1855
1897
  amount: BigInt(input.value)
1856
1898
  },
1857
1899
  tapLeafScript: [input.tapLeafScript]
1858
1900
  });
1859
- chunkSPDNHPM4_cjs.setArkPsbtField(tx, i, chunkSPDNHPM4_cjs.VtxoTaprootTree, input.tapTree);
1901
+ chunkX2EQLK4O_cjs.setArkPsbtField(tx, i, chunkX2EQLK4O_cjs.VtxoTaprootTree, input.tapTree);
1860
1902
  }
1861
1903
  for (const output of outputs) {
1862
1904
  tx.addOutput(output);
@@ -1865,8 +1907,8 @@ function buildVirtualTx(inputs, outputs) {
1865
1907
  return tx;
1866
1908
  }
1867
1909
  function buildCheckpointTx(vtxo, serverUnrollScript) {
1868
- const collaborativeClosure = chunkWMIPYZSB_cjs.decodeTapscript(chunkWMIPYZSB_cjs.scriptFromTapLeafScript(vtxo.tapLeafScript));
1869
- const checkpointVtxoScript = new chunkWMIPYZSB_cjs.VtxoScript([
1910
+ const collaborativeClosure = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(vtxo.tapLeafScript));
1911
+ const checkpointVtxoScript = new chunkCMPJR3HS_cjs.VtxoScript([
1870
1912
  serverUnrollScript.script,
1871
1913
  collaborativeClosure.script
1872
1914
  ]);
@@ -2010,7 +2052,7 @@ function combineTapscriptSigs(signedTx, originalTx) {
2010
2052
  }
2011
2053
  function isValidArkAddress(address) {
2012
2054
  try {
2013
- chunkWMIPYZSB_cjs.ArkAddress.decode(address);
2055
+ chunkCMPJR3HS_cjs.ArkAddress.decode(address);
2014
2056
  return true;
2015
2057
  } catch (e) {
2016
2058
  return false;
@@ -2280,16 +2322,16 @@ var FALLBACK_WALLET_DUST_AMOUNT = 330n;
2280
2322
  function getDustAmount(wallet) {
2281
2323
  return "dustAmount" in wallet ? wallet.dustAmount : FALLBACK_WALLET_DUST_AMOUNT;
2282
2324
  }
2283
- function extendCoin(wallet, utxo) {
2325
+ function extendCoinWithTapscript(boardingTapscript, utxo) {
2284
2326
  return {
2285
2327
  ...utxo,
2286
- forfeitTapLeafScript: wallet.boardingTapscript.forfeit(),
2287
- intentTapLeafScript: wallet.boardingTapscript.forfeit(),
2288
- tapTree: wallet.boardingTapscript.encode()
2328
+ forfeitTapLeafScript: boardingTapscript.forfeit(),
2329
+ intentTapLeafScript: boardingTapscript.forfeit(),
2330
+ tapTree: boardingTapscript.encode()
2289
2331
  };
2290
2332
  }
2291
2333
  function deriveContractTapscripts(contract) {
2292
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(contract.type);
2334
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
2293
2335
  if (!handler) {
2294
2336
  throw new Error(`No handler for contract type '${contract.type}'`);
2295
2337
  }
@@ -2359,7 +2401,7 @@ function validateRecipients(recipients, dustAmount) {
2359
2401
  for (const recipient of recipients) {
2360
2402
  let address;
2361
2403
  try {
2362
- address = chunkWMIPYZSB_cjs.ArkAddress.decode(recipient.address);
2404
+ address = chunkCMPJR3HS_cjs.ArkAddress.decode(recipient.address);
2363
2405
  } catch (e) {
2364
2406
  throw new Error(`Invalid Arkade address: ${recipient.address}`);
2365
2407
  }
@@ -2378,13 +2420,27 @@ function validateRecipients(recipients, dustAmount) {
2378
2420
  }
2379
2421
 
2380
2422
  // src/wallet/vtxo-manager.ts
2423
+ function selectPendingRecoveryOutpoints(contractsWithVtxos, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
2424
+ const out = /* @__PURE__ */ new Set();
2425
+ for (const { contract, vtxos } of contractsWithVtxos) {
2426
+ const serverPubKey = contract.params.serverPubKey;
2427
+ if (!serverPubKey) continue;
2428
+ if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status !== "EXPIRED") {
2429
+ continue;
2430
+ }
2431
+ for (const v of vtxos) {
2432
+ if (isSpendable(v) && !isRecoverable(v)) out.add(`${v.txid}:${v.vout}`);
2433
+ }
2434
+ }
2435
+ return out;
2436
+ }
2381
2437
  function isSweepCapable(wallet) {
2382
- return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet;
2438
+ return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet && "signOnchainBoardingTx" in wallet;
2383
2439
  }
2384
2440
  function assertSweepCapable(wallet) {
2385
2441
  if (!isSweepCapable(wallet)) {
2386
2442
  throw new Error(
2387
- "Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, and network"
2443
+ "Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, network, and signOnchainBoardingTx"
2388
2444
  );
2389
2445
  }
2390
2446
  }
@@ -2400,6 +2456,33 @@ async function runWithCrossInstanceLock(name, fn) {
2400
2456
  await fn();
2401
2457
  });
2402
2458
  }
2459
+ var MAX_VTXOS_PER_SETTLEMENT = 50;
2460
+ function byValueDescending(vtxos) {
2461
+ return [...vtxos].sort((a, b) => b.value - a.value);
2462
+ }
2463
+ function byExpiryAscending(vtxos) {
2464
+ const expiryKey = (vtxo) => {
2465
+ if (isRecoverable(vtxo)) return -Infinity;
2466
+ const batchExpiry = vtxo.virtualStatus.batchExpiry;
2467
+ if (isExpired(vtxo)) return batchExpiry ?? -Infinity;
2468
+ if (!batchExpiry) return Infinity;
2469
+ if (new Date(batchExpiry).getFullYear() < 2025) return Infinity;
2470
+ return batchExpiry;
2471
+ };
2472
+ return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
2473
+ }
2474
+ function capSettlementBatch(sorted, maxAmount) {
2475
+ const batch = [];
2476
+ let total = 0n;
2477
+ for (const vtxo of sorted) {
2478
+ if (batch.length >= MAX_VTXOS_PER_SETTLEMENT) break;
2479
+ const next = total + BigInt(vtxo.value);
2480
+ if (maxAmount >= 0n && next > maxAmount) continue;
2481
+ batch.push(vtxo);
2482
+ total = next;
2483
+ }
2484
+ return batch;
2485
+ }
2403
2486
  var DEFAULT_THRESHOLD_SECONDS = 259200;
2404
2487
  var DEFAULT_THRESHOLD_MS = DEFAULT_THRESHOLD_SECONDS * 1e3;
2405
2488
  var DEFAULT_RENEWAL_CONFIG = {
@@ -2409,7 +2492,8 @@ var DEFAULT_RENEWAL_CONFIG = {
2409
2492
  var DEFAULT_SETTLEMENT_CONFIG = {
2410
2493
  vtxoThreshold: DEFAULT_THRESHOLD_SECONDS,
2411
2494
  boardingUtxoSweep: true,
2412
- pollIntervalMs: 6e4
2495
+ pollIntervalMs: 6e4,
2496
+ deprecatedSignerMigration: true
2413
2497
  };
2414
2498
  function getRecoverableVtxos(vtxos, dustAmount) {
2415
2499
  return vtxos.filter((vtxo) => {
@@ -2463,6 +2547,51 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
2463
2547
  (vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || isRecoverable(vtxo) || isSpendable(vtxo) && isExpired(vtxo) || isSubdust(vtxo, dustAmount)
2464
2548
  );
2465
2549
  }
2550
+ function isMigrationCapable(wallet) {
2551
+ return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2552
+ }
2553
+ function classifiedToRef(c) {
2554
+ return {
2555
+ txid: c.vtxo.txid,
2556
+ vout: c.vtxo.vout,
2557
+ value: c.vtxo.value,
2558
+ signerPubKey: c.classification.signerPubKey,
2559
+ cutoffDate: c.classification.cutoffDate
2560
+ };
2561
+ }
2562
+ function classifiedBoardingToRef(c) {
2563
+ return {
2564
+ txid: c.coin.txid,
2565
+ vout: c.coin.vout,
2566
+ value: c.coin.value,
2567
+ signerPubKey: c.classification.signerPubKey,
2568
+ cutoffDate: c.classification.cutoffDate
2569
+ };
2570
+ }
2571
+ function mergeSignerReports(...reportLists) {
2572
+ const bySigner = /* @__PURE__ */ new Map();
2573
+ for (const list of reportLists) {
2574
+ for (const r of list) {
2575
+ const existing = bySigner.get(r.signerPubKey);
2576
+ if (existing) {
2577
+ existing.vtxoCount += r.vtxoCount;
2578
+ existing.totalValue += r.totalValue;
2579
+ existing.boardingCount += r.boardingCount;
2580
+ existing.boardingValue += r.boardingValue;
2581
+ existing.recoverableCount += r.recoverableCount;
2582
+ existing.recoverableValue += r.recoverableValue;
2583
+ existing.awaitingSweepCount += r.awaitingSweepCount;
2584
+ existing.awaitingSweepValue += r.awaitingSweepValue;
2585
+ if (r.nextSweepEta !== void 0) {
2586
+ existing.nextSweepEta = existing.nextSweepEta === void 0 ? r.nextSweepEta : Math.min(existing.nextSweepEta, r.nextSweepEta);
2587
+ }
2588
+ } else {
2589
+ bySigner.set(r.signerPubKey, { ...r });
2590
+ }
2591
+ }
2592
+ }
2593
+ return Array.from(bySigner.values());
2594
+ }
2466
2595
  var VtxoManager = class _VtxoManager {
2467
2596
  constructor(wallet, renewalConfig, settlementConfig) {
2468
2597
  this.wallet = wallet;
@@ -2478,15 +2607,9 @@ var VtxoManager = class _VtxoManager {
2478
2607
  } else {
2479
2608
  this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
2480
2609
  }
2481
- this.contractEventsSubscriptionReady = this.initializeSubscription().then(
2482
- (subscription) => {
2483
- this.contractEventsSubscription = subscription;
2484
- return subscription;
2485
- }
2486
- );
2610
+ this.contractEventsSubscriptionReady = this.initializeSubscription();
2487
2611
  }
2488
2612
  settlementConfig;
2489
- contractEventsSubscription;
2490
2613
  contractEventsSubscriptionReady;
2491
2614
  disposePromise;
2492
2615
  pollTimeoutId;
@@ -2523,6 +2646,15 @@ var VtxoManager = class _VtxoManager {
2523
2646
  lastVtxoSpentRefreshTimestamp = 0;
2524
2647
  vtxoSpentRefreshPromise;
2525
2648
  static VTXO_SPENT_REFRESH_COOLDOWN_MS = 3e4;
2649
+ // Cooldown/backoff for the automatic deprecated-signer migration pass.
2650
+ // Mirrors the periodic-settle machinery so a server-side migration failure
2651
+ // (e.g. arkd not yet accepting old-key inputs, or a closed cutoff window)
2652
+ // backs off exponentially instead of re-submitting an identical intent on
2653
+ // every poll. The manual migrateDeprecatedSignerVtxos() bypasses this.
2654
+ lastMigrationTimestamp = 0;
2655
+ consecutiveMigrationFailures = 0;
2656
+ static MIGRATION_COOLDOWN_MS = 3e4;
2657
+ static MIGRATION_MAX_BACKOFF_MS = 5 * 60 * 1e3;
2526
2658
  // ========== Recovery Methods ==========
2527
2659
  /**
2528
2660
  * Recover swept/expired virtual outputs by settling them back to the wallet's Arkade address.
@@ -2559,10 +2691,25 @@ var VtxoManager = class _VtxoManager {
2559
2691
  withUnrolled: false
2560
2692
  });
2561
2693
  const dustAmount = getDustAmount(this.wallet);
2562
- const { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
2694
+ let { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
2563
2695
  if (vtxosToRecover.length === 0) {
2564
2696
  throw new Error("No recoverable VTXOs found");
2565
2697
  }
2698
+ const info = await this.getInfoProvider()?.getInfo();
2699
+ const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
2700
+ const capped = capSettlementBatch(byValueDescending(vtxosToRecover), vtxoMaxAmount);
2701
+ if (capped.length < vtxosToRecover.length) {
2702
+ const recoverableCount = vtxosToRecover.length;
2703
+ ({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
2704
+ if (vtxosToRecover.length === 0) {
2705
+ throw new Error(
2706
+ `Capped recovery batch (highest-value subset of ${recoverableCount} recoverable VTXOs within the ${MAX_VTXOS_PER_SETTLEMENT}-input and ${vtxoMaxAmount}-sat limits) is below the dust threshold ${dustAmount}`
2707
+ );
2708
+ }
2709
+ }
2710
+ if (info && isMigrationCapable(this.wallet)) {
2711
+ await this.rotateForRecoverableInputs(vtxosToRecover, info);
2712
+ }
2566
2713
  const arkAddress = await this.wallet.getAddress();
2567
2714
  return this.wallet.settle(
2568
2715
  {
@@ -2712,6 +2859,25 @@ var VtxoManager = class _VtxoManager {
2712
2859
  if (vtxos.length === 0) {
2713
2860
  throw new Error("No VTXOs available to renew");
2714
2861
  }
2862
+ const info = await this.getInfoProvider()?.getInfo();
2863
+ const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
2864
+ const capped = capSettlementBatch(byExpiryAscending(vtxos), vtxoMaxAmount);
2865
+ if (vtxoMaxAmount >= 0n) {
2866
+ const oversized = vtxos.filter((vtxo) => BigInt(vtxo.value) > vtxoMaxAmount);
2867
+ if (oversized.length > 0) {
2868
+ console.warn(
2869
+ `Renewal: ${oversized.length} VTXO(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be renewed; they risk unilateral exit`
2870
+ );
2871
+ }
2872
+ }
2873
+ if (capped.length < vtxos.length) {
2874
+ vtxos = capped;
2875
+ if (vtxos.length === 0) {
2876
+ throw new Error(
2877
+ `No VTXOs available to renew within the per-output limit ${vtxoMaxAmount}`
2878
+ );
2879
+ }
2880
+ }
2715
2881
  const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
2716
2882
  const dustAmount = getDustAmount(this.wallet);
2717
2883
  if (BigInt(totalAmount) < dustAmount) {
@@ -2719,6 +2885,9 @@ var VtxoManager = class _VtxoManager {
2719
2885
  `Total amount ${totalAmount} is below dust threshold ${dustAmount}`
2720
2886
  );
2721
2887
  }
2888
+ if (info && isMigrationCapable(this.wallet)) {
2889
+ await this.rotateForRecoverableInputs(vtxos, info);
2890
+ }
2722
2891
  const arkAddress = await this.wallet.getAddress();
2723
2892
  const txid = await this.wallet.settle(
2724
2893
  {
@@ -2819,7 +2988,6 @@ var VtxoManager = class _VtxoManager {
2819
2988
  const boardingAddress = await this.wallet.getBoardingAddress();
2820
2989
  const feeRate = await this.getOnchainProvider().getFeeRate() ?? 1;
2821
2990
  const exitTapLeafScript = this.getBoardingExitLeaf();
2822
- const sequence = chunkWMIPYZSB_cjs.getSequence(exitTapLeafScript);
2823
2991
  const leafScript = exitTapLeafScript[1];
2824
2992
  const leafScriptSize = leafScript.length - 1;
2825
2993
  const controlBlockSize = exitTapLeafScript[0].merklePath.length * 32;
@@ -2838,21 +3006,30 @@ var VtxoManager = class _VtxoManager {
2838
3006
  `Sweep not economical: output ${outputAmount} sats after ${fee} sats fee is below dust (${dustAmount} sats)`
2839
3007
  );
2840
3008
  }
2841
- const tx = new chunkSPDNHPM4_cjs.Transaction();
3009
+ const tx = new chunkX2EQLK4O_cjs.Transaction();
2842
3010
  for (const utxo of expiredUtxos) {
3011
+ const utxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(utxo.tapTree);
3012
+ const utxoExitLeaf = utxoScript.leaves.find(
3013
+ (leaf) => chunkCMPJR3HS_cjs.CSVMultisigTapscript.isScriptValid(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(leaf)) === true
3014
+ );
3015
+ if (!utxoExitLeaf) {
3016
+ throw new Error(
3017
+ `Boarding sweep: no CSV exit leaf for UTXO ${utxo.txid}:${utxo.vout}`
3018
+ );
3019
+ }
2843
3020
  tx.addInput({
2844
3021
  txid: utxo.txid,
2845
3022
  index: utxo.vout,
2846
3023
  witnessUtxo: {
2847
- script: this.getBoardingOutputScript(),
3024
+ script: utxoScript.pkScript,
2848
3025
  amount: BigInt(utxo.value)
2849
3026
  },
2850
- tapLeafScript: [exitTapLeafScript],
2851
- sequence
3027
+ tapLeafScript: [utxoExitLeaf],
3028
+ sequence: chunkCMPJR3HS_cjs.getSequence(utxoExitLeaf)
2852
3029
  });
2853
3030
  }
2854
3031
  tx.addOutputAddress(boardingAddress, outputAmount, this.getNetwork());
2855
- const signedTx = await this.getIdentity().sign(tx);
3032
+ const signedTx = await this.getSweepWallet().signOnchainBoardingTx(tx);
2856
3033
  signedTx.finalize();
2857
3034
  const txid = await this.getOnchainProvider().broadcastTransaction(signedTx.hex);
2858
3035
  for (const u of expiredUtxos) {
@@ -2861,6 +3038,472 @@ var VtxoManager = class _VtxoManager {
2861
3038
  this.knownBoardingUtxos.add(`${txid}:0`);
2862
3039
  return txid;
2863
3040
  }
3041
+ // ========== Deprecated-Signer Migration Methods ==========
3042
+ /**
3043
+ * Cooperatively migrate VTXOs minted under a now-deprecated server signer
3044
+ * to the wallet's active-signer address. See {@link IVtxoManager}.
3045
+ */
3046
+ async migrateDeprecatedSignerVtxos(options) {
3047
+ return this.migrateCore(options);
3048
+ }
3049
+ /**
3050
+ * Machine-readable status of every deprecated server signer the wallet
3051
+ * currently holds funds under (Section 6), without migrating. Covers both
3052
+ * VTXO and boarding holdings (Section 7), merged per signer.
3053
+ *
3054
+ * @remarks This is no longer a pure repository/info read: surfacing boarding
3055
+ * holdings fans out per boarding address (`getCoins` round trips) and
3056
+ * refreshes the UTXO cache via `saveUtxos`.
3057
+ */
3058
+ async getDeprecatedSignerStatus() {
3059
+ const wallet = this.requireMigrationCapableWallet();
3060
+ const info = await wallet.arkProvider.getInfo();
3061
+ const { reports: vtxoReports } = await this.classifyDeprecatedSignerContracts(info);
3062
+ const { reports: boardingReports } = await this.classifyDeprecatedSignerBoarding(info);
3063
+ return mergeSignerReports(vtxoReports, boardingReports);
3064
+ }
3065
+ /**
3066
+ * Core migration routine shared by the manual API and the automatic poll
3067
+ * pass. Fetches a fresh {@link ArkInfo}, applies a mid-session signer
3068
+ * rotation when the wallet's own snapshot signer has been deprecated,
3069
+ * selects spendable VTXOs under deprecated-signer contracts (cutoff-first),
3070
+ * and settles them to the active-signer Ark address.
3071
+ */
3072
+ async migrateCore(options) {
3073
+ const wallet = this.requireMigrationCapableWallet();
3074
+ const info = await wallet.arkProvider.getInfo();
3075
+ const signerSet = signerSetFromInfo(info);
3076
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3077
+ const walletSignerHex = base.hex.encode(wallet.arkServerPublicKey);
3078
+ const walletClass = classifyAgainstSignerSet(walletSignerHex, signerSet, nowSeconds);
3079
+ if (signerSet.deprecated.size === 0 && walletClass.status === "CURRENT") {
3080
+ return { rotated: false, expired: [], signers: [] };
3081
+ }
3082
+ if (walletClass.status === "UNKNOWN_SIGNER") {
3083
+ const { reports: vtxoReports2 } = await this.classifyDeprecatedSignerContracts(info);
3084
+ const { reports: boardingReports2 } = await this.classifyDeprecatedSignerBoarding(info);
3085
+ return {
3086
+ rotated: false,
3087
+ expired: [],
3088
+ signers: mergeSignerReports(vtxoReports2, boardingReports2),
3089
+ skipped: "unknown-wallet-signer"
3090
+ };
3091
+ }
3092
+ const rotated = await this.ensureReceiveOnActiveSigner(info);
3093
+ const {
3094
+ reports: vtxoReports,
3095
+ migratable: vtxoMigratable,
3096
+ expired: vtxoExpired
3097
+ } = await this.classifyDeprecatedSignerContracts(info);
3098
+ const {
3099
+ reports: boardingReports,
3100
+ migratable: boardingMigratable,
3101
+ expired: boardingExpired
3102
+ } = await this.classifyDeprecatedSignerBoarding(info);
3103
+ const reports = mergeSignerReports(vtxoReports, boardingReports);
3104
+ const expiredRefs = [
3105
+ ...vtxoExpired.map(classifiedToRef),
3106
+ ...boardingExpired.map(classifiedBoardingToRef)
3107
+ ];
3108
+ if (vtxoMigratable.length === 0 && boardingMigratable.length === 0) {
3109
+ return {
3110
+ rotated,
3111
+ expired: expiredRefs,
3112
+ signers: reports,
3113
+ skipped: "no-deprecated-vtxos"
3114
+ };
3115
+ }
3116
+ const vtxoMaxAmount = info.vtxoMaxAmount;
3117
+ const dustAmount = getDustAmount(this.wallet);
3118
+ const report = {
3119
+ rotated,
3120
+ expired: expiredRefs,
3121
+ signers: reports
3122
+ };
3123
+ if (vtxoMigratable.length > 0) {
3124
+ report.vtxos = await this.runMigrationLeg(
3125
+ vtxoMigratable,
3126
+ (c) => c.vtxo.value,
3127
+ classifiedToRef,
3128
+ vtxoMaxAmount,
3129
+ dustAmount,
3130
+ "VTXO",
3131
+ (capped) => wallet.sendSelectedVtxosToSelf(capped.map((c) => c.vtxo))
3132
+ );
3133
+ }
3134
+ if (boardingMigratable.length > 0) {
3135
+ report.boarding = await this.runMigrationLeg(
3136
+ boardingMigratable,
3137
+ (c) => c.coin.value,
3138
+ classifiedBoardingToRef,
3139
+ vtxoMaxAmount,
3140
+ dustAmount,
3141
+ "boarding",
3142
+ async (capped) => {
3143
+ const arkAddress = await this.wallet.getAddress();
3144
+ const totalAmount = capped.reduce((sum, c) => sum + BigInt(c.coin.value), 0n);
3145
+ return this.wallet.settle(
3146
+ {
3147
+ inputs: capped.map((c) => c.coin),
3148
+ outputs: [{ address: arkAddress, amount: totalAmount }]
3149
+ },
3150
+ options?.eventCallback
3151
+ );
3152
+ }
3153
+ );
3154
+ }
3155
+ return report;
3156
+ }
3157
+ /**
3158
+ * Size and submit one migration leg. Filters inputs whose value alone
3159
+ * exceeds the per-output ceiling (`vtxoMaxAmount`; `< 0` means no limit) —
3160
+ * those can never form a ≤-ceiling output and must exit unilaterally — then
3161
+ * caps the rest (highest-value first; bounded by {@link MAX_VTXOS_PER_SETTLEMENT}
3162
+ * AND a gross total within `vtxoMaxAmount`), applies the protocol dust floor,
3163
+ * and submits the capped batch through `submit`. A throw from `submit` lands
3164
+ * in `error`; the caller's other leg still runs.
3165
+ *
3166
+ * Migration is mandatory and fee-exempt: every selected input moves at its
3167
+ * full value, so the gross total IS the aggregated output amount (kept under
3168
+ * the server ceiling by the cap). The dust floor guards the degenerate cases
3169
+ * where every input was oversized or the whole holding sums below dust.
3170
+ */
3171
+ async runMigrationLeg(candidates, valueOf, toRef, vtxoMaxAmount, dustAmount, legName, submit) {
3172
+ const oversizedRefs = [];
3173
+ const sized = [];
3174
+ for (const c of candidates) {
3175
+ if (vtxoMaxAmount >= 0n && BigInt(valueOf(c)) > vtxoMaxAmount) {
3176
+ oversizedRefs.push(toRef(c));
3177
+ } else {
3178
+ sized.push(c);
3179
+ }
3180
+ }
3181
+ if (oversizedRefs.length > 0) {
3182
+ console.warn(
3183
+ `Deprecated-signer migration (${legName}): ${oversizedRefs.length} input(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be migrated cooperatively; they require a unilateral exit.`
3184
+ );
3185
+ }
3186
+ const oversizedField = oversizedRefs.length > 0 ? { oversized: oversizedRefs } : {};
3187
+ const capped = capSettlementBatch(
3188
+ byValueDescending(sized.map((c) => ({ value: valueOf(c), c }))),
3189
+ vtxoMaxAmount
3190
+ ).map((w) => w.c);
3191
+ const deferred = sized.length - capped.length;
3192
+ const totalAmount = capped.reduce((sum, c) => sum + BigInt(valueOf(c)), 0n);
3193
+ if (totalAmount < dustAmount) {
3194
+ const onlyOversized = sized.length === 0 && oversizedRefs.length > 0;
3195
+ return {
3196
+ migrated: [],
3197
+ skipped: onlyOversized ? "oversized-only" : "below-dust",
3198
+ ...oversizedField
3199
+ };
3200
+ }
3201
+ try {
3202
+ const txid = await submit(capped);
3203
+ return {
3204
+ txid,
3205
+ migrated: capped.map(toRef),
3206
+ ...deferred > 0 ? { deferred } : {},
3207
+ ...oversizedField
3208
+ };
3209
+ } catch (e) {
3210
+ return {
3211
+ migrated: [],
3212
+ error: e instanceof Error ? e.message : String(e),
3213
+ ...oversizedField
3214
+ };
3215
+ }
3216
+ }
3217
+ /**
3218
+ * Enumerate the wallet's `default`/`delegate` contracts, classify each
3219
+ * against the fresh signer set, and split their spendable VTXOs into
3220
+ * cooperatively-migratable and cutoff-expired sets while building the
3221
+ * per-signer status report. Current-signer contracts are skipped; swept
3222
+ * (recoverable) VTXOs are excluded from the settle sets — those follow the
3223
+ * recovery path — but are still counted on EXPIRED report rows
3224
+ * (`recoverableCount`) so post-cutoff funds in flight stay visible.
3225
+ */
3226
+ async classifyDeprecatedSignerContracts(info) {
3227
+ const cm = await this.wallet.getContractManager();
3228
+ const signerSet = signerSetFromInfo(info);
3229
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3230
+ const contractsWithVtxos = await cm.getContractsWithVtxos({
3231
+ type: ["default", "delegate"]
3232
+ });
3233
+ const reportsBySigner = /* @__PURE__ */ new Map();
3234
+ const migratable = [];
3235
+ const expired = [];
3236
+ for (const { contract, vtxos } of contractsWithVtxos) {
3237
+ const serverPubKey = contract.params.serverPubKey;
3238
+ if (!serverPubKey) continue;
3239
+ const cls = classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds);
3240
+ if (cls.status === "CURRENT") continue;
3241
+ const recoverable = vtxos.filter((v) => isRecoverable(v));
3242
+ const spendable = vtxos.filter((v) => isSpendable(v) && !isRecoverable(v));
3243
+ const value = spendable.reduce((sum, v) => sum + v.value, 0);
3244
+ let recoverableCount = 0;
3245
+ let recoverableValue = 0;
3246
+ let awaitingSweepCount = 0;
3247
+ let awaitingSweepValue = 0;
3248
+ let nextSweepEta;
3249
+ if (cls.status === "EXPIRED") {
3250
+ recoverableCount = recoverable.length;
3251
+ recoverableValue = recoverable.reduce((sum, v) => sum + v.value, 0);
3252
+ awaitingSweepCount = spendable.length;
3253
+ awaitingSweepValue = value;
3254
+ for (const v of spendable) {
3255
+ const exp = v.virtualStatus.batchExpiry;
3256
+ if (exp !== void 0 && (nextSweepEta === void 0 || exp < nextSweepEta)) {
3257
+ nextSweepEta = exp;
3258
+ }
3259
+ }
3260
+ }
3261
+ const existing = reportsBySigner.get(cls.signerPubKey);
3262
+ if (existing) {
3263
+ existing.vtxoCount += spendable.length;
3264
+ existing.totalValue += value;
3265
+ existing.recoverableCount += recoverableCount;
3266
+ existing.recoverableValue += recoverableValue;
3267
+ existing.awaitingSweepCount += awaitingSweepCount;
3268
+ existing.awaitingSweepValue += awaitingSweepValue;
3269
+ if (nextSweepEta !== void 0) {
3270
+ existing.nextSweepEta = existing.nextSweepEta === void 0 ? nextSweepEta : Math.min(existing.nextSweepEta, nextSweepEta);
3271
+ }
3272
+ } else {
3273
+ reportsBySigner.set(cls.signerPubKey, {
3274
+ signerPubKey: cls.signerPubKey,
3275
+ status: cls.status,
3276
+ cutoffDate: cls.cutoffDate,
3277
+ secondsUntilCutoff: cls.secondsUntilCutoff,
3278
+ vtxoCount: spendable.length,
3279
+ totalValue: value,
3280
+ boardingCount: 0,
3281
+ boardingValue: 0,
3282
+ recoverableCount,
3283
+ recoverableValue,
3284
+ awaitingSweepCount,
3285
+ awaitingSweepValue,
3286
+ nextSweepEta
3287
+ });
3288
+ }
3289
+ if (isCooperativelyMigratable(cls.status)) {
3290
+ for (const v of spendable) {
3291
+ if (!v.virtualStatus.batchExpiry) continue;
3292
+ migratable.push({ vtxo: v, classification: cls });
3293
+ }
3294
+ } else if (cls.status === "EXPIRED") {
3295
+ for (const v of spendable) expired.push({ vtxo: v, classification: cls });
3296
+ }
3297
+ }
3298
+ return {
3299
+ reports: Array.from(reportsBySigner.values()),
3300
+ migratable,
3301
+ expired
3302
+ };
3303
+ }
3304
+ /**
3305
+ * Boarding sibling of {@link classifyDeprecatedSignerContracts} (Section 7):
3306
+ * fan out over the wallet's boarding addresses (current + historical), group
3307
+ * the on-chain UTXOs per address, classify each address's signer against the
3308
+ * fresh signer set, and split the confirmed boarding coins into cooperatively-
3309
+ * migratable and cutoff-expired sets while building the per-signer report.
3310
+ *
3311
+ * Discovery sees the active signer plus EVERY deprecated key (EXPIRED
3312
+ * included), so expired-signer boarding is still reported; migration
3313
+ * eligibility is gated afterwards by {@link isCooperativelyMigratable} and a
3314
+ * per-row boarding-output CSV check — never by the fetch. Current-signer
3315
+ * coins are classified `CURRENT` and ignored; foreign-ASP rows are excluded
3316
+ * because their keys are not in the signer set.
3317
+ */
3318
+ async classifyDeprecatedSignerBoarding(info) {
3319
+ const wallet = this.requireMigrationCapableWallet();
3320
+ const signerSet = signerSetFromInfo(info);
3321
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3322
+ const allowed = /* @__PURE__ */ new Set([signerSet.active, ...signerSet.deprecated.keys()]);
3323
+ const groups = await wallet.getBoardingUtxosForSigners(allowed);
3324
+ let chainTipHeight;
3325
+ if (groups.some((g) => g.csvTimelock.type === "blocks")) {
3326
+ const tip = await wallet.onchainProvider.getChainTip();
3327
+ chainTipHeight = tip.height;
3328
+ }
3329
+ const reportsBySigner = /* @__PURE__ */ new Map();
3330
+ const migratable = [];
3331
+ const expired = [];
3332
+ for (const group of groups) {
3333
+ const cls = classifyAgainstSignerSet(group.serverPubKey, signerSet, nowSeconds);
3334
+ if (cls.status === "CURRENT") continue;
3335
+ const confirmed = group.coins.filter((c) => c.status.confirmed);
3336
+ if (confirmed.length === 0) continue;
3337
+ const value = confirmed.reduce((sum, c) => sum + c.value, 0);
3338
+ const existing = reportsBySigner.get(cls.signerPubKey);
3339
+ if (existing) {
3340
+ existing.boardingCount += confirmed.length;
3341
+ existing.boardingValue += value;
3342
+ } else {
3343
+ reportsBySigner.set(cls.signerPubKey, {
3344
+ signerPubKey: cls.signerPubKey,
3345
+ status: cls.status,
3346
+ cutoffDate: cls.cutoffDate,
3347
+ secondsUntilCutoff: cls.secondsUntilCutoff,
3348
+ vtxoCount: 0,
3349
+ totalValue: 0,
3350
+ boardingCount: confirmed.length,
3351
+ boardingValue: value,
3352
+ // Boarding UTXOs don't carry an offchain sweep lifecycle; the
3353
+ // post-cutoff recover-on-sweep fields apply to VTXOs only and
3354
+ // are merged in from the VTXO classifier (mergeSignerReports).
3355
+ recoverableCount: 0,
3356
+ recoverableValue: 0,
3357
+ awaitingSweepCount: 0,
3358
+ awaitingSweepValue: 0
3359
+ });
3360
+ }
3361
+ for (const coin of confirmed) {
3362
+ const boardingExpired = hasBoardingTxExpired(
3363
+ coin,
3364
+ group.csvTimelock,
3365
+ chainTipHeight
3366
+ );
3367
+ if (isCooperativelyMigratable(cls.status) && !boardingExpired) {
3368
+ migratable.push({ coin, classification: cls });
3369
+ } else if (cls.status === "EXPIRED") {
3370
+ expired.push({ coin, classification: cls });
3371
+ }
3372
+ }
3373
+ }
3374
+ return {
3375
+ reports: Array.from(reportsBySigner.values()),
3376
+ migratable,
3377
+ expired
3378
+ };
3379
+ }
3380
+ /**
3381
+ * Automatic migration pass invoked from the poll loop. Self-contained:
3382
+ * respects an exponential cooldown and logs failures rather than throwing,
3383
+ * so a persistently failing migration backs off instead of re-submitting
3384
+ * an identical intent every cycle.
3385
+ */
3386
+ async runMigrationPass() {
3387
+ const cooldownMs = Math.min(
3388
+ _VtxoManager.MIGRATION_COOLDOWN_MS * Math.pow(2, this.consecutiveMigrationFailures),
3389
+ _VtxoManager.MIGRATION_MAX_BACKOFF_MS
3390
+ );
3391
+ if (Date.now() - this.lastMigrationTimestamp < cooldownMs) return;
3392
+ try {
3393
+ const report = await this.migrateCore();
3394
+ const legError = report.vtxos?.error ?? report.boarding?.error;
3395
+ if (legError) {
3396
+ this.consecutiveMigrationFailures++;
3397
+ console.error("Deprecated-signer migration leg failed:", legError);
3398
+ } else {
3399
+ this.consecutiveMigrationFailures = 0;
3400
+ }
3401
+ } catch (e) {
3402
+ this.consecutiveMigrationFailures++;
3403
+ console.error("Error during deprecated-signer migration:", e);
3404
+ } finally {
3405
+ this.lastMigrationTimestamp = Date.now();
3406
+ }
3407
+ }
3408
+ /** Asserts migration capability and returns the typed wallet. */
3409
+ requireMigrationCapableWallet() {
3410
+ if (!isMigrationCapable(this.wallet)) {
3411
+ throw new Error(
3412
+ "Deprecated-signer migration requires a Wallet instance with arkProvider, arkServerPublicKey, and rotateServerSigner"
3413
+ );
3414
+ }
3415
+ return this.wallet;
3416
+ }
3417
+ /**
3418
+ * If the wallet's own construction-time signer snapshot has been deprecated,
3419
+ * re-derive its receive/boarding state under the active signer so any output
3420
+ * built afterwards commits to the active key. No-op when the snapshot is
3421
+ * already current. Returns whether a rotation was applied. Treats an
3422
+ * unknown-signer snapshot as "do not rotate" (caller decides).
3423
+ *
3424
+ * Shared by the migration pass (where the wallet's own snapshot is the thing
3425
+ * being migrated) and the recovery/renewal/periodic-settle paths (via
3426
+ * {@link rotateForRecoverableInputs}), so a swept old-signer VTXO recovered
3427
+ * after cutoff re-mints under the active signer rather than re-committing to
3428
+ * the deprecated key (Section 6 / post-cutoff). `rotateServerSigner` is
3429
+ * idempotent and serializes itself against HD receive rotation, so repeated
3430
+ * calls across passes are safe.
3431
+ */
3432
+ async ensureReceiveOnActiveSigner(info) {
3433
+ const wallet = this.requireMigrationCapableWallet();
3434
+ const signerSet = signerSetFromInfo(info);
3435
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3436
+ const walletClass = classifyAgainstSignerSet(
3437
+ base.hex.encode(wallet.arkServerPublicKey),
3438
+ signerSet,
3439
+ nowSeconds
3440
+ );
3441
+ if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
3442
+ return false;
3443
+ }
3444
+ await wallet.rotateServerSigner(base.hex.decode(info.signerPubkey), info.checkpointTapscript);
3445
+ return true;
3446
+ }
3447
+ /**
3448
+ * Rotation guard for the recovery-bearing settle paths (recover / renew /
3449
+ * periodic settle). Pins the wallet's receive snapshot to the active signer
3450
+ * before they build their output, but ONLY when this pass actually carries
3451
+ * an input minted under a deprecated signer — so a routine current-signer
3452
+ * settle on a long-lived pre-rotation instance does not eagerly rotate.
3453
+ *
3454
+ * Cheap in the common case: a watch-only/proxy wallet (not migration-capable)
3455
+ * and a current/unknown wallet snapshot both short-circuit before the
3456
+ * contract round-trip, so the only instance that pays for the input scan is
3457
+ * the long-lived deprecated-snapshot one that genuinely needs rotating.
3458
+ *
3459
+ * Runs OUTSIDE any `renewalInProgress` window the caller sets, and
3460
+ * `rotateServerSigner` does not depend on that flag, so it cannot deadlock
3461
+ * against the receive rotator. Returns whether a rotation was applied.
3462
+ */
3463
+ async rotateForRecoverableInputs(inputs, info) {
3464
+ if (!isMigrationCapable(this.wallet)) return false;
3465
+ const signerSet = signerSetFromInfo(info);
3466
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3467
+ const walletClass = classifyAgainstSignerSet(
3468
+ base.hex.encode(this.wallet.arkServerPublicKey),
3469
+ signerSet,
3470
+ nowSeconds
3471
+ );
3472
+ if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
3473
+ return false;
3474
+ }
3475
+ if (!await this.anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds)) {
3476
+ return false;
3477
+ }
3478
+ return this.ensureReceiveOnActiveSigner(info);
3479
+ }
3480
+ /**
3481
+ * Whether any of the given input outpoints belongs to a contract whose
3482
+ * server signer classifies as non-`CURRENT` against the fresh signer set —
3483
+ * i.e. a deprecated-signer (incl. EXPIRED) input. Maps outpoints to their
3484
+ * owning contract via the ContractManager so it works on the typed
3485
+ * {@link ExtendedVirtualCoin}/{@link ExtendedCoin} inputs the recovery paths
3486
+ * carry (which don't expose `contractScript`).
3487
+ */
3488
+ async anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds) {
3489
+ if (inputs.length === 0) return false;
3490
+ const wanted = new Set(inputs.map((i) => `${i.txid}:${i.vout}`));
3491
+ const cm = await this.wallet.getContractManager();
3492
+ const contractsWithVtxos = await cm.getContractsWithVtxos({
3493
+ type: ["default", "delegate"]
3494
+ });
3495
+ for (const { contract, vtxos } of contractsWithVtxos) {
3496
+ const serverPubKey = contract.params.serverPubKey;
3497
+ if (!serverPubKey) continue;
3498
+ if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status === "CURRENT") {
3499
+ continue;
3500
+ }
3501
+ for (const v of vtxos) {
3502
+ if (wanted.has(`${v.txid}:${v.vout}`)) return true;
3503
+ }
3504
+ }
3505
+ return false;
3506
+ }
2864
3507
  // ========== Private Helpers ==========
2865
3508
  /** Asserts sweep capability and returns the typed wallet. */
2866
3509
  getSweepWallet() {
@@ -2870,7 +3513,7 @@ var VtxoManager = class _VtxoManager {
2870
3513
  /** Decodes the boarding tapscript exit path to extract the CSV timelock. */
2871
3514
  getBoardingTimelock() {
2872
3515
  const wallet = this.getSweepWallet();
2873
- const exitScript = chunkWMIPYZSB_cjs.CSVMultisigTapscript.decode(
3516
+ const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
2874
3517
  base.hex.decode(wallet.boardingTapscript.exitScript)
2875
3518
  );
2876
3519
  return exitScript.params.timelock;
@@ -2879,10 +3522,6 @@ var VtxoManager = class _VtxoManager {
2879
3522
  getBoardingExitLeaf() {
2880
3523
  return this.getSweepWallet().boardingTapscript.exit();
2881
3524
  }
2882
- /** Returns the pkScript (output script) of the boarding tapscript. */
2883
- getBoardingOutputScript() {
2884
- return this.getSweepWallet().boardingTapscript.pkScript;
2885
- }
2886
3525
  /** Returns the onchain provider for fee estimation and broadcasting. */
2887
3526
  getOnchainProvider() {
2888
3527
  return this.getSweepWallet().onchainProvider;
@@ -2891,14 +3530,20 @@ var VtxoManager = class _VtxoManager {
2891
3530
  getArkProvider() {
2892
3531
  return this.getSweepWallet().arkProvider;
2893
3532
  }
3533
+ /**
3534
+ * Read-only access to the ark provider for fetching server limits. Unlike
3535
+ * {@link getArkProvider}, this does not require full boarding-sweep
3536
+ * capability — recovery and renewal only need it to read `vtxoMaxAmount`.
3537
+ * Returns undefined when no provider is wired, which callers treat as
3538
+ * "no limit".
3539
+ */
3540
+ getInfoProvider() {
3541
+ return this.wallet.arkProvider;
3542
+ }
2894
3543
  /** Returns the Bitcoin network configuration from the wallet. */
2895
3544
  getNetwork() {
2896
3545
  return this.getSweepWallet().network;
2897
3546
  }
2898
- /** Returns the wallet's identity for transaction signing. */
2899
- getIdentity() {
2900
- return this.wallet.identity;
2901
- }
2902
3547
  async initializeSubscription() {
2903
3548
  if (this.settlementConfig === false) {
2904
3549
  return void 0;
@@ -2999,7 +3644,7 @@ var VtxoManager = class _VtxoManager {
2999
3644
  * or doesn't carry the metadata.
3000
3645
  */
3001
3646
  extractSpentOutpoint(error) {
3002
- const ark = chunkSPDNHPM4_cjs.maybeArkError(error);
3647
+ const ark = chunkX2EQLK4O_cjs.maybeArkError(error);
3003
3648
  if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
3004
3649
  const raw = ark.metadata?.vtxo_outpoint;
3005
3650
  if (typeof raw !== "string") return void 0;
@@ -3096,6 +3741,10 @@ var VtxoManager = class _VtxoManager {
3096
3741
  }
3097
3742
  }
3098
3743
  }
3744
+ const migrationEnabled = this.settlementConfig !== false && (this.settlementConfig?.deprecatedSignerMigration ?? DEFAULT_SETTLEMENT_CONFIG.deprecatedSignerMigration);
3745
+ if (migrationEnabled && isMigrationCapable(this.wallet)) {
3746
+ await this.runMigrationPass();
3747
+ }
3099
3748
  });
3100
3749
  } catch (e) {
3101
3750
  hadError = true;
@@ -3164,7 +3813,8 @@ var VtxoManager = class _VtxoManager {
3164
3813
  return;
3165
3814
  }
3166
3815
  const dustAmount = getDustAmount(this.wallet);
3167
- const { fees } = await this.getArkProvider().getInfo();
3816
+ const info = await this.getArkProvider().getInfo();
3817
+ const { fees, vtxoMaxAmount } = info;
3168
3818
  const estimator = new Estimator(fees.intentFee);
3169
3819
  let totalAmount = 0n;
3170
3820
  const filteredBoarding = [];
@@ -3179,7 +3829,10 @@ var VtxoManager = class _VtxoManager {
3179
3829
  totalAmount += BigInt(u.value) - BigInt(inputFee.satoshis);
3180
3830
  }
3181
3831
  const filteredVtxos = [];
3182
- for (const v of expiringVtxos) {
3832
+ for (const v of byExpiryAscending(expiringVtxos)) {
3833
+ if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
3834
+ break;
3835
+ }
3183
3836
  const inputFee = estimator.evalOffchainInput({
3184
3837
  amount: BigInt(v.value),
3185
3838
  type: v.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
@@ -3190,16 +3843,23 @@ var VtxoManager = class _VtxoManager {
3190
3843
  if (inputFee.satoshis >= v.value) {
3191
3844
  continue;
3192
3845
  }
3846
+ const net = BigInt(v.value) - BigInt(inputFee.satoshis);
3847
+ if (vtxoMaxAmount >= 0n && totalAmount + net > vtxoMaxAmount) {
3848
+ continue;
3849
+ }
3193
3850
  filteredVtxos.push(v);
3194
- totalAmount += BigInt(v.value) - BigInt(inputFee.satoshis);
3851
+ totalAmount += net;
3195
3852
  }
3196
3853
  if (filteredBoarding.length === 0 && filteredVtxos.length === 0) {
3197
3854
  return;
3198
3855
  }
3856
+ if (isMigrationCapable(this.wallet)) {
3857
+ await this.rotateForRecoverableInputs([...filteredBoarding, ...filteredVtxos], info);
3858
+ }
3199
3859
  const arkAddress = await this.wallet.getAddress();
3200
3860
  const outputFee = estimator.evalOffchainOutput({
3201
3861
  amount: totalAmount,
3202
- script: base.hex.encode(chunkWMIPYZSB_cjs.ArkAddress.decode(arkAddress).pkScript)
3862
+ script: base.hex.encode(chunkCMPJR3HS_cjs.ArkAddress.decode(arkAddress).pkScript)
3203
3863
  });
3204
3864
  totalAmount -= BigInt(outputFee.satoshis);
3205
3865
  if (totalAmount < dustAmount) return;
@@ -3258,7 +3918,6 @@ var VtxoManager = class _VtxoManager {
3258
3918
  clearTimeout(timer);
3259
3919
  }
3260
3920
  const subscription = await this.contractEventsSubscriptionReady;
3261
- this.contractEventsSubscription = void 0;
3262
3921
  subscription?.();
3263
3922
  })();
3264
3923
  return this.disposePromise;
@@ -3280,7 +3939,7 @@ var ArkNote = class _ArkNote {
3280
3939
  this.value = value;
3281
3940
  this.HRP = HRP;
3282
3941
  const preimageHash = utils_js.sha256(this.preimage);
3283
- this.vtxoScript = new chunkWMIPYZSB_cjs.VtxoScript([noteTapscript(preimageHash)]);
3942
+ this.vtxoScript = new chunkCMPJR3HS_cjs.VtxoScript([noteTapscript(preimageHash)]);
3284
3943
  const leaf = this.vtxoScript.leaves[0];
3285
3944
  this.txid = base.hex.encode(new Uint8Array(preimageHash).reverse());
3286
3945
  this.tapTree = this.vtxoScript.encode();
@@ -3799,15 +4458,17 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
3799
4458
  txTime = getTxCreatedAt ? await getTxCreatedAt(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1 : vtxo.createdAt.getTime() + 1;
3800
4459
  }
3801
4460
  const assets = subtractAssets(allSpent, changes);
3802
- sent.push({
3803
- key: { ...txKey, arkTxid: vtxo.arkTxId },
3804
- tag: "offchain",
3805
- type: "SENT" /* TxSent */,
3806
- amount: txAmount,
3807
- settled: true,
3808
- createdAt: txTime,
3809
- ...assets && { assets }
3810
- });
4461
+ if (txAmount !== 0 || assets) {
4462
+ sent.push({
4463
+ key: { ...txKey, arkTxid: vtxo.arkTxId },
4464
+ tag: "offchain",
4465
+ type: "SENT" /* TxSent */,
4466
+ amount: txAmount,
4467
+ settled: true,
4468
+ createdAt: txTime,
4469
+ ...assets && { assets }
4470
+ });
4471
+ }
3811
4472
  }
3812
4473
  if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
3813
4474
  const changes = fromOldestVtxo.filter(
@@ -3894,7 +4555,7 @@ var AssetManager = class extends ReadonlyAssetManager {
3894
4555
  const virtualCoins = await this.wallet.getVtxos({
3895
4556
  withRecoverable: false
3896
4557
  });
3897
- const controlAssetRef = params.controlAssetId ? chunkSPDNHPM4_cjs.AssetRef.fromId(chunkSPDNHPM4_cjs.AssetId.fromString(params.controlAssetId)) : null;
4558
+ const controlAssetRef = params.controlAssetId ? chunkX2EQLK4O_cjs.AssetRef.fromId(chunkX2EQLK4O_cjs.AssetId.fromString(params.controlAssetId)) : null;
3898
4559
  const coinSelection = selectVirtualCoins(virtualCoins, Number(this.wallet.dustAmount));
3899
4560
  let totalBtcSelected = 0n;
3900
4561
  const assetChanges = /* @__PURE__ */ new Map();
@@ -3907,8 +4568,8 @@ var AssetManager = class extends ReadonlyAssetManager {
3907
4568
  }
3908
4569
  }
3909
4570
  const groups = [];
3910
- const issuedAssetOutput = chunkSPDNHPM4_cjs.AssetOutput.create(0, params.amount);
3911
- const issuedAssetGroup = chunkSPDNHPM4_cjs.AssetGroup.create(
4571
+ const issuedAssetOutput = chunkX2EQLK4O_cjs.AssetOutput.create(0, params.amount);
4572
+ const issuedAssetGroup = chunkX2EQLK4O_cjs.AssetGroup.create(
3912
4573
  null,
3913
4574
  controlAssetRef,
3914
4575
  [],
@@ -3923,28 +4584,28 @@ var AssetManager = class extends ReadonlyAssetManager {
3923
4584
  for (const [inputIndex, assets] of assetInputs) {
3924
4585
  for (const asset of assets) {
3925
4586
  if (asset.assetId !== assetId) continue;
3926
- changeInputs.push(chunkSPDNHPM4_cjs.AssetInput.create(inputIndex, asset.amount));
4587
+ changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
3927
4588
  }
3928
4589
  }
3929
4590
  groups.push(
3930
- chunkSPDNHPM4_cjs.AssetGroup.create(
3931
- chunkSPDNHPM4_cjs.AssetId.fromString(assetId),
4591
+ chunkX2EQLK4O_cjs.AssetGroup.create(
4592
+ chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
3932
4593
  null,
3933
4594
  changeInputs,
3934
- [chunkSPDNHPM4_cjs.AssetOutput.create(0, amount)],
4595
+ [chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)],
3935
4596
  []
3936
4597
  )
3937
4598
  );
3938
4599
  }
3939
4600
  }
3940
4601
  const address = await this.wallet.getAddress();
3941
- const outputAddress = chunkWMIPYZSB_cjs.ArkAddress.decode(address);
4602
+ const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
3942
4603
  const outputs = [
3943
4604
  {
3944
4605
  script: outputAddress.pkScript,
3945
4606
  amount: BigInt(totalBtcSelected)
3946
4607
  },
3947
- Extension.create([chunkSPDNHPM4_cjs.Packet.create(groups)]).txOut()
4608
+ Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
3948
4609
  ];
3949
4610
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(
3950
4611
  coinSelection.inputs,
@@ -3952,7 +4613,7 @@ var AssetManager = class extends ReadonlyAssetManager {
3952
4613
  );
3953
4614
  return {
3954
4615
  arkTxId: arkTxid,
3955
- assetId: chunkSPDNHPM4_cjs.AssetId.create(arkTxid, 0).toString()
4616
+ assetId: chunkX2EQLK4O_cjs.AssetId.create(arkTxid, 0).toString()
3956
4617
  };
3957
4618
  }
3958
4619
  /**
@@ -4024,16 +4685,16 @@ var AssetManager = class extends ReadonlyAssetManager {
4024
4685
  for (const [inputIndex, assets] of assetInputs) {
4025
4686
  for (const asset of assets) {
4026
4687
  if (asset.assetId !== params.assetId) continue;
4027
- reissueInputs.push(chunkSPDNHPM4_cjs.AssetInput.create(inputIndex, asset.amount));
4688
+ reissueInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
4028
4689
  }
4029
4690
  }
4030
4691
  const totalAssetAmount = assetToReissueAmount + params.amount;
4031
- const reissueAssetIdObj = chunkSPDNHPM4_cjs.AssetId.fromString(params.assetId);
4032
- const reissueAssetGroup = chunkSPDNHPM4_cjs.AssetGroup.create(
4692
+ const reissueAssetIdObj = chunkX2EQLK4O_cjs.AssetId.fromString(params.assetId);
4693
+ const reissueAssetGroup = chunkX2EQLK4O_cjs.AssetGroup.create(
4033
4694
  reissueAssetIdObj,
4034
4695
  null,
4035
4696
  reissueInputs,
4036
- [chunkSPDNHPM4_cjs.AssetOutput.create(0, totalAssetAmount)],
4697
+ [chunkX2EQLK4O_cjs.AssetOutput.create(0, totalAssetAmount)],
4037
4698
  []
4038
4699
  );
4039
4700
  const groups = [reissueAssetGroup];
@@ -4042,27 +4703,27 @@ var AssetManager = class extends ReadonlyAssetManager {
4042
4703
  for (const [inputIndex, assets] of assetInputs) {
4043
4704
  for (const asset of assets) {
4044
4705
  if (asset.assetId !== assetId) continue;
4045
- changeInputs.push(chunkSPDNHPM4_cjs.AssetInput.create(inputIndex, asset.amount));
4706
+ changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
4046
4707
  }
4047
4708
  }
4048
4709
  groups.push(
4049
- chunkSPDNHPM4_cjs.AssetGroup.create(
4050
- chunkSPDNHPM4_cjs.AssetId.fromString(assetId),
4710
+ chunkX2EQLK4O_cjs.AssetGroup.create(
4711
+ chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
4051
4712
  null,
4052
4713
  changeInputs,
4053
- [chunkSPDNHPM4_cjs.AssetOutput.create(0, amount)],
4714
+ [chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)],
4054
4715
  []
4055
4716
  )
4056
4717
  );
4057
4718
  }
4058
4719
  const address = await this.wallet.getAddress();
4059
- const outputAddress = chunkWMIPYZSB_cjs.ArkAddress.decode(address);
4720
+ const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
4060
4721
  const outputs = [
4061
4722
  {
4062
4723
  script: outputAddress.pkScript,
4063
4724
  amount: BigInt(totalBtcSelected)
4064
4725
  },
4065
- Extension.create([chunkSPDNHPM4_cjs.Packet.create(groups)]).txOut()
4726
+ Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
4066
4727
  ];
4067
4728
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
4068
4729
  return arkTxid;
@@ -4129,27 +4790,27 @@ var AssetManager = class extends ReadonlyAssetManager {
4129
4790
  for (const [inputIndex, assets] of assetInputs) {
4130
4791
  for (const asset of assets) {
4131
4792
  if (asset.assetId !== assetId) continue;
4132
- changeInputs.push(chunkSPDNHPM4_cjs.AssetInput.create(inputIndex, asset.amount));
4793
+ changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
4133
4794
  }
4134
4795
  }
4135
4796
  groups.push(
4136
- chunkSPDNHPM4_cjs.AssetGroup.create(
4137
- chunkSPDNHPM4_cjs.AssetId.fromString(assetId),
4797
+ chunkX2EQLK4O_cjs.AssetGroup.create(
4798
+ chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
4138
4799
  null,
4139
4800
  changeInputs,
4140
- amount > 0n ? [chunkSPDNHPM4_cjs.AssetOutput.create(0, amount)] : [],
4801
+ amount > 0n ? [chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)] : [],
4141
4802
  []
4142
4803
  )
4143
4804
  );
4144
4805
  }
4145
4806
  const address = await this.wallet.getAddress();
4146
- const outputAddress = chunkWMIPYZSB_cjs.ArkAddress.decode(address);
4807
+ const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
4147
4808
  const outputs = [
4148
4809
  {
4149
4810
  script: outputAddress.pkScript,
4150
4811
  amount: BigInt(totalBtcSelected)
4151
4812
  },
4152
- Extension.create([chunkSPDNHPM4_cjs.Packet.create(groups)]).txOut()
4813
+ Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
4153
4814
  ];
4154
4815
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
4155
4816
  return arkTxid;
@@ -4174,7 +4835,7 @@ function castMetadata(metadata) {
4174
4835
  } else {
4175
4836
  throw new Error("Invalid metadata value type");
4176
4837
  }
4177
- md.push(chunkSPDNHPM4_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
4838
+ md.push(chunkX2EQLK4O_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
4178
4839
  }
4179
4840
  return md;
4180
4841
  }
@@ -4192,7 +4853,7 @@ var DelegateManagerImpl = class {
4192
4853
  if (vtxos.length === 0) {
4193
4854
  return { delegated: [], failed: [] };
4194
4855
  }
4195
- const destinationScript = chunkWMIPYZSB_cjs.ArkAddress.decode(destination).pkScript;
4856
+ const destinationScript = chunkCMPJR3HS_cjs.ArkAddress.decode(destination).pkScript;
4196
4857
  const arkInfo = await this.arkInfoProvider.getInfo();
4197
4858
  const delegateInfo = await this.delegateProvider.getDelegateInfo();
4198
4859
  const eligible = vtxos.filter(
@@ -4340,7 +5001,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
4340
5001
  const delegateFee = BigInt(Number(fee));
4341
5002
  if (delegateFee > 0n) {
4342
5003
  outputs.push({
4343
- script: chunkWMIPYZSB_cjs.ArkAddress.decode(delegateAddress).pkScript,
5004
+ script: chunkCMPJR3HS_cjs.ArkAddress.decode(delegateAddress).pkScript,
4344
5005
  amount: delegateFee
4345
5006
  });
4346
5007
  }
@@ -4373,7 +5034,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
4373
5034
  destinationScript
4374
5035
  );
4375
5036
  const forfeitOutputScript = btcSigner.OutScript.encode(
4376
- btcSigner.Address(chunkWMIPYZSB_cjs.getNetwork(network)).decode(forfeitAddress)
5037
+ btcSigner.Address(chunkCMPJR3HS_cjs.getNetwork(network)).decode(forfeitAddress)
4377
5038
  );
4378
5039
  const forfeits = await Promise.all(
4379
5040
  vtxos.filter((v) => !isRecoverable(v)).map(async (coin) => {
@@ -4401,7 +5062,7 @@ async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, for
4401
5062
  index: input.vout,
4402
5063
  witnessUtxo: {
4403
5064
  amount: BigInt(input.value),
4404
- script: chunkWMIPYZSB_cjs.VtxoScript.decode(input.tapTree).pkScript
5065
+ script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
4405
5066
  },
4406
5067
  sighashType: btcSigner.SigHash.ALL_ANYONECANPAY,
4407
5068
  tapLeafScript: [delegateTapLeaf]
@@ -4459,7 +5120,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
4459
5120
  expire_at: 0,
4460
5121
  cosigners_public_keys: cosignerPubKeys
4461
5122
  };
4462
- const proof = chunkSPDNHPM4_cjs.Intent.create(message, coins, outputs);
5123
+ const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, outputs);
4463
5124
  const signedProof = await identity.sign(proof);
4464
5125
  return {
4465
5126
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -4477,10 +5138,10 @@ function getDayTimestamp(timestamp) {
4477
5138
  function findDelegateTapLeaf(vtxo, delegatePubkey) {
4478
5139
  if (!vtxo.tapTree) return void 0;
4479
5140
  const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
4480
- const vtxoScript = chunkWMIPYZSB_cjs.VtxoScript.decode(vtxo.tapTree);
5141
+ const vtxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree);
4481
5142
  return vtxoScript.leaves.find((tapLeaf) => {
4482
- const arkTapscript = chunkWMIPYZSB_cjs.decodeTapscript(chunkWMIPYZSB_cjs.scriptFromTapLeafScript(tapLeaf));
4483
- if (!chunkWMIPYZSB_cjs.MultisigTapscript.is(arkTapscript)) return false;
5143
+ const arkTapscript = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(tapLeaf));
5144
+ if (!chunkCMPJR3HS_cjs.MultisigTapscript.is(arkTapscript)) return false;
4484
5145
  return arkTapscript.params.pubkeys.map(base.hex.encode).includes(pk);
4485
5146
  });
4486
5147
  }
@@ -4684,7 +5345,7 @@ var InMemoryContractRepository = class {
4684
5345
  }
4685
5346
  };
4686
5347
  function scriptFromArkAddress(address) {
4687
- return base.hex.encode(chunkWMIPYZSB_cjs.ArkAddress.decode(address).pkScript);
5348
+ return base.hex.encode(chunkCMPJR3HS_cjs.ArkAddress.decode(address).pkScript);
4688
5349
  }
4689
5350
 
4690
5351
  // src/repositories/indexedDB/schema.ts
@@ -5980,6 +6641,16 @@ function isDiscoverable(handler) {
5980
6641
  }
5981
6642
 
5982
6643
  // src/contracts/contractWatcher.ts
6644
+ function computeReconnectDelay(attempt, baseMs, maxMs) {
6645
+ return Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
6646
+ }
6647
+ var DEFAULT_CONTRACT_WATCHER_CONFIG = {
6648
+ failsafePollIntervalMs: 2e4,
6649
+ reconnectDelayMs: 1e3,
6650
+ maxReconnectDelayMs: 5e3,
6651
+ maxReconnectAttempts: 0
6652
+ // unlimited
6653
+ };
5983
6654
  var ContractWatcher = class {
5984
6655
  config;
5985
6656
  contracts = /* @__PURE__ */ new Map();
@@ -5999,14 +6670,7 @@ var ContractWatcher = class {
5999
6670
  */
6000
6671
  constructor(config) {
6001
6672
  this.config = {
6002
- failsafePollIntervalMs: 6e4,
6003
- // 1 minute
6004
- reconnectDelayMs: 1e3,
6005
- // 1 second
6006
- maxReconnectDelayMs: 3e4,
6007
- // 30 seconds
6008
- maxReconnectAttempts: 0,
6009
- // unlimited
6673
+ ...DEFAULT_CONTRACT_WATCHER_CONFIG,
6010
6674
  ...config
6011
6675
  };
6012
6676
  }
@@ -6199,7 +6863,7 @@ var ContractWatcher = class {
6199
6863
  this.connectionState = "connected";
6200
6864
  this.reconnectAttempts = 0;
6201
6865
  this.listenLoop().catch((e) => {
6202
- if (chunkSPDNHPM4_cjs.isEventSourceError(e)) {
6866
+ if (chunkX2EQLK4O_cjs.isEventSourceError(e)) {
6203
6867
  console.debug("ContractWatcher subscription disconnected; reconnecting");
6204
6868
  } else {
6205
6869
  console.error(e);
@@ -6234,8 +6898,9 @@ var ContractWatcher = class {
6234
6898
  }
6235
6899
  this.connectionState = "reconnecting";
6236
6900
  this.reconnectAttempts++;
6237
- const delay = Math.min(
6238
- this.config.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),
6901
+ const delay = computeReconnectDelay(
6902
+ this.reconnectAttempts,
6903
+ this.config.reconnectDelayMs,
6239
6904
  this.config.maxReconnectDelayMs
6240
6905
  );
6241
6906
  this.reconnectTimeoutId = setTimeout(() => {
@@ -6537,8 +7202,11 @@ function cursorCutoff(requestStartedAt) {
6537
7202
  }
6538
7203
 
6539
7204
  // src/contracts/contractManager.ts
6540
- var DEFAULT_PAGE_SIZE = 500;
7205
+ function areCoalescibleContractTypes(a, b) {
7206
+ return a === "default" && b === "boarding" || a === "boarding" && b === "default";
7207
+ }
6541
7208
  var SCAN_MAX_INDEX = 1e4;
7209
+ var DEFAULT_SCAN_BATCH = 10;
6542
7210
  var ContractManager = class _ContractManager {
6543
7211
  config;
6544
7212
  watcher;
@@ -6639,7 +7307,7 @@ var ContractManager = class _ContractManager {
6639
7307
  * `persisted` is `true`.
6640
7308
  */
6641
7309
  async upsertContract(params) {
6642
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(params.type);
7310
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(params.type);
6643
7311
  if (!handler) {
6644
7312
  throw new Error(`No handler registered for contract type '${params.type}'`);
6645
7313
  }
@@ -6662,8 +7330,11 @@ var ContractManager = class _ContractManager {
6662
7330
  const [existing] = await this.getContracts({ script: params.script });
6663
7331
  if (existing) {
6664
7332
  if (existing.type === params.type) return { contract: existing, persisted: false };
7333
+ if (areCoalescibleContractTypes(existing.type, params.type)) {
7334
+ return { contract: existing, persisted: false };
7335
+ }
6665
7336
  throw new Error(
6666
- `Contract with script ${params.script} already exists with with type ${existing.type}.`
7337
+ `Contract with script ${params.script} already exists with type ${existing.type}.`
6667
7338
  );
6668
7339
  }
6669
7340
  const contract = {
@@ -6692,6 +7363,19 @@ var ContractManager = class _ContractManager {
6692
7363
  * other handler hit it).
6693
7364
  * - `persistAndWatchContract` rejecting is operational/fatal and
6694
7365
  * propagates (only `discoverAt` is guarded).
7366
+ * - Within an index the handler probes run concurrently (independent
7367
+ * network reads); their hits are persisted sequentially in
7368
+ * `discoverables` order to preserve the first-wins collision tie-break.
7369
+ * - Indices are probed `batchSize` at a time (a second concurrency layer
7370
+ * over the per-index probes), but each window is CAPPED to
7371
+ * `gapLimit - unused` indices — the most a serial scan could still reach
7372
+ * before the gap window is guaranteed to close. So every index probed in
7373
+ * a window is one a one-index-at-a-time scan would also reach: nothing is
7374
+ * over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
7375
+ * invoked on exactly the same index set. The window's hits are still
7376
+ * processed strictly in ascending index order, so the discovered set,
7377
+ * persisted rows, `lastIndexUsed`, and `handlerErrors` are byte-for-byte
7378
+ * identical to the serial path — only the wall-clock differs.
6695
7379
  */
6696
7380
  async scanContracts(opts) {
6697
7381
  const gapLimit = opts.gapLimit ?? 20;
@@ -6700,35 +7384,69 @@ var ContractManager = class _ContractManager {
6700
7384
  `scanContracts: gapLimit must be a positive integer (got ${String(opts.gapLimit)})`
6701
7385
  );
6702
7386
  }
6703
- const discoverables = chunkGYSK5R57_cjs.contractHandlers.getRegisteredTypes().map((t) => chunkGYSK5R57_cjs.contractHandlers.get(t)).filter(isDiscoverable);
7387
+ const batchSize = opts.batchSize ?? DEFAULT_SCAN_BATCH;
7388
+ if (!Number.isInteger(batchSize) || batchSize <= 0) {
7389
+ throw new Error(
7390
+ `scanContracts: batchSize must be a positive integer (got ${String(opts.batchSize)})`
7391
+ );
7392
+ }
7393
+ const registered = chunkGUTKJMSF_cjs.contractHandlers.getRegisteredTypes().map((t) => chunkGUTKJMSF_cjs.contractHandlers.get(t)).filter(isDiscoverable);
7394
+ const discoverables = [
7395
+ ...registered.filter((h) => h.type === "boarding"),
7396
+ ...registered.filter((h) => h.type !== "boarding")
7397
+ ];
6704
7398
  const maxIdx = opts.hd ? SCAN_MAX_INDEX : 0;
6705
7399
  const handlerErrors = [];
6706
7400
  let lastIndexUsed = -1;
6707
7401
  let unused = 0;
6708
7402
  let i = 0;
7403
+ const probeIndex = async (index) => {
7404
+ const descriptor = opts.materialize(index);
7405
+ return Promise.all(
7406
+ discoverables.map(async (h) => {
7407
+ try {
7408
+ return {
7409
+ ok: true,
7410
+ found: await h.discoverAt(index, descriptor, opts.deps)
7411
+ };
7412
+ } catch (error) {
7413
+ return { ok: false, error };
7414
+ }
7415
+ })
7416
+ );
7417
+ };
6709
7418
  while (i <= maxIdx && unused < gapLimit) {
6710
- const descriptor = opts.materialize(i);
6711
- let hitAtThisIndex = false;
6712
- for (const h of discoverables) {
6713
- let found;
6714
- try {
6715
- found = await h.discoverAt(i, descriptor, opts.deps);
6716
- } catch (error) {
6717
- handlerErrors.push({ handler: h.type, index: i, error });
6718
- continue;
7419
+ const windowEnd = Math.min(maxIdx, i + Math.min(batchSize, gapLimit - unused) - 1);
7420
+ const windowIndices = [];
7421
+ for (let idx = i; idx <= windowEnd; idx++) windowIndices.push(idx);
7422
+ const windowProbes = await Promise.all(windowIndices.map(probeIndex));
7423
+ for (let w = 0; w < windowIndices.length; w++) {
7424
+ const index = windowIndices[w];
7425
+ const probes = windowProbes[w];
7426
+ let hitAtThisIndex = false;
7427
+ for (let h = 0; h < discoverables.length; h++) {
7428
+ const probe = probes[h];
7429
+ if (!probe.ok) {
7430
+ handlerErrors.push({
7431
+ handler: discoverables[h].type,
7432
+ index,
7433
+ error: probe.error
7434
+ });
7435
+ continue;
7436
+ }
7437
+ for (const c of probe.found) {
7438
+ await this.persistAndWatchContract(c);
7439
+ hitAtThisIndex = true;
7440
+ }
6719
7441
  }
6720
- for (const c of found) {
6721
- await this.persistAndWatchContract(c);
6722
- hitAtThisIndex = true;
7442
+ if (hitAtThisIndex) {
7443
+ lastIndexUsed = index;
7444
+ unused = 0;
7445
+ } else {
7446
+ unused += 1;
6723
7447
  }
6724
7448
  }
6725
- if (hitAtThisIndex) {
6726
- lastIndexUsed = i;
6727
- unused = 0;
6728
- } else {
6729
- unused += 1;
6730
- }
6731
- i += 1;
7449
+ i = windowEnd + 1;
6732
7450
  }
6733
7451
  if (opts.hd && i > maxIdx && unused < gapLimit) {
6734
7452
  throw new Error(
@@ -6856,7 +7574,7 @@ var ContractManager = class _ContractManager {
6856
7574
  const { contractScript, collaborative = true, walletPubKey, vtxo } = options;
6857
7575
  const [contract] = await this.getContracts({ script: contractScript });
6858
7576
  if (!contract) return [];
6859
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(contract.type);
7577
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
6860
7578
  if (!handler) return [];
6861
7579
  const script = handler.createScript(contract.params);
6862
7580
  const context = {
@@ -6876,7 +7594,7 @@ var ContractManager = class _ContractManager {
6876
7594
  const { contractScript, collaborative = true, walletPubKey } = options;
6877
7595
  const [contract] = await this.getContracts({ script: contractScript });
6878
7596
  if (!contract) return [];
6879
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(contract.type);
7597
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
6880
7598
  if (!handler) return [];
6881
7599
  const script = handler.createScript(contract.params);
6882
7600
  const context = {
@@ -7110,7 +7828,7 @@ var ContractManager = class _ContractManager {
7110
7828
  }
7111
7829
  return result;
7112
7830
  }
7113
- async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
7831
+ async fetchContractVtxosBulk(contracts, pageSize = chunkGUTKJMSF_cjs.DEFAULT_PAGE_SIZE, syncWindow) {
7114
7832
  if (contracts.length === 0) {
7115
7833
  return /* @__PURE__ */ new Map();
7116
7834
  }
@@ -7286,7 +8004,7 @@ var HDDescriptorProvider = class _HDDescriptorProvider {
7286
8004
  */
7287
8005
  materializeDescriptorAt(index) {
7288
8006
  const descriptor = this.identity.descriptor;
7289
- const network = chunkGYSK5R57_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
8007
+ const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
7290
8008
  const expansion = descriptorsScure.expand({ descriptor, network, index });
7291
8009
  const keyInfo = expansion.expansionMap?.["@0"];
7292
8010
  if (!keyInfo?.keyExpression) {
@@ -7429,12 +8147,13 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7429
8147
  const provider = await resolveDescriptorProvider(config, setup.walletRepository);
7430
8148
  if (!provider) return void 0;
7431
8149
  const allowSilentFallback = (config.walletMode ?? "auto") === "auto";
7432
- const expectedContractType = setup.offchainTapscript instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script ? "delegate" : "default";
8150
+ const expectedContractType = setup.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? "delegate" : "default";
7433
8151
  const factoryOpts = {
7434
8152
  walletRepository: setup.walletRepository,
7435
8153
  contractRepository: setup.contractRepository,
7436
8154
  serverPubKey: setup.serverPubKey,
7437
- expectedContractType
8155
+ expectedContractType,
8156
+ baselineReceivePubKey: setup.offchainTapscript.options.pubKey
7438
8157
  };
7439
8158
  let boot;
7440
8159
  try {
@@ -7479,14 +8198,17 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7479
8198
  receivePubkey: existing.pubKey
7480
8199
  };
7481
8200
  }
7482
- let descriptor;
7483
- if (hasPeekableDescriptor(provider)) {
7484
- descriptor = await provider.getCurrentSigningDescriptor();
8201
+ const current = hasPeekableDescriptor(provider) ? await provider.getCurrentSigningDescriptor() : void 0;
8202
+ if (current === void 0) {
8203
+ const descriptor = await provider.getNextSigningDescriptor();
8204
+ return {
8205
+ rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
8206
+ receivePubkey: deriveLeafPubkey(descriptor)
8207
+ };
7485
8208
  }
7486
- descriptor ??= await provider.getNextSigningDescriptor();
7487
8209
  return {
7488
8210
  rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
7489
- receivePubkey: deriveLeafPubkey(descriptor)
8211
+ receivePubkey: opts.baselineReceivePubKey ?? deriveLeafPubkey(current)
7490
8212
  };
7491
8213
  }
7492
8214
  /**
@@ -7549,6 +8271,22 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7549
8271
  async drain() {
7550
8272
  await this.chain.catch(() => void 0);
7551
8273
  }
8274
+ /**
8275
+ * Run `fn` on the rotator's serialization chain, so it cannot interleave
8276
+ * with a receive `rotate()`. Used by {@link Wallet.rotateServerSigner} to
8277
+ * serialize server-signer rotation against HD receive rotation: both
8278
+ * rebuild and swap `offchainTapscript`, so running them concurrently could
8279
+ * tear the wallet's visible receive state. The chain keeps advancing even
8280
+ * if `fn` rejects (its own caller still sees the rejection).
8281
+ */
8282
+ runExclusive(fn) {
8283
+ const run = this.chain.catch(() => void 0).then(fn);
8284
+ this.chain = run.then(
8285
+ () => void 0,
8286
+ () => void 0
8287
+ );
8288
+ return run;
8289
+ }
7552
8290
  /**
7553
8291
  * Tear down the subscription first so no late `vtxo_received` event
7554
8292
  * can queue work on a disposing wallet, then drain any in-flight
@@ -7592,7 +8330,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7592
8330
  const newAddress = newTapscript.address(wallet.network.hrp, wallet.arkServerPublicKey).encode();
7593
8331
  const manager = await wallet.getContractManager();
7594
8332
  const csvTimelock = newTapscript.options.csvTimelock;
7595
- const csvTimelockStr = chunkWMIPYZSB_cjs.timelockToSequence(csvTimelock).toString();
8333
+ const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
7596
8334
  const serverPubKeyHex = base.hex.encode(newTapscript.options.serverPubKey);
7597
8335
  const baseParams = {
7598
8336
  script: newScript,
@@ -7606,11 +8344,11 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7606
8344
  // produce unsigned PSBTs that the server rejects with
7607
8345
  // `INVALID_PSBT_INPUT (5): missing tapscript spend sig`.
7608
8346
  metadata: {
7609
- source: chunkGYSK5R57_cjs.WALLET_RECEIVE_SOURCE,
8347
+ source: chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE,
7610
8348
  signingDescriptor: descriptor
7611
8349
  }
7612
8350
  };
7613
- if (newTapscript instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script) {
8351
+ if (newTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
7614
8352
  await manager.createContract({
7615
8353
  ...baseParams,
7616
8354
  type: "delegate",
@@ -7642,7 +8380,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7642
8380
  };
7643
8381
  function deriveLeafPubkey(descriptor) {
7644
8382
  try {
7645
- return chunkGYSK5R57_cjs.deriveDescriptorLeafPubKey(descriptor);
8383
+ return chunkGUTKJMSF_cjs.deriveDescriptorLeafPubKey(descriptor);
7646
8384
  } catch (e) {
7647
8385
  throw new NonRangeableDescriptorError(
7648
8386
  "Cannot derive leaf pubkey: descriptor is not a materialized, parsable tr(...) shape.",
@@ -7651,10 +8389,10 @@ function deriveLeafPubkey(descriptor) {
7651
8389
  }
7652
8390
  }
7653
8391
  function rebuildTapscript(current, pubKey) {
7654
- if (current instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script) {
7655
- return new chunkGYSK5R57_cjs.DelegateVtxo.Script({ ...current.options, pubKey });
8392
+ if (current instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
8393
+ return new chunkGUTKJMSF_cjs.DelegateVtxo.Script({ ...current.options, pubKey });
7656
8394
  }
7657
- return new chunkGYSK5R57_cjs.DefaultVtxo.Script({ ...current.options, pubKey });
8395
+ return new chunkGUTKJMSF_cjs.DefaultVtxo.Script({ ...current.options, pubKey });
7658
8396
  }
7659
8397
  async function pickActiveReceive(contractRepository, serverPubKey, expectedType) {
7660
8398
  const candidates = await contractRepository.getContracts({
@@ -7663,7 +8401,7 @@ async function pickActiveReceive(contractRepository, serverPubKey, expectedType)
7663
8401
  });
7664
8402
  const serverPubKeyHex = base.hex.encode(serverPubKey);
7665
8403
  const matching = candidates.filter(
7666
- (c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === chunkGYSK5R57_cjs.WALLET_RECEIVE_SOURCE
8404
+ (c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE
7667
8405
  ).sort((a, b) => {
7668
8406
  if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt;
7669
8407
  return signingDescriptorIndex(b.metadata?.signingDescriptor) - signingDescriptorIndex(a.metadata?.signingDescriptor);
@@ -7719,7 +8457,7 @@ var DescriptorSigningProviderMissingError = class extends Error {
7719
8457
  };
7720
8458
 
7721
8459
  // src/wallet/inputSignerRouter.ts
7722
- var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate"]);
8460
+ var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate", "boarding"]);
7723
8461
  var InputSignerRouter = class {
7724
8462
  constructor(deps) {
7725
8463
  this.deps = deps;
@@ -7837,12 +8575,12 @@ var InputSignerRouter = class {
7837
8575
  };
7838
8576
 
7839
8577
  // src/wallet/wallet.ts
7840
- var getArkadeServerUrl = ({ arkServerUrl }) => arkServerUrl || chunkWMIPYZSB_cjs.DEFAULT_ARKADE_SERVER_URL;
8578
+ var getArkadeServerUrl = ({ arkServerUrl }) => arkServerUrl || chunkCMPJR3HS_cjs.DEFAULT_ARKADE_SERVER_URL;
7841
8579
  function intentProofJobs(coins) {
7842
8580
  if (coins.length === 0) return [];
7843
8581
  const coinJobs = coins.map((coin, i) => ({
7844
8582
  index: i + 1,
7845
- lookupScript: chunkWMIPYZSB_cjs.VtxoScript.decode(coin.tapTree).pkScript
8583
+ lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(coin.tapTree).pkScript
7846
8584
  }));
7847
8585
  return [{ index: 0, lookupScript: coinJobs[0].lookupScript }, ...coinJobs];
7848
8586
  }
@@ -7851,6 +8589,11 @@ function extractArkProviderUrl(provider) {
7851
8589
  return typeof serverUrl === "string" && serverUrl.length > 0 ? serverUrl : void 0;
7852
8590
  }
7853
8591
  var MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
8592
+ function toXOnlyPubKey(pubkey) {
8593
+ if (pubkey.length === 33) return pubkey.slice(1);
8594
+ if (pubkey.length === 32) return pubkey;
8595
+ throw new Error(`invalid signer pubkey length: expected 32 or 33, got ${pubkey.length}`);
8596
+ }
7854
8597
  function delayToTimelock(delay) {
7855
8598
  return {
7856
8599
  value: delay,
@@ -7861,27 +8604,37 @@ function dedupeTimelocks(timelocks) {
7861
8604
  const seen = /* @__PURE__ */ new Set();
7862
8605
  const deduped = [];
7863
8606
  for (const timelock of timelocks) {
7864
- const sequence = chunkWMIPYZSB_cjs.timelockToSequence(timelock).toString();
8607
+ const sequence = chunkCMPJR3HS_cjs.timelockToSequence(timelock).toString();
7865
8608
  if (seen.has(sequence)) continue;
7866
8609
  seen.add(sequence);
7867
8610
  deduped.push(timelock);
7868
8611
  }
7869
8612
  return deduped;
7870
8613
  }
7871
- function areSameScriptBaselineTypesCompatible(existingType, requestedType) {
7872
- if (existingType === requestedType) return true;
7873
- return existingType === "default" && requestedType === "boarding" || existingType === "boarding" && requestedType === "default";
7874
- }
7875
8614
  async function ensureWalletContract(manager, params) {
7876
- const [existing] = await manager.getContracts({ script: params.script });
7877
- if (existing && existing.type !== params.type && areSameScriptBaselineTypesCompatible(existing.type, params.type)) {
7878
- if (params.type === "default" && existing.type === "boarding") {
7879
- await manager.updateContract(params.script, { type: "default" });
7880
- }
7881
- return;
7882
- }
7883
8615
  await manager.createContract(params);
7884
8616
  }
8617
+ async function resolveBoardingBootTapscript(contractRepository, serverPubKey, baseline) {
8618
+ const serverPubKeyHex = base.hex.encode(serverPubKey);
8619
+ const candidates = await contractRepository.getContracts({
8620
+ type: ["boarding"],
8621
+ state: "active"
8622
+ });
8623
+ const newest = candidates.filter(
8624
+ (c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE
8625
+ ).sort((a, b) => {
8626
+ if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt;
8627
+ return signingDescriptorIndex(b.metadata?.signingDescriptor) - signingDescriptorIndex(a.metadata?.signingDescriptor);
8628
+ })[0];
8629
+ if (!newest?.params.pubKey) return baseline;
8630
+ try {
8631
+ const pubKey = base.hex.decode(newest.params.pubKey);
8632
+ return new chunkGUTKJMSF_cjs.DefaultVtxo.Script({ ...baseline.options, pubKey });
8633
+ } catch (e) {
8634
+ console.warn("Skipping malformed boarding contract at boot", newest.script, e);
8635
+ return baseline;
8636
+ }
8637
+ }
7885
8638
  function hasToReadonly(identity) {
7886
8639
  return typeof identity === "object" && identity !== null && "toReadonly" in identity && typeof identity.toReadonly === "function";
7887
8640
  }
@@ -7891,8 +8644,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
7891
8644
  this.network = network;
7892
8645
  this.onchainProvider = onchainProvider;
7893
8646
  this.indexerProvider = indexerProvider;
7894
- this.arkServerPublicKey = arkServerPublicKey;
7895
- this.boardingTapscript = boardingTapscript;
7896
8647
  this.dustAmount = dustAmount;
7897
8648
  this.walletRepository = walletRepository;
7898
8649
  this.contractRepository = contractRepository;
@@ -7908,6 +8659,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
7908
8659
  }
7909
8660
  }
7910
8661
  this._offchainTapscript = offchainTapscript;
8662
+ this._boardingTapscript = boardingTapscript;
8663
+ this._arkServerPublicKey = arkServerPublicKey;
7911
8664
  this.watcherConfig = watcherConfig;
7912
8665
  this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
7913
8666
  this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
@@ -7916,7 +8669,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
7916
8669
  _contractManagerInitializing;
7917
8670
  watcherConfig;
7918
8671
  _assetManager;
7919
- _syncVtxosInflight;
7920
8672
  walletContractTimelocks;
7921
8673
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
7922
8674
  // from getVtxos() so concurrent callers (UI, VtxoManager auto-renewal,
@@ -7934,6 +8686,70 @@ var ReadonlyWallet = class _ReadonlyWallet {
7934
8686
  * {@link WalletReceiveRotator.rotate} is the sole intended caller of.
7935
8687
  */
7936
8688
  _offchainTapscript;
8689
+ /**
8690
+ * Backing field for the current boarding tapscript (the QR / onboarding
8691
+ * target). Read via the public `boardingTapscript` getter; written only
8692
+ * by {@link Wallet.setBoardingTapscriptForRotation}, the sanctioned
8693
+ * boarding-rotation write path (analogue of `_offchainTapscript`). It is
8694
+ * a *current value*, not a fixed setup constant, because per-derivation
8695
+ * boarding rotation (plan §6-II) swaps it when a fresh boarding address
8696
+ * is explicitly allocated. Static / `auto` wallets never rotate it, so
8697
+ * it stays the index-0 baseline for their lifetime.
8698
+ */
8699
+ _boardingTapscript;
8700
+ /**
8701
+ * Backing field for the active server signer (x-only, 32 bytes). Read via
8702
+ * the public {@link arkServerPublicKey} getter; written only by
8703
+ * {@link Wallet.setArkServerPublicKeyForRotation}, the sanctioned
8704
+ * server-signer rotation write path (analogue of `_offchainTapscript`). It
8705
+ * is a *current value*, not a fixed constructor constant, because
8706
+ * mid-session server-signer rotation (plan §4) swaps it when arkd rotates
8707
+ * its active signer. Wallets that never span a rotation keep their
8708
+ * construction-time snapshot for their lifetime.
8709
+ */
8710
+ _arkServerPublicKey;
8711
+ /**
8712
+ * x-only hex of the operator's deprecated signer keys (from
8713
+ * `ArkInfo.deprecatedSigners`), cached for the OFFLINE read/watch paths.
8714
+ * The boarding watch/history surfaces ({@link getBoardingAddresses},
8715
+ * {@link getBoardingTxs}) fan out over {current} ∪ this set so a deposit at
8716
+ * a boarding address minted under a now-rotated operator signer keeps being
8717
+ * watched. Refreshed from the server-info snapshot at construction (via the
8718
+ * create() factories) and on a detected signer change. Deliberately NOT
8719
+ * consulted by the spend path — {@link getBoardingUtxos} stays
8720
+ * current-signer-only (a deprecated-signer input in a plain settle() is
8721
+ * rejected; old-signer recovery goes through the migration API).
8722
+ */
8723
+ _deprecatedSigners = /* @__PURE__ */ new Map();
8724
+ /**
8725
+ * Refresh the cached deprecated-signer set from a fresh server-info
8726
+ * snapshot. Called by the create() factories at construction and by the
8727
+ * server-info-change handler mid-session. Lenient: a malformed deprecated
8728
+ * entry is skipped, never fatal to wallet creation.
8729
+ */
8730
+ refreshDeprecatedSigners(info) {
8731
+ const next = /* @__PURE__ */ new Map();
8732
+ for (const s of info.deprecatedSigners ?? []) {
8733
+ if (!s.pubkey) continue;
8734
+ try {
8735
+ next.set(toXOnlySignerHex(s.pubkey), s.cutoffDate ?? 0n);
8736
+ } catch (e) {
8737
+ console.warn("Skipping malformed deprecated signer pubkey", s.pubkey, e);
8738
+ }
8739
+ }
8740
+ this._deprecatedSigners = next;
8741
+ }
8742
+ /**
8743
+ * The signer set the boarding WATCH/HISTORY paths fan out over: the wallet's
8744
+ * current signer plus every cached deprecated signer. Distinct from the
8745
+ * spend path, which is current-signer-only.
8746
+ */
8747
+ watchedBoardingSigners() {
8748
+ return /* @__PURE__ */ new Set([
8749
+ toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey)),
8750
+ ...this._deprecatedSigners.keys()
8751
+ ]);
8752
+ }
7937
8753
  /**
7938
8754
  * Currently-active receive tapscript. Read-only from the outside;
7939
8755
  * mutated only via {@link Wallet.setOffchainTapscriptForRotation}
@@ -7943,15 +8759,71 @@ var ReadonlyWallet = class _ReadonlyWallet {
7943
8759
  return this._offchainTapscript;
7944
8760
  }
7945
8761
  /**
7946
- * Protected helper to set up shared wallet configuration.
7947
- * Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
8762
+ * The wallet's current active server signer (x-only, 32 bytes). Read-only
8763
+ * from the outside; mutated only via
8764
+ * {@link Wallet.setArkServerPublicKeyForRotation} during mid-session
8765
+ * server-signer rotation (plan §4). Single-valued for wallets that never
8766
+ * span a rotation.
7948
8767
  */
7949
- static async setupWalletConfig(config, pubKey) {
7950
- const arkadeServerUrl = getArkadeServerUrl(config);
7951
- const arkProvider = config.arkProvider || new chunkSPDNHPM4_cjs.RestArkProvider(arkadeServerUrl);
7952
- let indexerProvider = config.indexerProvider;
7953
- if (!indexerProvider) {
7954
- let indexerUrl = config.indexerUrl;
8768
+ get arkServerPublicKey() {
8769
+ return this._arkServerPublicKey;
8770
+ }
8771
+ /**
8772
+ * The wallet's current boarding tapscript (the on-chain onboarding
8773
+ * target). Read-only from the outside; mutated only via
8774
+ * {@link Wallet.setBoardingTapscriptForRotation} when a fresh boarding
8775
+ * address is explicitly allocated. Single-valued for static / `auto`
8776
+ * wallets.
8777
+ */
8778
+ get boardingTapscript() {
8779
+ return this._boardingTapscript;
8780
+ }
8781
+ /**
8782
+ * Listeners fired after the boarding tapscript rotates to a fresh index
8783
+ * (see {@link Wallet.setBoardingTapscriptForRotation}). A live
8784
+ * {@link notifyIncomingFunds} onchain watcher registers one so it can
8785
+ * re-subscribe to include the newly allocated boarding address within the
8786
+ * same session — without it, a deposit to the fresh address wouldn't fire
8787
+ * a notification until the watcher's next re-init. Always empty for
8788
+ * readonly / static / `auto` wallets, which never rotate boarding.
8789
+ */
8790
+ _boardingRotationListeners = /* @__PURE__ */ new Set();
8791
+ /**
8792
+ * Register a listener invoked synchronously after each boarding rotation.
8793
+ * Returns an unsubscribe function. Protected: only internal subscribers
8794
+ * (the incoming-funds watcher) participate.
8795
+ */
8796
+ onBoardingRotation(listener) {
8797
+ this._boardingRotationListeners.add(listener);
8798
+ return () => {
8799
+ this._boardingRotationListeners.delete(listener);
8800
+ };
8801
+ }
8802
+ /**
8803
+ * Notify boarding-rotation listeners. Called by the boarding-rotation
8804
+ * write path ({@link Wallet.setBoardingTapscriptForRotation}) once the new
8805
+ * tapscript is in place. A throwing listener is isolated so it can neither
8806
+ * break the rotation nor starve sibling listeners.
8807
+ */
8808
+ notifyBoardingRotation() {
8809
+ for (const listener of this._boardingRotationListeners) {
8810
+ try {
8811
+ listener();
8812
+ } catch (e) {
8813
+ console.warn("Boarding-rotation listener failed", e);
8814
+ }
8815
+ }
8816
+ }
8817
+ /**
8818
+ * Protected helper to set up shared wallet configuration.
8819
+ * Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
8820
+ */
8821
+ static async setupWalletConfig(config, pubKey) {
8822
+ const arkadeServerUrl = getArkadeServerUrl(config);
8823
+ const arkProvider = config.arkProvider || new chunkX2EQLK4O_cjs.RestArkProvider(arkadeServerUrl);
8824
+ let indexerProvider = config.indexerProvider;
8825
+ if (!indexerProvider) {
8826
+ let indexerUrl = config.indexerUrl;
7955
8827
  if (!indexerUrl) {
7956
8828
  if (config.arkProvider) {
7957
8829
  const derived = extractArkProviderUrl(config.arkProvider);
@@ -7965,10 +8837,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
7965
8837
  indexerUrl = arkadeServerUrl;
7966
8838
  }
7967
8839
  }
7968
- indexerProvider = new chunkSPDNHPM4_cjs.RestIndexerProvider(indexerUrl);
8840
+ indexerProvider = new chunkX2EQLK4O_cjs.RestIndexerProvider(indexerUrl);
7969
8841
  }
7970
8842
  const info = await arkProvider.getInfo();
7971
- const network = chunkWMIPYZSB_cjs.getNetwork(info.network);
8843
+ const network = chunkCMPJR3HS_cjs.getNetwork(info.network);
7972
8844
  if ("descriptor" in config.identity) {
7973
8845
  const descriptor = config.identity.descriptor;
7974
8846
  const identityIsMainnet = !descriptor.includes("tpub");
@@ -8015,11 +8887,11 @@ var ReadonlyWallet = class _ReadonlyWallet {
8015
8887
  serverPubKey,
8016
8888
  csvTimelock: exitTimelock
8017
8889
  };
8018
- const offchainTapscript = !delegatePubKey ? new chunkGYSK5R57_cjs.DefaultVtxo.Script(offchainOptions) : new chunkGYSK5R57_cjs.DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
8019
- const boardingTapscript = chunkGYSK5R57_cjs.BoardingContractHandler.createScript({
8890
+ const offchainTapscript = !delegatePubKey ? new chunkGUTKJMSF_cjs.DefaultVtxo.Script(offchainOptions) : new chunkGUTKJMSF_cjs.DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
8891
+ const boardingTapscript = chunkGUTKJMSF_cjs.BoardingContractHandler.createScript({
8020
8892
  pubKey: base.hex.encode(pubKey),
8021
8893
  serverPubKey: base.hex.encode(serverPubKey),
8022
- csvTimelock: chunkWMIPYZSB_cjs.timelockToSequence(boardingTimelock).toString()
8894
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingTimelock).toString()
8023
8895
  });
8024
8896
  const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
8025
8897
  const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
@@ -8054,7 +8926,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8054
8926
  throw new Error("Invalid configured public key");
8055
8927
  }
8056
8928
  const setup = await _ReadonlyWallet.setupWalletConfig(config, pubkey);
8057
- return new _ReadonlyWallet(
8929
+ const wallet = new _ReadonlyWallet(
8058
8930
  config.identity,
8059
8931
  setup.network,
8060
8932
  setup.onchainProvider,
@@ -8069,6 +8941,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
8069
8941
  config.watcherConfig,
8070
8942
  setup.walletContractTimelocks
8071
8943
  );
8944
+ wallet.refreshDeprecatedSigners(setup.info);
8945
+ return wallet;
8072
8946
  }
8073
8947
  get arkAddress() {
8074
8948
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -8092,10 +8966,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
8092
8966
  * Return the wallet's combined onchain and offchain balances.
8093
8967
  */
8094
8968
  async getBalance() {
8095
- const [boardingUtxos, vtxos] = await Promise.all([
8969
+ const [boardingUtxos, vtxos, pendingOutpoints] = await Promise.all([
8096
8970
  this.getBoardingUtxos(),
8097
- this.getVtxos()
8971
+ this.getVtxos(),
8972
+ this.pendingRecoveryOutpoints()
8098
8973
  ]);
8974
+ const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
8099
8975
  let confirmed = 0;
8100
8976
  let unconfirmed = 0;
8101
8977
  for (const utxo of boardingUtxos) {
@@ -8108,11 +8984,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
8108
8984
  let settled = 0;
8109
8985
  let preconfirmed = 0;
8110
8986
  let recoverable = 0;
8111
- settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled").reduce((sum, coin) => sum + coin.value, 0);
8112
- preconfirmed = vtxos.filter((coin) => coin.virtualStatus.state === "preconfirmed").reduce((sum, coin) => sum + coin.value, 0);
8987
+ let pendingRecovery = 0;
8988
+ settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
8989
+ preconfirmed = vtxos.filter(
8990
+ (coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
8991
+ ).reduce((sum, coin) => sum + coin.value, 0);
8113
8992
  recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
8993
+ pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
8114
8994
  const totalBoarding = confirmed + unconfirmed;
8115
- const totalOffchain = settled + preconfirmed + recoverable;
8995
+ const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
8116
8996
  const assetBalances = /* @__PURE__ */ new Map();
8117
8997
  for (const vtxo of vtxos) {
8118
8998
  if (!isSpendable(vtxo)) continue;
@@ -8137,6 +9017,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8137
9017
  preconfirmed,
8138
9018
  available: settled + preconfirmed,
8139
9019
  recoverable,
9020
+ pendingRecovery,
8140
9021
  total: totalBoarding + totalOffchain,
8141
9022
  assets
8142
9023
  };
@@ -8163,6 +9044,23 @@ var ReadonlyWallet = class _ReadonlyWallet {
8163
9044
  return !!(f.withUnrolled && vtxo.isUnrolled);
8164
9045
  });
8165
9046
  }
9047
+ /**
9048
+ * Outpoints of VTXOs whose deprecated signer is past its cutoff (EXPIRED) and
9049
+ * which have not yet been swept — unspendable until they recover. Offline:
9050
+ * classifies the repo's contracts against the cached signer set (active +
9051
+ * {@link _deprecatedSigners}, cutoffs included). Empty fast-path when no
9052
+ * signer is deprecated. Consumed by {@link getBalance} (the `pendingRecovery`
9053
+ * bucket) and the send coin-selection path so neither counts nor spends them.
9054
+ */
9055
+ async pendingRecoveryOutpoints() {
9056
+ if (this._deprecatedSigners.size === 0) return /* @__PURE__ */ new Set();
9057
+ const contractManager = await this.getContractManager();
9058
+ const contractsWithVtxos = await contractManager.getContractsWithVtxos();
9059
+ return selectPendingRecoveryOutpoints(contractsWithVtxos, {
9060
+ active: toXOnlySignerHex(base.hex.encode(this.offchainTapscript.options.serverPubKey)),
9061
+ deprecated: this._deprecatedSigners
9062
+ });
9063
+ }
8166
9064
  /**
8167
9065
  * Return wallet transaction history derived from Arkade state and boarding transactions.
8168
9066
  */
@@ -8182,43 +9080,59 @@ var ReadonlyWallet = class _ReadonlyWallet {
8182
9080
  await clearSyncCursor(this.walletRepository);
8183
9081
  }
8184
9082
  /**
8185
- * Build a transaction history view for the wallet's boarding address.
9083
+ * The on-chain (P2TR) addresses of every boarding tapscript this wallet
9084
+ * uses — the current address plus any historical rotated boarding
9085
+ * addresses. The aggregating boarding readers (history, notifications) fan
9086
+ * out over this set so deposits at previous boarding addresses are still
9087
+ * surfaced (plan §6-IV); {@link getBoardingAddress} stays single-valued.
9088
+ */
9089
+ async getBoardingAddresses() {
9090
+ const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
9091
+ return tapscripts.map((t) => t.onchainAddress(this.network));
9092
+ }
9093
+ /**
9094
+ * Build a transaction history view across the wallet's boarding addresses
9095
+ * (current + historical rotated; plan §6-IV.1).
8186
9096
  */
8187
9097
  async getBoardingTxs() {
8188
9098
  const utxos = [];
8189
9099
  const commitmentsToIgnore = /* @__PURE__ */ new Set();
8190
- const boardingAddress = await this.getBoardingAddress();
8191
- const txs = await this.onchainProvider.getTransactions(boardingAddress);
9100
+ const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
8192
9101
  const outspendCache = /* @__PURE__ */ new Map();
8193
- for (const tx of txs) {
8194
- for (let i = 0; i < tx.vout.length; i++) {
8195
- const vout = tx.vout[i];
8196
- if (vout.scriptpubkey_address === boardingAddress) {
8197
- let spentStatuses = outspendCache.get(tx.txid);
8198
- if (!spentStatuses) {
8199
- spentStatuses = await this.onchainProvider.getTxOutspends(tx.txid);
8200
- outspendCache.set(tx.txid, spentStatuses);
8201
- }
8202
- const spentStatus = spentStatuses[i];
8203
- if (spentStatus?.spent) {
8204
- commitmentsToIgnore.add(spentStatus.txid);
9102
+ for (const tapscript of tapscripts) {
9103
+ const boardingAddress = tapscript.onchainAddress(this.network);
9104
+ const scriptHex = base.hex.encode(tapscript.pkScript);
9105
+ const txs = await this.onchainProvider.getTransactions(boardingAddress);
9106
+ for (const tx of txs) {
9107
+ for (let i = 0; i < tx.vout.length; i++) {
9108
+ const vout = tx.vout[i];
9109
+ if (vout.scriptpubkey_address === boardingAddress) {
9110
+ let spentStatuses = outspendCache.get(tx.txid);
9111
+ if (!spentStatuses) {
9112
+ spentStatuses = await this.onchainProvider.getTxOutspends(tx.txid);
9113
+ outspendCache.set(tx.txid, spentStatuses);
9114
+ }
9115
+ const spentStatus = spentStatuses[i];
9116
+ if (spentStatus?.spent) {
9117
+ commitmentsToIgnore.add(spentStatus.txid);
9118
+ }
9119
+ utxos.push({
9120
+ txid: tx.txid,
9121
+ vout: i,
9122
+ value: Number(vout.value),
9123
+ status: {
9124
+ confirmed: tx.status.confirmed,
9125
+ block_time: tx.status.block_time
9126
+ },
9127
+ isUnrolled: true,
9128
+ virtualStatus: {
9129
+ state: spentStatus?.spent ? "spent" : "settled",
9130
+ commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
9131
+ },
9132
+ createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
9133
+ script: scriptHex
9134
+ });
8205
9135
  }
8206
- utxos.push({
8207
- txid: tx.txid,
8208
- vout: i,
8209
- value: Number(vout.value),
8210
- status: {
8211
- confirmed: tx.status.confirmed,
8212
- block_time: tx.status.block_time
8213
- },
8214
- isUnrolled: true,
8215
- virtualStatus: {
8216
- state: spentStatus?.spent ? "spent" : "settled",
8217
- commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
8218
- },
8219
- createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
8220
- script: base.hex.encode(this.boardingTapscript.pkScript)
8221
- });
8222
9136
  }
8223
9137
  }
8224
9138
  }
@@ -8248,48 +9162,176 @@ var ReadonlyWallet = class _ReadonlyWallet {
8248
9162
  };
8249
9163
  }
8250
9164
  /**
8251
- * Fetch and cache onchain inputs (UTXOs) received at the boarding address.
9165
+ * The set of boarding tapscripts whose on-chain UTXOs belong to this
9166
+ * wallet — the current display tapscript plus every historical boarding
9167
+ * address it has used. Under per-derivation rotation (plan §6-II) a wallet
9168
+ * can hold unspent boarding UTXOs at several addresses at once, so fund
9169
+ * discovery / spending must enumerate them all, not just the current one
9170
+ * (plan §6-III.1). Deduplicated by scriptPubKey.
9171
+ *
9172
+ * Always includes the index-0 baseline (identity x-only key), which covers
9173
+ * the degenerate equal-delay case where the index-0 boarding row is
9174
+ * coalesced onto a `default` row and so isn't a `boarding`-typed contract.
9175
+ *
9176
+ * @param allowedSigners - Optional set of x-only-hex server keys whose
9177
+ * persisted boarding rows are included. Defaults to `{current x-only
9178
+ * signer}`, preserving today's current-signer-only discovery (and the
9179
+ * foreign-ASP guard). The deprecated-signer migration path widens this to
9180
+ * reach old-signer boarding addresses. The index-0 baseline and the
9181
+ * current display tapscript are always included regardless of the set.
8252
9182
  */
8253
- async getBoardingUtxos() {
8254
- const boardingAddress = await this.getBoardingAddress();
8255
- const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
8256
- const utxos = boardingUtxos.map((utxo) => {
8257
- return extendCoin(this, utxo);
9183
+ async getBoardingTapscripts(allowedSigners) {
9184
+ const byScript = /* @__PURE__ */ new Map();
9185
+ const add = (s) => byScript.set(base.hex.encode(s.pkScript), s);
9186
+ const boardingCsv = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9187
+ add(
9188
+ new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9189
+ pubKey: await this.identity.xOnlyPublicKey(),
9190
+ serverPubKey: this.boardingTapscript.options.serverPubKey,
9191
+ csvTimelock: boardingCsv
9192
+ })
9193
+ );
9194
+ add(this.boardingTapscript);
9195
+ const serverPubKeyHex = base.hex.encode(this.boardingTapscript.options.serverPubKey);
9196
+ const allowed = allowedSigners ?? /* @__PURE__ */ new Set([toXOnlySignerHex(serverPubKeyHex)]);
9197
+ const boardingContracts = await this.contractRepository.getContracts({
9198
+ type: ["boarding"]
8258
9199
  });
8259
- await this.walletRepository.saveUtxos(boardingAddress, utxos);
8260
- return utxos;
9200
+ for (const c of boardingContracts) {
9201
+ if (!allowed.has(toXOnlySignerHex(c.params.serverPubKey))) continue;
9202
+ try {
9203
+ add(chunkGUTKJMSF_cjs.BoardingContractHandler.createScript(c.params));
9204
+ } catch (e) {
9205
+ console.warn("Skipping malformed boarding contract", c.script, e);
9206
+ }
9207
+ }
9208
+ return [...byScript.values()];
9209
+ }
9210
+ /**
9211
+ * Fetch and cache onchain inputs (UTXOs) received at the boarding addresses
9212
+ * of the given signer set, grouped per boarding address so the caller keeps
9213
+ * the address↔signer association that {@link ExtendedCoin} cannot carry
9214
+ * (it retains only the encoded leaves/tapTree the spend needs, not the
9215
+ * `DefaultVtxo.Script` and its `serverPubKey`/CSV delay).
9216
+ *
9217
+ * Per group it does exactly what {@link getBoardingUtxos} does per tapscript:
9218
+ * `getCoins` → {@link extendCoinWithTapscript} → `saveUtxos`. Offline-first:
9219
+ * it does not call `getInfo()`; the caller supplies the allowed signer set,
9220
+ * so the only network calls are the per-address `getCoins`.
9221
+ *
9222
+ * @param allowedSigners - x-only-hex server keys whose boarding addresses to
9223
+ * fetch (passed through to {@link getBoardingTapscripts}).
9224
+ */
9225
+ async getBoardingUtxosForSigners(allowedSigners) {
9226
+ const tapscripts = await this.getBoardingTapscripts(allowedSigners);
9227
+ const groups = [];
9228
+ for (const tapscript of tapscripts) {
9229
+ const address = tapscript.onchainAddress(this.network);
9230
+ const coins = await this.onchainProvider.getCoins(address);
9231
+ const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
9232
+ await this.walletRepository.saveUtxos(address, utxos);
9233
+ groups.push({
9234
+ tapscript,
9235
+ // Normalize so the group key matches the axis/contract x-only
9236
+ // form regardless of how the tapscript's key was stored.
9237
+ serverPubKey: toXOnlySignerHex(base.hex.encode(tapscript.options.serverPubKey)),
9238
+ // Per-row CSV delay decoded from THIS tapscript's exit leaf —
9239
+ // not the wallet's current boarding timelock, which a signer
9240
+ // rotation may have changed.
9241
+ csvTimelock: chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(tapscript.exitScript)).params.timelock,
9242
+ coins: utxos
9243
+ });
9244
+ }
9245
+ return groups;
9246
+ }
9247
+ /**
9248
+ * Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding
9249
+ * addresses — the current address plus any historical rotated boarding
9250
+ * addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is
9251
+ * annotated with the tapscript of the address it actually sits on, so the
9252
+ * spending path forfeits / exits it with the correct per-index leaves.
9253
+ *
9254
+ * Current-signer only: a flatten of {@link getBoardingUtxosForSigners} over
9255
+ * the wallet's current signer, so the two paths cannot drift. Old-signer
9256
+ * boarding recovery goes through the deprecated-signer migration API
9257
+ * instead (it would otherwise pull EXPIRED-signer inputs into a plain
9258
+ * `settle()` that the server must reject).
9259
+ */
9260
+ async getBoardingUtxos() {
9261
+ const currentOnly = /* @__PURE__ */ new Set([
9262
+ toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey))
9263
+ ]);
9264
+ const groups = await this.getBoardingUtxosForSigners(currentOnly);
9265
+ return groups.flatMap((g) => g.coins);
8261
9266
  }
8262
9267
  /**
8263
9268
  * Subscribe to onchain and offchain notifications for newly received funds.
8264
9269
  *
9270
+ * The onchain watcher tracks the full boarding-address set (current +
9271
+ * historical rotated). When boarding rotates *after* subscribing — e.g.
9272
+ * rotate-on-board allocates a fresh address via
9273
+ * {@link getNewBoardingAddress} — the watcher automatically re-subscribes
9274
+ * to widen its set, so a deposit to the new address fires a notification
9275
+ * within the same session (no watcher re-init required). The re-subscribe
9276
+ * is driven by {@link onBoardingRotation}; static / `auto` / readonly
9277
+ * wallets never rotate boarding, so it never fires for them.
9278
+ *
8265
9279
  * @param eventCallback - Callback invoked when matching funds are detected
8266
9280
  * @returns A function that stops the subscriptions
8267
9281
  */
8268
9282
  async notifyIncomingFunds(eventCallback) {
8269
9283
  const arkAddress = await this.getAddress();
8270
- const boardingAddress = await this.getBoardingAddress();
8271
9284
  let onchainStopFunc;
8272
9285
  let indexerStopFunc;
8273
- if (this.onchainProvider && boardingAddress) {
8274
- const findVoutOnTx = (tx) => {
8275
- return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
8276
- };
8277
- onchainStopFunc = await this.onchainProvider.watchAddresses(
8278
- [boardingAddress],
8279
- (txs) => {
8280
- const coins = txs.filter((tx) => findVoutOnTx(tx) !== -1).map((tx) => {
8281
- const { txid, status } = tx;
8282
- const vout = findVoutOnTx(tx);
8283
- const value = Number(tx.vout[vout].value);
8284
- return { txid, vout, value, status };
8285
- });
8286
- eventCallback({
8287
- type: "utxo",
8288
- coins
8289
- });
9286
+ let boardingRotationStopFunc;
9287
+ let stopped = false;
9288
+ let onchainChain = Promise.resolve();
9289
+ const subscribeOnchain = () => {
9290
+ onchainChain = onchainChain.then(async () => {
9291
+ if (stopped || !this.onchainProvider) return;
9292
+ const boardingAddresses = await this.getBoardingAddresses();
9293
+ if (boardingAddresses.length === 0) return;
9294
+ const boardingAddressSet = new Set(boardingAddresses);
9295
+ const previousStop = onchainStopFunc;
9296
+ const stop = await this.onchainProvider.watchAddresses(
9297
+ boardingAddresses,
9298
+ (txs) => {
9299
+ const coins = txs.flatMap((tx) => {
9300
+ const { txid, status } = tx;
9301
+ const matched = [];
9302
+ tx.vout.forEach((v, vout) => {
9303
+ if (boardingAddressSet.has(v.scriptpubkey_address)) {
9304
+ matched.push({
9305
+ txid,
9306
+ vout,
9307
+ value: Number(v.value),
9308
+ status
9309
+ });
9310
+ }
9311
+ });
9312
+ return matched;
9313
+ });
9314
+ eventCallback({
9315
+ type: "utxo",
9316
+ coins
9317
+ });
9318
+ }
9319
+ );
9320
+ if (stopped) {
9321
+ stop();
9322
+ return;
8290
9323
  }
8291
- );
8292
- }
9324
+ onchainStopFunc = stop;
9325
+ previousStop?.();
9326
+ }).catch((e) => {
9327
+ console.warn("Failed to (re)subscribe boarding-funds watcher", e);
9328
+ });
9329
+ return onchainChain;
9330
+ };
9331
+ boardingRotationStopFunc = this.onBoardingRotation(() => {
9332
+ void subscribeOnchain();
9333
+ });
9334
+ await subscribeOnchain();
8293
9335
  if (this.indexerProvider && arkAddress) {
8294
9336
  const cm = await this.getContractManager();
8295
9337
  let annotationQueue = Promise.resolve();
@@ -8318,7 +9360,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
8318
9360
  });
8319
9361
  }
8320
9362
  const stopFunc = () => {
9363
+ stopped = true;
9364
+ boardingRotationStopFunc?.();
8321
9365
  onchainStopFunc?.();
9366
+ onchainStopFunc = void 0;
8322
9367
  indexerStopFunc?.();
8323
9368
  };
8324
9369
  return stopFunc;
@@ -8359,7 +9404,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8359
9404
  });
8360
9405
  for (const contract of contracts) {
8361
9406
  if (map.has(contract.script)) continue;
8362
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(contract.type);
9407
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
8363
9408
  if (handler) {
8364
9409
  const script = handler.createScript(contract.params);
8365
9410
  map.set(contract.script, script);
@@ -8424,60 +9469,82 @@ var ReadonlyWallet = class _ReadonlyWallet {
8424
9469
  watcherConfig: this.watcherConfig
8425
9470
  });
8426
9471
  const baselinePubkey = await this.identity.xOnlyPublicKey();
8427
- for (const csvTimelock of this.walletContractTimelocks) {
8428
- const csvTimelockStr = chunkWMIPYZSB_cjs.timelockToSequence(csvTimelock).toString();
8429
- const defaultScript = new chunkGYSK5R57_cjs.DefaultVtxo.Script({
9472
+ const delegatePubKey = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
9473
+ const baselineSigners = [
9474
+ this.offchainTapscript.options.serverPubKey,
9475
+ ...[...this._deprecatedSigners.keys()].map((h) => base.hex.decode(h))
9476
+ ];
9477
+ const seenBaselineScripts = /* @__PURE__ */ new Set();
9478
+ for (const serverPubKey of baselineSigners) {
9479
+ for (const csvTimelock of this.walletContractTimelocks) {
9480
+ const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
9481
+ const defaultScript = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9482
+ pubKey: baselinePubkey,
9483
+ serverPubKey,
9484
+ csvTimelock
9485
+ });
9486
+ const defaultScriptHex = base.hex.encode(defaultScript.pkScript);
9487
+ if (!seenBaselineScripts.has(defaultScriptHex)) {
9488
+ seenBaselineScripts.add(defaultScriptHex);
9489
+ await ensureWalletContract(manager, {
9490
+ type: "default",
9491
+ params: {
9492
+ pubKey: base.hex.encode(defaultScript.options.pubKey),
9493
+ serverPubKey: base.hex.encode(serverPubKey),
9494
+ csvTimelock: csvTimelockStr
9495
+ },
9496
+ script: defaultScriptHex,
9497
+ address: defaultScript.address(this.network.hrp, serverPubKey).encode(),
9498
+ state: "active"
9499
+ });
9500
+ }
9501
+ if (delegatePubKey) {
9502
+ const delegateScript = new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
9503
+ pubKey: baselinePubkey,
9504
+ serverPubKey,
9505
+ delegatePubKey,
9506
+ csvTimelock
9507
+ });
9508
+ const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
9509
+ if (seenBaselineScripts.has(delegateScriptHex)) continue;
9510
+ seenBaselineScripts.add(delegateScriptHex);
9511
+ await manager.createContract({
9512
+ type: "delegate",
9513
+ params: {
9514
+ pubKey: base.hex.encode(delegateScript.options.pubKey),
9515
+ serverPubKey: base.hex.encode(serverPubKey),
9516
+ delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
9517
+ csvTimelock: csvTimelockStr
9518
+ },
9519
+ script: delegateScriptHex,
9520
+ address: delegateScript.address(this.network.hrp, serverPubKey).encode(),
9521
+ state: "active"
9522
+ });
9523
+ }
9524
+ }
9525
+ }
9526
+ const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9527
+ for (const serverPubKey of baselineSigners) {
9528
+ const baselineBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
8430
9529
  pubKey: baselinePubkey,
8431
- serverPubKey: this.offchainTapscript.options.serverPubKey,
8432
- csvTimelock
9530
+ serverPubKey,
9531
+ csvTimelock: boardingCsvTimelock
8433
9532
  });
8434
- const defaultScriptHex = base.hex.encode(defaultScript.pkScript);
9533
+ const boardingScriptHex = base.hex.encode(baselineBoarding.pkScript);
9534
+ if (seenBaselineScripts.has(boardingScriptHex)) continue;
9535
+ seenBaselineScripts.add(boardingScriptHex);
8435
9536
  await ensureWalletContract(manager, {
8436
- type: "default",
9537
+ type: "boarding",
8437
9538
  params: {
8438
- pubKey: base.hex.encode(defaultScript.options.pubKey),
8439
- serverPubKey: base.hex.encode(defaultScript.options.serverPubKey),
8440
- csvTimelock: csvTimelockStr
9539
+ pubKey: base.hex.encode(baselineBoarding.options.pubKey),
9540
+ serverPubKey: base.hex.encode(serverPubKey),
9541
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
8441
9542
  },
8442
- script: defaultScriptHex,
8443
- address: defaultScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
9543
+ script: boardingScriptHex,
9544
+ address: baselineBoarding.address(this.network.hrp, serverPubKey).encode(),
8444
9545
  state: "active"
8445
9546
  });
8446
- if (this.offchainTapscript instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script) {
8447
- const delegateScript = new chunkGYSK5R57_cjs.DelegateVtxo.Script({
8448
- pubKey: baselinePubkey,
8449
- serverPubKey: this.offchainTapscript.options.serverPubKey,
8450
- delegatePubKey: this.offchainTapscript.options.delegatePubKey,
8451
- csvTimelock
8452
- });
8453
- const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
8454
- await manager.createContract({
8455
- type: "delegate",
8456
- params: {
8457
- pubKey: base.hex.encode(delegateScript.options.pubKey),
8458
- serverPubKey: base.hex.encode(delegateScript.options.serverPubKey),
8459
- delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
8460
- csvTimelock: csvTimelockStr
8461
- },
8462
- script: delegateScriptHex,
8463
- address: delegateScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
8464
- state: "active"
8465
- });
8466
- }
8467
9547
  }
8468
- const boardingScriptHex = base.hex.encode(this.boardingTapscript.pkScript);
8469
- const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGYSK5R57_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
8470
- await ensureWalletContract(manager, {
8471
- type: "boarding",
8472
- params: {
8473
- pubKey: base.hex.encode(this.boardingTapscript.options.pubKey),
8474
- serverPubKey: base.hex.encode(this.boardingTapscript.options.serverPubKey),
8475
- csvTimelock: chunkWMIPYZSB_cjs.timelockToSequence(boardingCsvTimelock).toString()
8476
- },
8477
- script: boardingScriptHex,
8478
- address: this.boardingTapscript.address(this.network.hrp, this.arkServerPublicKey).encode(),
8479
- state: "active"
8480
- });
8481
9548
  return manager;
8482
9549
  }
8483
9550
  /** Dispose wallet-owned managers and release background resources. */
@@ -8510,7 +9577,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8510
9577
  walletContractTimelocks
8511
9578
  );
8512
9579
  this.arkProvider = arkProvider;
8513
- this.serverUnrollScript = serverUnrollScript;
8514
9580
  this.forfeitOutputScript = forfeitOutputScript;
8515
9581
  this.forfeitPubkey = forfeitPubkey;
8516
9582
  this.identity = identity;
@@ -8531,6 +9597,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8531
9597
  this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
8532
9598
  }
8533
9599
  this._delegateManager = delegateProvider ? new DelegateManagerImpl(delegateProvider, arkProvider, identity) : void 0;
9600
+ this._serverUnrollScript = serverUnrollScript;
8534
9601
  this._receiveRotator = receiveRotator;
8535
9602
  this._descriptorProvider = descriptorProvider;
8536
9603
  this._signerRouter = new InputSignerRouter({
@@ -8556,6 +9623,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8556
9623
  * the contract manager is up first.
8557
9624
  */
8558
9625
  _receiveRotator;
9626
+ /**
9627
+ * Unsubscribe handle for the arkProvider's `onServerInfoChanged` stream
9628
+ * (mid-session signer-rotation detection). Torn down in {@link dispose}.
9629
+ */
9630
+ _serverInfoUnsub;
9631
+ /**
9632
+ * Tail of the serialized {@link handleServerInfoChanged} chain. Each
9633
+ * `onServerInfoChanged` event chains onto it so handlers run one at a time,
9634
+ * and {@link dispose} awaits it so an in-flight re-derive/rotation settles
9635
+ * before the contract manager is torn down underneath it.
9636
+ */
9637
+ _serverInfoInFlight = Promise.resolve();
9638
+ /**
9639
+ * React to a mid-session server-info change (driven by the arkProvider's
9640
+ * `DIGEST_MISMATCH` detection). First refresh the cached deprecated-signer
9641
+ * set so the boarding WATCH path immediately widens to the just-deprecated
9642
+ * signer, then — only if the active signer actually changed — rotate the
9643
+ * wallet onto it via {@link rotateServerSigner} (re-deriving the offchain +
9644
+ * boarding display tapscripts and registering the current-signer rows).
9645
+ * Old-signer rows stay active, so existing funds remain watched. Failures
9646
+ * are logged, never thrown back into the provider's emit loop.
9647
+ */
9648
+ async handleServerInfoChanged(info) {
9649
+ this.refreshDeprecatedSigners(info);
9650
+ try {
9651
+ const newActive = toXOnlySignerHex(info.signerPubkey);
9652
+ const current = toXOnlySignerHex(base.hex.encode(this.arkServerPublicKey));
9653
+ if (newActive !== current) {
9654
+ await this.rotateServerSigner(
9655
+ base.hex.decode(info.signerPubkey),
9656
+ info.checkpointTapscript
9657
+ );
9658
+ }
9659
+ } catch (e) {
9660
+ console.warn("server-signer rotation on info change failed", e);
9661
+ }
9662
+ }
8559
9663
  _receiveRotatorInstalled = false;
8560
9664
  /**
8561
9665
  * Descriptor-aware signer used by {@link _signerRouter} to sign
@@ -8575,6 +9679,230 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8575
9679
  setOffchainTapscriptForRotation(tapscript) {
8576
9680
  this._offchainTapscript = tapscript;
8577
9681
  }
9682
+ /**
9683
+ * @internal Sole write path for `boardingTapscript` after construction.
9684
+ * Called by {@link Wallet.getNewBoardingAddress} once the rotated
9685
+ * boarding contract has been persisted. External code must treat
9686
+ * `boardingTapscript` as read-only.
9687
+ */
9688
+ setBoardingTapscriptForRotation(tapscript) {
9689
+ this._boardingTapscript = tapscript;
9690
+ this.notifyBoardingRotation();
9691
+ }
9692
+ /**
9693
+ * @internal Sole write path for `arkServerPublicKey` after construction.
9694
+ * Called by {@link Wallet.rotateServerSigner} once the rotated offchain and
9695
+ * boarding contract rows have been persisted. External code must treat
9696
+ * `arkServerPublicKey` as read-only.
9697
+ */
9698
+ setArkServerPublicKeyForRotation(serverPubKey) {
9699
+ this._arkServerPublicKey = serverPubKey;
9700
+ }
9701
+ /**
9702
+ * Output script for checkpoint transactions, decoded from the server's
9703
+ * `checkpointTapscript`. Server-controlled state: pinned at construction
9704
+ * and re-sourced from a fresh `ArkInfo` on server-signer rotation. Read it
9705
+ * through {@link serverUnrollScript}; write it only through
9706
+ * {@link setServerUnrollScriptForRotation}.
9707
+ */
9708
+ _serverUnrollScript;
9709
+ get serverUnrollScript() {
9710
+ return this._serverUnrollScript;
9711
+ }
9712
+ /**
9713
+ * @internal Sole write path for `serverUnrollScript` after construction.
9714
+ * Called by {@link Wallet._doRotateServerSigner} with the checkpoint script
9715
+ * sourced from the fresh `ArkInfo` that triggered the rotation, so the send
9716
+ * path builds checkpoints against the new server epoch. External code must
9717
+ * treat `serverUnrollScript` as read-only.
9718
+ */
9719
+ setServerUnrollScriptForRotation(script) {
9720
+ this._serverUnrollScript = script;
9721
+ }
9722
+ /**
9723
+ * Serializes {@link rotateServerSigner} for static / non-HD wallets (which
9724
+ * have no {@link WalletReceiveRotator} chain to ride). Coalesces concurrent
9725
+ * migration passes so two callers cannot both rebuild and swap the
9726
+ * tapscripts. HD wallets serialize on the rotator's chain instead, via
9727
+ * {@link WalletReceiveRotator.runExclusive}.
9728
+ */
9729
+ _serverRotationChain = Promise.resolve();
9730
+ /**
9731
+ * Allocate and return a *fresh* on-chain boarding address, rotating the
9732
+ * wallet's current boarding tapscript to a new HD index.
9733
+ *
9734
+ * This is the explicit boarding allocator — the analogue of dotnet's
9735
+ * `GetNextContract(NextContractPurpose.Boarding)`. Unlike
9736
+ * {@link getBoardingAddress} (a stable read of the current display
9737
+ * address that never burns an index), each call here:
9738
+ *
9739
+ * - allocates the next index from the shared HD stream (so boarding and
9740
+ * L2 receive interleave on one monotonic index);
9741
+ * - builds the boarding tapscript at that index with the boarding-exit
9742
+ * CSV;
9743
+ * - persists an `active` `boarding` contract tagged
9744
+ * {@link WALLET_RECEIVE_SOURCE} (with its `signingDescriptor`) so the
9745
+ * ContractWatcher monitors it, boot can restore it as the current
9746
+ * boarding address, and descriptor-aware signing can recover the
9747
+ * per-index key;
9748
+ * - swaps the wallet's current `boardingTapscript`.
9749
+ *
9750
+ * Gated by `walletMode`: a static / `auto` wallet has no descriptor
9751
+ * provider and keeps a single index-0 boarding address for its lifetime,
9752
+ * so this returns the existing {@link getBoardingAddress} unchanged
9753
+ * (no rotation, no index burned).
9754
+ */
9755
+ async getNewBoardingAddress() {
9756
+ const provider = this._descriptorProvider;
9757
+ if (!provider) {
9758
+ return this.getBoardingAddress();
9759
+ }
9760
+ const descriptor = await provider.getNextSigningDescriptor();
9761
+ const pubKey = chunkGUTKJMSF_cjs.deriveDescriptorLeafPubKey(descriptor);
9762
+ const newBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9763
+ ...this._boardingTapscript.options,
9764
+ pubKey
9765
+ });
9766
+ const csvTimelock = newBoarding.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9767
+ const manager = await this.getContractManager();
9768
+ await manager.createContract({
9769
+ type: "boarding",
9770
+ params: {
9771
+ pubKey: base.hex.encode(pubKey),
9772
+ serverPubKey: base.hex.encode(newBoarding.options.serverPubKey),
9773
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString()
9774
+ },
9775
+ script: base.hex.encode(newBoarding.pkScript),
9776
+ address: newBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
9777
+ state: "active",
9778
+ metadata: {
9779
+ source: chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE,
9780
+ signingDescriptor: descriptor
9781
+ }
9782
+ });
9783
+ this.setBoardingTapscriptForRotation(newBoarding);
9784
+ return newBoarding.onchainAddress(this.network);
9785
+ }
9786
+ /**
9787
+ * Mid-session server-signer rotation (plan §4). When arkd rotates its
9788
+ * active signer mid-session — the case the long-lived service worker and
9789
+ * Expo background processes that own automatic migration must handle — a
9790
+ * wallet constructed before the rotation keeps deriving old-signer receive
9791
+ * addresses. Building a migration output to such an address would produce a
9792
+ * VTXO the server must reject, so the wallet must first re-derive its own
9793
+ * receive state under the new active signer.
9794
+ *
9795
+ * Follows the {@link WalletReceiveRotator.rotate} write-path pattern with
9796
+ * the server key swapped instead of the user key: build the new offchain
9797
+ * and boarding tapscripts locally (preserving every other option),
9798
+ * register the matching `default`/`delegate` and `boarding` contract rows
9799
+ * through {@link ContractManager.createContract}, and only then commit the
9800
+ * new tapscripts and server key to the wallet's visible state. The signing
9801
+ * metadata of the current receive/boarding rows is carried onto the new
9802
+ * rows so a rotated (descriptor-backed) receive pubkey can still sign.
9803
+ *
9804
+ * The old-signer contract rows are intentionally left `active` and watched
9805
+ * — they are exactly the deprecated-signer contracts the migration pass
9806
+ * drains. Idempotent: a no-op when the wallet already tracks `xonly`.
9807
+ *
9808
+ * Serialized against HD receive rotation so the two paths (both of which
9809
+ * rebuild and swap `offchainTapscript`) cannot interleave.
9810
+ *
9811
+ * @internal Invoked by the {@link VtxoManager} migration pass; not part of
9812
+ * the stable public API.
9813
+ */
9814
+ async rotateServerSigner(newServerPubKey, checkpointTapscript) {
9815
+ const xonly = toXOnlyPubKey(newServerPubKey);
9816
+ let newServerUnrollScript;
9817
+ try {
9818
+ newServerUnrollScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(checkpointTapscript));
9819
+ } catch (e) {
9820
+ throw new Error("Invalid checkpointTapscript from server");
9821
+ }
9822
+ if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
9823
+ if (this._receiveRotator) {
9824
+ await this._receiveRotator.runExclusive(
9825
+ () => this._doRotateServerSigner(xonly, newServerUnrollScript)
9826
+ );
9827
+ return;
9828
+ }
9829
+ const run = this._serverRotationChain.catch(() => void 0).then(() => this._doRotateServerSigner(xonly, newServerUnrollScript));
9830
+ this._serverRotationChain = run.then(
9831
+ () => void 0,
9832
+ () => void 0
9833
+ );
9834
+ return run;
9835
+ }
9836
+ async _doRotateServerSigner(xonly, newServerUnrollScript) {
9837
+ if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
9838
+ const manager = await this.getContractManager();
9839
+ const [currentOffchainRow] = await manager.getContracts({
9840
+ script: this.defaultContractScript
9841
+ });
9842
+ const currentBoardingScript = base.hex.encode(this._boardingTapscript.pkScript);
9843
+ const [currentBoardingRow] = await manager.getContracts({
9844
+ script: currentBoardingScript
9845
+ });
9846
+ const newOffchain = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
9847
+ ...this.offchainTapscript.options,
9848
+ serverPubKey: xonly
9849
+ }) : new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9850
+ ...this.offchainTapscript.options,
9851
+ serverPubKey: xonly
9852
+ });
9853
+ const newBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9854
+ ...this._boardingTapscript.options,
9855
+ serverPubKey: xonly
9856
+ });
9857
+ const offchainCsv = chunkCMPJR3HS_cjs.timelockToSequence(newOffchain.options.csvTimelock).toString();
9858
+ const newOffchainScript = base.hex.encode(newOffchain.pkScript);
9859
+ const newOffchainAddress = newOffchain.address(this.network.hrp, xonly).encode();
9860
+ if (newOffchain instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
9861
+ await manager.createContract({
9862
+ type: "delegate",
9863
+ params: {
9864
+ pubKey: base.hex.encode(newOffchain.options.pubKey),
9865
+ serverPubKey: base.hex.encode(xonly),
9866
+ delegatePubKey: base.hex.encode(newOffchain.options.delegatePubKey),
9867
+ csvTimelock: offchainCsv
9868
+ },
9869
+ script: newOffchainScript,
9870
+ address: newOffchainAddress,
9871
+ state: "active",
9872
+ metadata: currentOffchainRow?.metadata
9873
+ });
9874
+ } else {
9875
+ await manager.createContract({
9876
+ type: "default",
9877
+ params: {
9878
+ pubKey: base.hex.encode(newOffchain.options.pubKey),
9879
+ serverPubKey: base.hex.encode(xonly),
9880
+ csvTimelock: offchainCsv
9881
+ },
9882
+ script: newOffchainScript,
9883
+ address: newOffchainAddress,
9884
+ state: "active",
9885
+ metadata: currentOffchainRow?.metadata
9886
+ });
9887
+ }
9888
+ const boardingCsv = newBoarding.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9889
+ await manager.createContract({
9890
+ type: "boarding",
9891
+ params: {
9892
+ pubKey: base.hex.encode(newBoarding.options.pubKey),
9893
+ serverPubKey: base.hex.encode(xonly),
9894
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsv).toString()
9895
+ },
9896
+ script: base.hex.encode(newBoarding.pkScript),
9897
+ address: newBoarding.address(this.network.hrp, xonly).encode(),
9898
+ state: "active",
9899
+ metadata: currentBoardingRow?.metadata
9900
+ });
9901
+ this.setOffchainTapscriptForRotation(newOffchain);
9902
+ this.setBoardingTapscriptForRotation(newBoarding);
9903
+ this.setArkServerPublicKeyForRotation(xonly);
9904
+ this.setServerUnrollScriptForRotation(newServerUnrollScript);
9905
+ }
8578
9906
  /**
8579
9907
  * Async mutex that serializes all operations submitting VTXOs to the Arkade
8580
9908
  * server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
@@ -8658,13 +9986,26 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8658
9986
  const hd = provider instanceof HDDescriptorProvider;
8659
9987
  const staticDescriptor = hd ? void 0 : `tr(${base.hex.encode(await this.identity.xOnlyPublicKey())})`;
8660
9988
  const materialize = (index) => hd ? provider.materializeDescriptorAt(index) : staticDescriptor;
8661
- const delegatePubKey = this.offchainTapscript instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
9989
+ const delegatePubKey = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
9990
+ const arkInfo = await this.arkProvider.getInfo();
9991
+ const currentSignerPubKey = toXOnlyPubKey(base.hex.decode(arkInfo.signerPubkey));
9992
+ const deprecatedSignerPubKeys = arkInfo.deprecatedSigners.map(
9993
+ (s) => toXOnlyPubKey(base.hex.decode(s.pubkey))
9994
+ );
8662
9995
  const deps = {
8663
9996
  indexerProvider: this.indexerProvider,
8664
9997
  onchainProvider: this.onchainProvider,
8665
9998
  network: { hrp: this.network.hrp },
8666
- serverPubKey: this.offchainTapscript.options.serverPubKey,
9999
+ // Full network for the boarding on-chain (P2TR) probe — the
10000
+ // `{ hrp }` shape above lacks the `bech32` data
10001
+ // `VtxoScript.onchainAddress` needs (plan §6-I.1).
10002
+ onchainNetwork: this.network,
10003
+ serverPubKey: currentSignerPubKey,
10004
+ deprecatedSignerPubKeys,
8667
10005
  csvTimelocks: this.walletContractTimelocks,
10006
+ // Boarding-exit CSV so the boarding handler can build its
10007
+ // candidate script (distinct from the unilateral-exit matrix).
10008
+ boardingTimelock: this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK,
8668
10009
  delegatePubKey
8669
10010
  };
8670
10011
  const result = await manager.scanContracts({
@@ -8722,6 +10063,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8722
10063
  }
8723
10064
  async dispose() {
8724
10065
  await this._restoreInFlight?.catch(() => void 0);
10066
+ this._serverInfoUnsub?.();
10067
+ this._serverInfoUnsub = void 0;
10068
+ await this._serverInfoInFlight?.catch(() => void 0);
8725
10069
  let rotatorError;
8726
10070
  try {
8727
10071
  await this._receiveRotator?.dispose();
@@ -8765,7 +10109,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8765
10109
  let serverUnrollScript;
8766
10110
  try {
8767
10111
  const raw = base.hex.decode(setup.info.checkpointTapscript);
8768
- serverUnrollScript = chunkWMIPYZSB_cjs.CSVMultisigTapscript.decode(raw);
10112
+ serverUnrollScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(raw);
8769
10113
  } catch (e) {
8770
10114
  throw new Error("Invalid checkpointTapscript from server");
8771
10115
  }
@@ -8796,6 +10140,25 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8796
10140
  boot?.rotator,
8797
10141
  boot?.provider
8798
10142
  );
10143
+ wallet.refreshDeprecatedSigners(setup.info);
10144
+ {
10145
+ const ap = setup.arkProvider;
10146
+ if (typeof ap.onServerInfoChanged === "function") {
10147
+ wallet._serverInfoUnsub = ap.onServerInfoChanged((info) => {
10148
+ wallet._serverInfoInFlight = wallet._serverInfoInFlight.then(() => wallet.handleServerInfoChanged(info)).catch(() => void 0);
10149
+ });
10150
+ }
10151
+ }
10152
+ if (boot?.provider) {
10153
+ const resolvedBoarding = await resolveBoardingBootTapscript(
10154
+ setup.contractRepository,
10155
+ setup.serverPubKey,
10156
+ setup.boardingTapscript
10157
+ );
10158
+ if (resolvedBoarding !== setup.boardingTapscript) {
10159
+ wallet.setBoardingTapscriptForRotation(resolvedBoarding);
10160
+ }
10161
+ }
8799
10162
  await wallet.getVtxoManager();
8800
10163
  return wallet;
8801
10164
  }
@@ -8818,7 +10181,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8818
10181
  */
8819
10182
  async toReadonly() {
8820
10183
  const readonlyIdentity = hasToReadonly(this.identity) ? await this.identity.toReadonly() : this.identity;
8821
- return new ReadonlyWallet(
10184
+ const readonly = new ReadonlyWallet(
8822
10185
  readonlyIdentity,
8823
10186
  this.network,
8824
10187
  this.onchainProvider,
@@ -8833,6 +10196,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8833
10196
  this.watcherConfig,
8834
10197
  this.walletContractTimelocks
8835
10198
  );
10199
+ readonly._deprecatedSigners = new Map(this._deprecatedSigners);
10200
+ return readonly;
8836
10201
  }
8837
10202
  /** Returns the delegate manager when delegation support is configured. */
8838
10203
  async getDelegateManager() {
@@ -8858,10 +10223,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8858
10223
  if (params.selectedVtxos && params.selectedVtxos.length > 0) {
8859
10224
  return this._withTxLock(async () => {
8860
10225
  const offchainTapscript = this.offchainTapscript;
8861
- const arkAddress = offchainTapscript.address(
8862
- this.network.hrp,
8863
- this.arkServerPublicKey
8864
- );
10226
+ const serverPubKey = this.arkServerPublicKey;
10227
+ const serverUnrollScript = this.serverUnrollScript;
10228
+ const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
8865
10229
  const selectedVtxoSum = params.selectedVtxos.map((v) => v.value).reduce((a, b) => a + b, 0);
8866
10230
  if (selectedVtxoSum < params.amount) {
8867
10231
  throw new Error("Selected VTXOs do not cover specified amount");
@@ -8871,7 +10235,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8871
10235
  inputs: params.selectedVtxos,
8872
10236
  changeAmount: BigInt(changeAmount)
8873
10237
  };
8874
- const outputAddress = chunkWMIPYZSB_cjs.ArkAddress.decode(params.address);
10238
+ const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(params.address);
8875
10239
  const outputScript = BigInt(params.amount) < this.dustAmount ? outputAddress.subdustPkScript : outputAddress.pkScript;
8876
10240
  const outputs = [
8877
10241
  {
@@ -8886,25 +10250,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8886
10250
  amount: BigInt(selected.changeAmount)
8887
10251
  });
8888
10252
  }
8889
- this._addPendingSpends(selected.inputs);
8890
- try {
8891
- const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
8892
- selected.inputs,
8893
- outputs
8894
- );
8895
- await this.updateDbAfterOffchainTx(
8896
- selected.inputs,
8897
- arkTxid,
8898
- signedCheckpointTxs,
8899
- params.amount,
8900
- selected.changeAmount,
8901
- selected.changeAmount > 0n ? outputs.length - 1 : 0,
8902
- offchainTapscript
8903
- );
8904
- return arkTxid;
8905
- } finally {
8906
- this._removePendingSpends(selected.inputs);
8907
- }
10253
+ return this._submitOffchainSpend(selected.inputs, outputs, {
10254
+ sentAmount: params.amount,
10255
+ changeAmount: selected.changeAmount,
10256
+ changeVout: selected.changeAmount > 0n ? outputs.length - 1 : 0,
10257
+ offchainTapscript,
10258
+ serverPubKey,
10259
+ serverUnrollScript
10260
+ });
8908
10261
  });
8909
10262
  }
8910
10263
  return this.send({
@@ -8934,11 +10287,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8934
10287
  }
8935
10288
  }
8936
10289
  }
10290
+ const offchainAddress = await this.getAddress();
10291
+ const offchainPkScript = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress).pkScript;
10292
+ const offchainOutputScript = base.hex.encode(offchainPkScript);
8937
10293
  if (!params) {
8938
- const { fees } = await this.arkProvider.getInfo();
10294
+ const { fees, vtxoMaxAmount } = await this.arkProvider.getInfo();
8939
10295
  const estimator = new Estimator(fees.intentFee);
8940
10296
  let amount = 0;
8941
- const exitScript = chunkWMIPYZSB_cjs.CSVMultisigTapscript.decode(
10297
+ const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
8942
10298
  base.hex.decode(this.boardingTapscript.exitScript)
8943
10299
  );
8944
10300
  const boardingTimelock = exitScript.params.timelock;
@@ -8963,7 +10319,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8963
10319
  }
8964
10320
  const vtxos = await this.getVtxos({ withRecoverable: true });
8965
10321
  const filteredVtxos = [];
8966
- for (const vtxo of vtxos) {
10322
+ for (const vtxo of byValueDescending(vtxos)) {
10323
+ if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
10324
+ break;
10325
+ }
8967
10326
  const inputFee = estimator.evalOffchainInput({
8968
10327
  amount: BigInt(vtxo.value),
8969
10328
  type: vtxo.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
@@ -8974,20 +10333,31 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8974
10333
  if (inputFee.satoshis >= vtxo.value) {
8975
10334
  continue;
8976
10335
  }
10336
+ const net = vtxo.value - inputFee.satoshis;
10337
+ if (vtxoMaxAmount >= 0n) {
10338
+ const projectedAmount = BigInt(amount + net);
10339
+ const projectedOutputFee = estimator.evalOffchainOutput({
10340
+ amount: projectedAmount,
10341
+ script: offchainOutputScript
10342
+ });
10343
+ if (projectedAmount - BigInt(projectedOutputFee.satoshis) > vtxoMaxAmount) {
10344
+ continue;
10345
+ }
10346
+ }
8977
10347
  filteredVtxos.push(vtxo);
8978
- amount += vtxo.value - inputFee.satoshis;
10348
+ amount += net;
8979
10349
  }
8980
10350
  const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
8981
10351
  if (inputs.length === 0) {
8982
10352
  throw new Error("No inputs found");
8983
10353
  }
8984
10354
  const output = {
8985
- address: await this.getAddress(),
10355
+ address: offchainAddress,
8986
10356
  amount: BigInt(amount)
8987
10357
  };
8988
10358
  const outputFee = estimator.evalOffchainOutput({
8989
10359
  amount: output.amount,
8990
- script: base.hex.encode(chunkWMIPYZSB_cjs.ArkAddress.decode(output.address).pkScript)
10360
+ script: offchainOutputScript
8991
10361
  });
8992
10362
  output.amount -= BigInt(outputFee.satoshis);
8993
10363
  if (output.amount <= this.dustAmount) {
@@ -9004,7 +10374,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9004
10374
  for (const [index, output] of params.outputs.entries()) {
9005
10375
  let script;
9006
10376
  try {
9007
- const addr = chunkWMIPYZSB_cjs.ArkAddress.decode(output.address);
10377
+ const addr = chunkCMPJR3HS_cjs.ArkAddress.decode(output.address);
9008
10378
  script = addr.pkScript;
9009
10379
  hasOffchainOutputs = true;
9010
10380
  } catch {
@@ -9027,8 +10397,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9027
10397
  }
9028
10398
  }
9029
10399
  let outputAssets;
9030
- const destinationScript = chunkWMIPYZSB_cjs.ArkAddress.decode(await this.getAddress()).pkScript;
9031
- const assetOutputIndex = findDestinationOutputIndex(outputs, destinationScript);
10400
+ const assetOutputIndex = findDestinationOutputIndex(outputs, offchainPkScript);
9032
10401
  if (assetInputs.size > 0) {
9033
10402
  if (assetOutputIndex === -1) {
9034
10403
  throw new Error("Cannot assign assets: no output matches the destination address");
@@ -9096,6 +10465,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9096
10465
  eventCallback: eventCallback ? (event) => Promise.resolve(eventCallback(event)) : void 0
9097
10466
  });
9098
10467
  await this.updateDbAfterSettle(params.inputs, commitmentTxid);
10468
+ await this.maybeRotateBoardingAfterBoard(params.inputs);
9099
10469
  return commitmentTxid;
9100
10470
  } catch (error) {
9101
10471
  const inputIds = params.inputs.map((i) => `${i.txid}:${i.vout}`).join(",");
@@ -9113,6 +10483,41 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9113
10483
  });
9114
10484
  }
9115
10485
  }
10486
+ /**
10487
+ * Rotate the boarding address after a board (rotate-on-board trigger).
10488
+ *
10489
+ * Mirrors {@link WalletReceiveRotator}'s L2 rotation, but driven by a
10490
+ * board instead of a `vtxo_received` event: when a settle consumes at
10491
+ * least one boarding (on-chain) UTXO, the current boarding address has
10492
+ * served its purpose, so we allocate a fresh one via
10493
+ * {@link getNewBoardingAddress}. A settle that consumed only VTXOs (a
10494
+ * renewal / offboard) is not a board and leaves the boarding address
10495
+ * untouched.
10496
+ *
10497
+ * Boarding inputs are the non-VTXO coins (no `virtualStatus`), the same
10498
+ * discriminator {@link handleSettlementFinalizationEvent} uses; the
10499
+ * `typeof` guard skips arknote string inputs before the `in` test.
10500
+ *
10501
+ * No-ops for static / `auto` wallets (no descriptor provider — boarding
10502
+ * stays on its fixed index-0 address). Best-effort and non-fatal: the
10503
+ * settle has already committed and its txid must be returned, so a
10504
+ * rotation failure is logged and swallowed rather than thrown. Funds at
10505
+ * the retired boarding address remain discoverable — the old `boarding`
10506
+ * contract stays active and {@link getBoardingUtxos} fans out over the
10507
+ * full historical boarding set.
10508
+ */
10509
+ async maybeRotateBoardingAfterBoard(inputs) {
10510
+ if (!this._descriptorProvider) return;
10511
+ const consumedBoarding = inputs.some(
10512
+ (input) => typeof input !== "string" && !("virtualStatus" in input)
10513
+ );
10514
+ if (!consumedBoarding) return;
10515
+ try {
10516
+ await this.getNewBoardingAddress();
10517
+ } catch (e) {
10518
+ console.warn("Failed to rotate boarding address after board", e);
10519
+ }
10520
+ }
9116
10521
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
9117
10522
  const signedForfeits = [];
9118
10523
  const isVtxo = (input) => "virtualStatus" in input;
@@ -9177,7 +10582,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9177
10582
  index: input.vout,
9178
10583
  witnessUtxo: {
9179
10584
  amount: BigInt(input.value),
9180
- script: chunkWMIPYZSB_cjs.VtxoScript.decode(input.tapTree).pkScript
10585
+ script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
9181
10586
  },
9182
10587
  sighashType: btcSigner.SigHash.DEFAULT,
9183
10588
  tapLeafScript: [input.forfeitTapLeafScript]
@@ -9196,7 +10601,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9196
10601
  forfeitTx = await this._signerRouter.sign(forfeitTx, [
9197
10602
  {
9198
10603
  index: 0,
9199
- lookupScript: chunkWMIPYZSB_cjs.VtxoScript.decode(input.tapTree).pkScript
10604
+ lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
9200
10605
  }
9201
10606
  ]);
9202
10607
  signedForfeits.push(base.base64.encode(forfeitTx.toPSBT()));
@@ -9236,7 +10641,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9236
10641
  if (skip) {
9237
10642
  return { skip };
9238
10643
  }
9239
- const sweepTapscript = chunkWMIPYZSB_cjs.CSVMultisigTapscript.encode({
10644
+ const sweepTapscript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.encode({
9240
10645
  timelock: {
9241
10646
  value: event.batchExpiry,
9242
10647
  type: event.batchExpiry >= 512n ? "seconds" : "blocks"
@@ -9323,11 +10728,24 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9323
10728
  }
9324
10729
  return jobs;
9325
10730
  }
10731
+ /**
10732
+ * @internal Sign an on-chain boarding exit / sweep transaction, routing
10733
+ * each input to the correct key by its `witnessUtxo.script`: the identity
10734
+ * for index-0 / static boarding, the per-index descriptor for a rotated
10735
+ * boarding UTXO (plan §6-III.3). Used by
10736
+ * {@link VtxoManager.sweepExpiredBoardingUtxos}; without it, the
10737
+ * unilateral exit of a rotated boarding UTXO would be signed with the
10738
+ * wrong (index-0) key and rejected.
10739
+ */
10740
+ async signOnchainBoardingTx(tx) {
10741
+ const signed = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
10742
+ return signed;
10743
+ }
9326
10744
  async safeRegisterIntent(intent, inputs) {
9327
10745
  try {
9328
10746
  return await this.arkProvider.registerIntent(intent);
9329
10747
  } catch (error) {
9330
- if (error instanceof chunkSPDNHPM4_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
10748
+ if (error instanceof chunkX2EQLK4O_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
9331
10749
  const deleteIntent = await this.makeDeleteIntentSignature(inputs);
9332
10750
  await this.arkProvider.deleteIntent(deleteIntent);
9333
10751
  return this.arkProvider.registerIntent(intent);
@@ -9343,7 +10761,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9343
10761
  expire_at: 0,
9344
10762
  cosigners_public_keys: cosignerPubKeys
9345
10763
  };
9346
- const proof = chunkSPDNHPM4_cjs.Intent.create(message, coins, outputs);
10764
+ const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, outputs);
9347
10765
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9348
10766
  return {
9349
10767
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9355,7 +10773,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9355
10773
  type: "delete",
9356
10774
  expire_at: 0
9357
10775
  };
9358
- const proof = chunkSPDNHPM4_cjs.Intent.create(message, coins, []);
10776
+ const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, []);
9359
10777
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9360
10778
  return {
9361
10779
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9367,7 +10785,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9367
10785
  type: "get-pending-tx",
9368
10786
  expire_at: 0
9369
10787
  };
9370
- const proof = chunkSPDNHPM4_cjs.Intent.create(message, coins, []);
10788
+ const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, []);
9371
10789
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9372
10790
  return {
9373
10791
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9518,12 +10936,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9518
10936
  throw new Error("At least one receiver is required");
9519
10937
  }
9520
10938
  const offchainTapscript = this.offchainTapscript;
9521
- const outputAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
10939
+ const serverPubKey = this.arkServerPublicKey;
10940
+ const serverUnrollScript = this.serverUnrollScript;
10941
+ const outputAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
9522
10942
  const address = outputAddress.encode();
9523
10943
  const recipients = validateRecipients(args, Number(this.dustAmount));
9524
- const virtualCoins = await this.getVtxos({
10944
+ const allVirtualCoins = await this.getVtxos({
9525
10945
  withRecoverable: false
9526
10946
  });
10947
+ const pendingRecovery = await this.pendingRecoveryOutpoints();
10948
+ const virtualCoins = pendingRecovery.size ? allVirtualCoins.filter((c) => !pendingRecovery.has(`${c.txid}:${c.vout}`)) : allVirtualCoins;
9527
10949
  const assetChanges = /* @__PURE__ */ new Map();
9528
10950
  let selectedCoins = [];
9529
10951
  let btcAmountToSelect = 0;
@@ -9645,33 +11067,128 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9645
11067
  outputs.push(Extension.create([assetPacket]).txOut());
9646
11068
  }
9647
11069
  const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
9648
- this._addPendingSpends(selectedCoins);
11070
+ return this._submitOffchainSpend(selectedCoins, outputs, {
11071
+ sentAmount,
11072
+ changeAmount: BigInt(changeAmount),
11073
+ changeVout: changeReceiver ? changeIndex : 0,
11074
+ offchainTapscript,
11075
+ serverPubKey,
11076
+ serverUnrollScript,
11077
+ changeAssets: changeReceiver?.assets
11078
+ });
11079
+ }
11080
+ /**
11081
+ * Shared tail of every Ark-transaction spend path (`send`, selected-VTXO
11082
+ * `sendBitcoin`, and {@link sendSelectedVtxosToSelf}): hide the inputs from
11083
+ * concurrent `getVtxos()`, build+submit the offchain tx, persist the spent
11084
+ * inputs and any wallet-owned (change / self) output, then release the
11085
+ * pending-spend hold. Callers own coin selection, output construction, and
11086
+ * the synchronous epoch snapshot; this owns the submit/persist sequence.
11087
+ */
11088
+ async _submitOffchainSpend(inputs, outputs, persist) {
11089
+ this._addPendingSpends(inputs);
9649
11090
  try {
9650
11091
  const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
9651
- selectedCoins,
9652
- outputs
11092
+ inputs,
11093
+ outputs,
11094
+ persist.serverUnrollScript
9653
11095
  );
9654
11096
  await this.updateDbAfterOffchainTx(
9655
- selectedCoins,
11097
+ inputs,
9656
11098
  arkTxid,
9657
11099
  signedCheckpointTxs,
9658
- sentAmount,
9659
- BigInt(changeAmount),
9660
- changeReceiver ? changeIndex : 0,
9661
- offchainTapscript,
9662
- changeReceiver?.assets
11100
+ persist.sentAmount,
11101
+ persist.changeAmount,
11102
+ persist.changeVout,
11103
+ persist.offchainTapscript,
11104
+ persist.serverPubKey,
11105
+ persist.changeAssets,
11106
+ persist.recordSentHistory ?? true
9663
11107
  );
9664
11108
  return arkTxid;
9665
11109
  } finally {
9666
- this._removePendingSpends(selectedCoins);
9667
- }
11110
+ this._removePendingSpends(inputs);
11111
+ }
11112
+ }
11113
+ /**
11114
+ * @internal Migration primitive (deprecated-signer plan, step 1). Spend an
11115
+ * explicit set of the wallet's own deprecated-signer VTXOs into a single
11116
+ * full-value output on the wallet's *active* signer, through the Ark send
11117
+ * path (not `settle`) so arkd builds checkpoints against the active server
11118
+ * epoch. Consumed in-process by {@link VtxoManager}'s migration pass; not
11119
+ * part of the public `IWallet` API and never accepts boarding `ExtendedCoin`
11120
+ * inputs.
11121
+ *
11122
+ * The caller (`migrateCore`) must have already moved the wallet onto the
11123
+ * active signer (`ensureReceiveOnActiveSigner`) and sized the batch (caps +
11124
+ * dust floor); this method validates the inputs, preserves all input assets
11125
+ * on the self output, and persists the new active-signer VTXO even though
11126
+ * there is no separate change output. It records no `TxSent` history — the
11127
+ * funds never leave the wallet.
11128
+ */
11129
+ async sendSelectedVtxosToSelf(inputs) {
11130
+ if (inputs.length === 0) {
11131
+ throw new Error("sendSelectedVtxosToSelf: no inputs");
11132
+ }
11133
+ return this._withTxLock(async () => {
11134
+ const offchainTapscript = this.offchainTapscript;
11135
+ const serverPubKey = this.arkServerPublicKey;
11136
+ const serverUnrollScript = this.serverUnrollScript;
11137
+ const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
11138
+ for (const input of inputs) {
11139
+ if (!isSpendable(input) || isRecoverable(input)) {
11140
+ throw new Error(
11141
+ `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} is not cooperatively spendable`
11142
+ );
11143
+ }
11144
+ if (!input.virtualStatus.batchExpiry) {
11145
+ throw new Error(
11146
+ `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} has no batchExpiry`
11147
+ );
11148
+ }
11149
+ }
11150
+ const total = inputs.reduce((sum, c) => sum + BigInt(c.value), 0n);
11151
+ const outputs = [
11152
+ {
11153
+ script: total < this.dustAmount ? arkAddress.subdustPkScript : arkAddress.pkScript,
11154
+ amount: total
11155
+ }
11156
+ ];
11157
+ const assetInputs = selectedCoinsToAssetInputs(inputs);
11158
+ let selfAssets;
11159
+ if (assetInputs.size > 0) {
11160
+ const totals = /* @__PURE__ */ new Map();
11161
+ for (const [, assets] of assetInputs) {
11162
+ for (const a of assets) {
11163
+ totals.set(a.assetId, (totals.get(a.assetId) ?? 0n) + a.amount);
11164
+ }
11165
+ }
11166
+ selfAssets = [...totals].map(([assetId, amount]) => ({ assetId, amount }));
11167
+ const selfReceiver = {
11168
+ address: arkAddress.encode(),
11169
+ assets: selfAssets
11170
+ };
11171
+ const packet = createAssetPacket(assetInputs, [], selfReceiver);
11172
+ outputs.push(Extension.create([packet]).txOut());
11173
+ }
11174
+ return this._submitOffchainSpend(inputs, outputs, {
11175
+ sentAmount: 0,
11176
+ changeAmount: total,
11177
+ changeVout: 0,
11178
+ offchainTapscript,
11179
+ serverPubKey,
11180
+ changeAssets: selfAssets,
11181
+ recordSentHistory: false,
11182
+ serverUnrollScript
11183
+ });
11184
+ });
9668
11185
  }
9669
11186
  /**
9670
11187
  * Build an offchain transaction from the given inputs and outputs,
9671
11188
  * sign it, submit to the Arkade provider, and finalize.
9672
11189
  * @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
9673
11190
  */
9674
- async buildAndSubmitOffchainTx(inputs, outputs) {
11191
+ async buildAndSubmitOffchainTx(inputs, outputs, serverUnrollScript = this.serverUnrollScript) {
9675
11192
  const offchainTx = buildOffchainTx(
9676
11193
  inputs.map((input) => {
9677
11194
  return {
@@ -9680,11 +11197,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9680
11197
  };
9681
11198
  }),
9682
11199
  outputs,
9683
- this.serverUnrollScript
11200
+ serverUnrollScript
9684
11201
  );
9685
11202
  const arkTxJobs = inputs.map((input, index) => ({
9686
11203
  index,
9687
- lookupScript: chunkWMIPYZSB_cjs.VtxoScript.decode(input.tapTree).pkScript
11204
+ lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
9688
11205
  }));
9689
11206
  const checkpointJobs = offchainTx.checkpoints.map(
9690
11207
  (c) => this.inputSigningJobsFromWitnessUtxos(c)
@@ -9754,14 +11271,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9754
11271
  return { arkTxid, signedCheckpointTxs };
9755
11272
  }
9756
11273
  // mark virtual outputs as spent, save change outputs if any.
9757
- // `offchainTapscript` is the snapshot the caller captured under
9758
- // `_txLock` before any `await`; deriving both the change-VTXO
9759
- // metadata and `primaryAddress` from it here guarantees the local
9760
- // record matches the pkScript the server saw on the inbound
9761
- // transaction, even if `WalletReceiveRotator.rotate` swaps
9762
- // `this.offchainTapscript` mid-flight.
9763
- async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, changeAssets) {
9764
- const primaryAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey).encode();
11274
+ // `offchainTapscript` and `serverPubKey` are the epoch snapshot the
11275
+ // caller captured under `_txLock` before any `await`; deriving both the
11276
+ // change-VTXO metadata and `primaryAddress` from them here guarantees the
11277
+ // local record matches the address/pkScript the server saw on the inbound
11278
+ // transaction, even if `rotateServerSigner` swaps `this.offchainTapscript`
11279
+ // / `this.arkServerPublicKey` mid-flight.
11280
+ async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, serverPubKey, changeAssets, recordSentHistory = true) {
11281
+ const primaryAddress = offchainTapscript.address(this.network.hrp, serverPubKey).encode();
9765
11282
  try {
9766
11283
  const spentVtxos = [];
9767
11284
  const commitmentTxIds = /* @__PURE__ */ new Set();
@@ -9868,19 +11385,21 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9868
11385
  [changeVtxo]
9869
11386
  );
9870
11387
  }
9871
- await this.walletRepository.saveTransactions(primaryAddress, [
9872
- {
9873
- key: {
9874
- boardingTxid: "",
9875
- commitmentTxid: "",
9876
- arkTxid
9877
- },
9878
- amount: sentAmount,
9879
- type: "SENT" /* TxSent */,
9880
- settled: false,
9881
- createdAt
9882
- }
9883
- ]);
11388
+ if (recordSentHistory) {
11389
+ await this.walletRepository.saveTransactions(primaryAddress, [
11390
+ {
11391
+ key: {
11392
+ boardingTxid: "",
11393
+ commitmentTxid: "",
11394
+ arkTxid
11395
+ },
11396
+ amount: sentAmount,
11397
+ type: "SENT" /* TxSent */,
11398
+ settled: false,
11399
+ createdAt
11400
+ }
11401
+ ]);
11402
+ }
9884
11403
  } catch (e) {
9885
11404
  console.warn("error saving offchain tx to repository", e);
9886
11405
  throw e;
@@ -9889,10 +11408,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9889
11408
  // mark virtual outputs as spent/settled, remove boarding inputs
9890
11409
  async updateDbAfterSettle(inputs, commitmentTxid) {
9891
11410
  try {
9892
- const boardingAddress = await this.getBoardingAddress();
9893
11411
  const spentVtxos = [];
9894
11412
  const inputArkTxIds = /* @__PURE__ */ new Set();
9895
- const boardingUtxoToRemove = /* @__PURE__ */ new Set();
11413
+ const boardingRemovalsByAddress = /* @__PURE__ */ new Map();
9896
11414
  const isVtxo = (input) => "virtualStatus" in input;
9897
11415
  const vtxoInputs = inputs.filter(isVtxo);
9898
11416
  const cm = await this.getContractManager();
@@ -9914,7 +11432,20 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9914
11432
  isSpent: true
9915
11433
  });
9916
11434
  } else {
9917
- boardingUtxoToRemove.add(`${input.txid}:${input.vout}`);
11435
+ let sourceAddress;
11436
+ try {
11437
+ sourceAddress = chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).onchainAddress(
11438
+ this.network
11439
+ );
11440
+ } catch {
11441
+ sourceAddress = this.boardingTapscript.onchainAddress(this.network);
11442
+ }
11443
+ let set = boardingRemovalsByAddress.get(sourceAddress);
11444
+ if (!set) {
11445
+ set = /* @__PURE__ */ new Set();
11446
+ boardingRemovalsByAddress.set(sourceAddress, set);
11447
+ }
11448
+ set.add(`${input.txid}:${input.vout}`);
9918
11449
  }
9919
11450
  }
9920
11451
  if (spentVtxos.length > 0) {
@@ -9946,14 +11477,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9946
11477
  );
9947
11478
  }
9948
11479
  }
9949
- if (boardingUtxoToRemove.size > 0) {
9950
- const currentUtxos = await this.walletRepository.getUtxos(boardingAddress);
9951
- const filtered = currentUtxos.filter(
9952
- (u) => !boardingUtxoToRemove.has(`${u.txid}:${u.vout}`)
9953
- );
9954
- await this.walletRepository.deleteUtxos(boardingAddress);
11480
+ for (const [address, toRemove] of boardingRemovalsByAddress) {
11481
+ const currentUtxos = await this.walletRepository.getUtxos(address);
11482
+ const filtered = currentUtxos.filter((u) => !toRemove.has(`${u.txid}:${u.vout}`));
11483
+ await this.walletRepository.deleteUtxos(address);
9955
11484
  if (filtered.length > 0) {
9956
- await this.walletRepository.saveUtxos(boardingAddress, filtered);
11485
+ await this.walletRepository.saveUtxos(address, filtered);
9957
11486
  }
9958
11487
  }
9959
11488
  } catch (e) {
@@ -10161,7 +11690,7 @@ var MessageBus = class {
10161
11690
  this.initialized = true;
10162
11691
  }
10163
11692
  async buildServices(config) {
10164
- const arkProvider = new chunkSPDNHPM4_cjs.RestArkProvider(config.arkServer.url);
11693
+ const arkProvider = new chunkX2EQLK4O_cjs.RestArkProvider(config.arkServer.url);
10165
11694
  const storage = {
10166
11695
  walletRepository: this.walletRepository,
10167
11696
  contractRepository: this.contractRepository
@@ -10537,7 +12066,7 @@ var Ramps = class {
10537
12066
  }
10538
12067
  amount = amount ?? totalAmount;
10539
12068
  const offchainAddress = await this.wallet.getAddress();
10540
- const offchainAddr = chunkWMIPYZSB_cjs.ArkAddress.decode(offchainAddress);
12069
+ const offchainAddr = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress);
10541
12070
  const offchainScript = base.hex.encode(offchainAddr.pkScript);
10542
12071
  const outputFee = estimator.evalOffchainOutput({
10543
12072
  amount,
@@ -10635,7 +12164,7 @@ var Ramps = class {
10635
12164
  let destinationScript;
10636
12165
  for (const networkName of networkNames) {
10637
12166
  try {
10638
- const network = chunkWMIPYZSB_cjs.networks[networkName];
12167
+ const network = chunkCMPJR3HS_cjs.networks[networkName];
10639
12168
  const addr = btcSigner.Address(network).decode(destinationAddress);
10640
12169
  destinationScript = btcSigner.OutScript.encode(addr);
10641
12170
  break;
@@ -10728,6 +12257,82 @@ var DelegateNotConfiguredError = class extends Error {
10728
12257
  };
10729
12258
  var DelegatorNotConfiguredError = DelegateNotConfiguredError;
10730
12259
  var DEFAULT_MESSAGE_TAG = "WALLET_UPDATER";
12260
+ var serializeMigrationVtxoRef = (ref) => ({
12261
+ txid: ref.txid,
12262
+ vout: ref.vout,
12263
+ value: ref.value,
12264
+ signerPubKey: ref.signerPubKey,
12265
+ cutoffDate: ref.cutoffDate?.toString()
12266
+ });
12267
+ var deserializeMigrationVtxoRef = (ref) => ({
12268
+ txid: ref.txid,
12269
+ vout: ref.vout,
12270
+ value: ref.value,
12271
+ signerPubKey: ref.signerPubKey,
12272
+ cutoffDate: ref.cutoffDate != null ? BigInt(ref.cutoffDate) : void 0
12273
+ });
12274
+ var serializeDeprecatedSignerReport = (report) => ({
12275
+ signerPubKey: report.signerPubKey,
12276
+ status: report.status,
12277
+ cutoffDate: report.cutoffDate?.toString(),
12278
+ secondsUntilCutoff: report.secondsUntilCutoff,
12279
+ vtxoCount: report.vtxoCount,
12280
+ totalValue: report.totalValue,
12281
+ boardingCount: report.boardingCount,
12282
+ boardingValue: report.boardingValue,
12283
+ recoverableCount: report.recoverableCount,
12284
+ recoverableValue: report.recoverableValue,
12285
+ awaitingSweepCount: report.awaitingSweepCount,
12286
+ awaitingSweepValue: report.awaitingSweepValue,
12287
+ nextSweepEta: report.nextSweepEta
12288
+ });
12289
+ var deserializeDeprecatedSignerReport = (report) => ({
12290
+ signerPubKey: report.signerPubKey,
12291
+ status: report.status,
12292
+ cutoffDate: report.cutoffDate != null ? BigInt(report.cutoffDate) : void 0,
12293
+ secondsUntilCutoff: report.secondsUntilCutoff,
12294
+ vtxoCount: report.vtxoCount,
12295
+ totalValue: report.totalValue,
12296
+ boardingCount: report.boardingCount,
12297
+ boardingValue: report.boardingValue,
12298
+ recoverableCount: report.recoverableCount,
12299
+ recoverableValue: report.recoverableValue,
12300
+ awaitingSweepCount: report.awaitingSweepCount,
12301
+ awaitingSweepValue: report.awaitingSweepValue,
12302
+ nextSweepEta: report.nextSweepEta
12303
+ });
12304
+ var serializeMigrationLegReport = (leg) => ({
12305
+ txid: leg.txid,
12306
+ migrated: leg.migrated.map(serializeMigrationVtxoRef),
12307
+ skipped: leg.skipped,
12308
+ deferred: leg.deferred,
12309
+ oversized: leg.oversized?.map(serializeMigrationVtxoRef),
12310
+ error: leg.error
12311
+ });
12312
+ var deserializeMigrationLegReport = (leg) => ({
12313
+ txid: leg.txid,
12314
+ migrated: leg.migrated.map(deserializeMigrationVtxoRef),
12315
+ skipped: leg.skipped,
12316
+ deferred: leg.deferred,
12317
+ oversized: leg.oversized?.map(deserializeMigrationVtxoRef),
12318
+ error: leg.error
12319
+ });
12320
+ var serializeMigrationReport = (report) => ({
12321
+ rotated: report.rotated,
12322
+ skipped: report.skipped,
12323
+ vtxos: report.vtxos ? serializeMigrationLegReport(report.vtxos) : void 0,
12324
+ boarding: report.boarding ? serializeMigrationLegReport(report.boarding) : void 0,
12325
+ expired: report.expired.map(serializeMigrationVtxoRef),
12326
+ signers: report.signers.map(serializeDeprecatedSignerReport)
12327
+ });
12328
+ var deserializeMigrationReport = (report) => ({
12329
+ rotated: report.rotated,
12330
+ skipped: report.skipped,
12331
+ vtxos: report.vtxos ? deserializeMigrationLegReport(report.vtxos) : void 0,
12332
+ boarding: report.boarding ? deserializeMigrationLegReport(report.boarding) : void 0,
12333
+ expired: report.expired.map(deserializeMigrationVtxoRef),
12334
+ signers: report.signers.map(deserializeDeprecatedSignerReport)
12335
+ });
10731
12336
  var WalletMessageHandler = class {
10732
12337
  messageTag;
10733
12338
  wallet;
@@ -10809,7 +12414,9 @@ var WalletMessageHandler = class {
10809
12414
  // page-side PING / MESSAGE_BUS_NOT_INITIALIZED path triggered by concurrent
10810
12415
  // short requests (GET_STATUS, GET_BALANCE, ...).
10811
12416
  isLongRunning(message) {
10812
- return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || // HD restore walks the index range with one indexer round-trip per
12417
+ return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || // Migration may apply a server-signer rotation and then run a full
12418
+ // settle, so it streams settlement events like RENEW_VTXOS.
12419
+ message.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS" || // HD restore walks the index range with one indexer round-trip per
10813
12420
  // step until it hits gapLimit consecutive unused indices. The bus
10814
12421
  // deadline must not race the scan; liveness stays covered by PING.
10815
12422
  message.type === "RESTORE_WALLET";
@@ -11175,6 +12782,36 @@ var WalletMessageHandler = class {
11175
12782
  payload: { txid }
11176
12783
  });
11177
12784
  }
12785
+ case "MIGRATE_DEPRECATED_SIGNER_VTXOS": {
12786
+ const wallet = this.requireWallet();
12787
+ const vtxoManager = await wallet.getVtxoManager();
12788
+ const report = await vtxoManager.migrateDeprecatedSignerVtxos({
12789
+ eventCallback: (e) => {
12790
+ this.scheduleForNextTick(
12791
+ () => this.tagged({
12792
+ id,
12793
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_EVENT",
12794
+ payload: e
12795
+ })
12796
+ );
12797
+ }
12798
+ });
12799
+ return this.tagged({
12800
+ id,
12801
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS",
12802
+ payload: { report: serializeMigrationReport(report) }
12803
+ });
12804
+ }
12805
+ case "GET_DEPRECATED_SIGNER_STATUS": {
12806
+ const wallet = this.requireWallet();
12807
+ const vtxoManager = await wallet.getVtxoManager();
12808
+ const signers = await vtxoManager.getDeprecatedSignerStatus();
12809
+ return this.tagged({
12810
+ id,
12811
+ type: "DEPRECATED_SIGNER_STATUS",
12812
+ payload: { signers: signers.map(serializeDeprecatedSignerReport) }
12813
+ });
12814
+ }
11178
12815
  case "RESTORE_WALLET": {
11179
12816
  const wallet = this.requireWallet();
11180
12817
  try {
@@ -11204,13 +12841,14 @@ var WalletMessageHandler = class {
11204
12841
  // Wallet methods
11205
12842
  async handleInitWallet({ payload }) {
11206
12843
  const { arkServerUrl } = payload;
11207
- this.indexerProvider = new chunkSPDNHPM4_cjs.RestIndexerProvider(arkServerUrl);
12844
+ this.indexerProvider = new chunkX2EQLK4O_cjs.RestIndexerProvider(arkServerUrl);
11208
12845
  await this.onWalletInitialized();
11209
12846
  }
11210
12847
  async handleGetBalance() {
11211
- const [boardingUtxos, allVtxos] = await Promise.all([
12848
+ const [boardingUtxos, allVtxos, pendingOutpoints] = await Promise.all([
11212
12849
  this.getAllBoardingUtxos(),
11213
- this.getVtxosFromRepo()
12850
+ this.getVtxosFromRepo(),
12851
+ this.readonlyWallet ? this.readonlyWallet.pendingRecoveryOutpoints() : Promise.resolve(/* @__PURE__ */ new Set())
11214
12852
  ]);
11215
12853
  let confirmed = 0;
11216
12854
  let unconfirmed = 0;
@@ -11226,8 +12864,11 @@ var WalletMessageHandler = class {
11226
12864
  let settled = 0;
11227
12865
  let preconfirmed = 0;
11228
12866
  let recoverable = 0;
12867
+ let pendingRecovery = 0;
11229
12868
  for (const vtxo of spendableVtxos) {
11230
- if (vtxo.virtualStatus.state === "settled") {
12869
+ if (pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
12870
+ pendingRecovery += vtxo.value;
12871
+ } else if (vtxo.virtualStatus.state === "settled") {
11231
12872
  settled += vtxo.value;
11232
12873
  } else if (vtxo.virtualStatus.state === "preconfirmed") {
11233
12874
  preconfirmed += vtxo.value;
@@ -11239,7 +12880,7 @@ var WalletMessageHandler = class {
11239
12880
  }
11240
12881
  }
11241
12882
  const totalBoarding = confirmed + unconfirmed;
11242
- const totalOffchain = settled + preconfirmed + recoverable;
12883
+ const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
11243
12884
  const assetBalances = /* @__PURE__ */ new Map();
11244
12885
  for (const vtxo of spendableVtxos) {
11245
12886
  if (vtxo.assets) {
@@ -11263,6 +12904,7 @@ var WalletMessageHandler = class {
11263
12904
  preconfirmed,
11264
12905
  available: settled + preconfirmed,
11265
12906
  recoverable,
12907
+ pendingRecovery,
11266
12908
  total: totalBoarding + totalOffchain,
11267
12909
  assets
11268
12910
  };
@@ -11353,9 +12995,7 @@ var WalletMessageHandler = class {
11353
12995
  );
11354
12996
  }
11355
12997
  if (funds.type === "utxo") {
11356
- const utxos = funds.coins.map((utxo) => extendCoin(this.readonlyWallet, utxo));
11357
- const boardingAddress = await this.readonlyWallet.getBoardingAddress();
11358
- await this.walletRepository?.saveUtxos(boardingAddress, utxos);
12998
+ const utxos = await this.readonlyWallet.getBoardingUtxos();
11359
12999
  this.scheduleForNextTick(
11360
13000
  () => this.tagged({
11361
13001
  type: "UTXO_UPDATE",
@@ -11384,13 +13024,16 @@ var WalletMessageHandler = class {
11384
13024
  return;
11385
13025
  }
11386
13026
  const vtxos = await this.getVtxosFromRepo();
11387
- const boardingAddress = await this.readonlyWallet.getBoardingAddress();
11388
- const coins = await this.readonlyWallet.onchainProvider.getCoins(boardingAddress);
11389
- await this.walletRepository.deleteUtxos(boardingAddress);
11390
- await this.walletRepository.saveUtxos(
11391
- boardingAddress,
11392
- coins.map((utxo) => extendCoin(this.readonlyWallet, utxo))
11393
- );
13027
+ const boardingAddresses = await this.readonlyWallet.getBoardingAddresses();
13028
+ const fresh = await this.readonlyWallet.getBoardingUtxos();
13029
+ const freshKeys = new Set(fresh.map((u) => `${u.txid}:${u.vout}`));
13030
+ for (const addr of boardingAddresses) {
13031
+ const cached = await this.walletRepository.getUtxos(addr);
13032
+ const kept = cached.filter((u) => freshKeys.has(`${u.txid}:${u.vout}`));
13033
+ if (kept.length === cached.length) continue;
13034
+ await this.walletRepository.deleteUtxos(addr);
13035
+ if (kept.length > 0) await this.walletRepository.saveUtxos(addr, kept);
13036
+ }
11394
13037
  const address = await this.readonlyWallet.getAddress();
11395
13038
  const txs = await this.buildTransactionHistoryFromCache(vtxos);
11396
13039
  if (txs) await this.walletRepository.saveTransactions(address, txs);
@@ -11647,6 +13290,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
11647
13290
  GET_EXPIRING_VTXOS: 2e4,
11648
13291
  GET_EXPIRED_BOARDING_UTXOS: 2e4,
11649
13292
  GET_RECOVERABLE_BALANCE: 2e4,
13293
+ GET_DEPRECATED_SIGNER_STATUS: 2e4,
11650
13294
  RELOAD_WALLET: 2e4,
11651
13295
  // Transactions — need more headroom.
11652
13296
  // SETTLE / RECOVER_VTXOS / RENEW_VTXOS go through the streaming path and
@@ -11662,6 +13306,9 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
11662
13306
  RECOVER_VTXOS: 5e4,
11663
13307
  RENEW_VTXOS: 5e4,
11664
13308
  SWEEP_EXPIRED_BOARDING_UTXOS: 5e4,
13309
+ // Streaming/long-running like RENEW_VTXOS (rotation + settle); the value is
13310
+ // kept for type completeness and is never enforced as an inactivity deadline.
13311
+ MIGRATE_DEPRECATED_SIGNER_VTXOS: 5e4,
11665
13312
  // RESTORE_WALLET is a streaming/long-running path (sendMessageWithEvents)
11666
13313
  // like SETTLE; the value here is kept for type completeness and is never
11667
13314
  // enforced as an inactivity deadline.
@@ -11687,6 +13334,7 @@ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
11687
13334
  "GET_DELEGATE_INFO",
11688
13335
  "GET_RECOVERABLE_BALANCE",
11689
13336
  "GET_EXPIRED_BOARDING_UTXOS",
13337
+ "GET_DEPRECATED_SIGNER_STATUS",
11690
13338
  "GET_VTXOS",
11691
13339
  "GET_CONTRACTS",
11692
13340
  "GET_CONTRACTS_WITH_VTXOS",
@@ -12809,6 +14457,42 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
12809
14457
  throw new Error(`Failed to sweep expired boarding utxos: ${e}`);
12810
14458
  }
12811
14459
  },
14460
+ async migrateDeprecatedSignerVtxos(options) {
14461
+ const message = {
14462
+ tag: messageTag,
14463
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS",
14464
+ id: getRandomId()
14465
+ };
14466
+ try {
14467
+ const response = await wallet.sendMessageWithEvents(
14468
+ message,
14469
+ (resp) => options?.eventCallback?.(
14470
+ resp.payload
14471
+ ),
14472
+ (resp) => resp.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS"
14473
+ );
14474
+ return deserializeMigrationReport(
14475
+ response.payload.report
14476
+ );
14477
+ } catch (e) {
14478
+ throw new Error(`Failed to migrate deprecated-signer vtxos: ${e}`);
14479
+ }
14480
+ },
14481
+ async getDeprecatedSignerStatus() {
14482
+ const message = {
14483
+ tag: messageTag,
14484
+ type: "GET_DEPRECATED_SIGNER_STATUS",
14485
+ id: getRandomId()
14486
+ };
14487
+ try {
14488
+ const response = await wallet.sendMessage(message);
14489
+ return response.payload.signers.map(
14490
+ deserializeDeprecatedSignerReport
14491
+ );
14492
+ } catch (e) {
14493
+ throw new Error(`Failed to get deprecated-signer status: ${e}`);
14494
+ }
14495
+ },
12812
14496
  async dispose() {
12813
14497
  return;
12814
14498
  }
@@ -12837,12 +14521,12 @@ var OnchainWallet = class _OnchainWallet {
12837
14521
  * @returns Configured onchain wallet
12838
14522
  * @throws Error if the configured identity cannot produce a valid x-only public key
12839
14523
  */
12840
- static async create(identity, networkName = chunkWMIPYZSB_cjs.DEFAULT_NETWORK_NAME, provider) {
14524
+ static async create(identity, networkName = chunkCMPJR3HS_cjs.DEFAULT_NETWORK_NAME, provider) {
12841
14525
  const pubkey = await identity.xOnlyPublicKey();
12842
14526
  if (!pubkey) {
12843
14527
  throw new Error("Invalid configured public key");
12844
14528
  }
12845
- const network = chunkWMIPYZSB_cjs.getNetwork(networkName);
14529
+ const network = chunkCMPJR3HS_cjs.getNetwork(networkName);
12846
14530
  const onchainProvider = provider || new EsploraProvider(ESPLORA_URL[networkName]);
12847
14531
  const onchainP2TR = btcSigner.p2tr(pubkey, void 0, network);
12848
14532
  return new _OnchainWallet(identity, network, onchainP2TR, onchainProvider);
@@ -12948,7 +14632,7 @@ var OnchainWallet = class _OnchainWallet {
12948
14632
  if (!inputs) {
12949
14633
  throw new Error("Fee estimation failed");
12950
14634
  }
12951
- let tx = new chunkSPDNHPM4_cjs.Transaction();
14635
+ let tx = new chunkX2EQLK4O_cjs.Transaction();
12952
14636
  for (const input of inputs) {
12953
14637
  tx.addInput({
12954
14638
  txid: input.txid,
@@ -12979,7 +14663,7 @@ var OnchainWallet = class _OnchainWallet {
12979
14663
  */
12980
14664
  async bumpP2A(parent) {
12981
14665
  const parentVsize = parent.vsize;
12982
- let child = new chunkSPDNHPM4_cjs.Transaction({
14666
+ let child = new chunkX2EQLK4O_cjs.Transaction({
12983
14667
  version: 3,
12984
14668
  allowLegacyWitnessUtxo: true
12985
14669
  });
@@ -13714,13 +15398,19 @@ function isHeaderSubscribeResult(v) {
13714
15398
  const obj = v;
13715
15399
  return typeof obj.height === "number" && typeof obj.hex === "string";
13716
15400
  }
15401
+ function errorText(err) {
15402
+ if (typeof err === "string") return err;
15403
+ if (err && typeof err === "object") {
15404
+ const e = err;
15405
+ return [e.message, e.str].filter((v) => typeof v === "string").join(" ");
15406
+ }
15407
+ return "";
15408
+ }
13717
15409
  function isMissingHeightError(err) {
13718
- const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
13719
- return msg.toLowerCase().includes("missingheight");
15410
+ return errorText(err).toLowerCase().includes("missingheight");
13720
15411
  }
13721
15412
  function isTxNotInBlockError(err) {
13722
- const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
13723
- const normalized = msg.toLowerCase();
15413
+ const normalized = errorText(err).toLowerCase();
13724
15414
  return normalized.includes("not yet in a block") || normalized.includes("not in a block") || normalized.includes("not in block") || normalized.includes("no confirmed transaction");
13725
15415
  }
13726
15416
  function childTxidFromHex(txHex) {
@@ -13736,7 +15426,7 @@ exports.BIP322 = void 0;
13736
15426
  async function sign2(message, identity, network) {
13737
15427
  const xOnlyPubKey = await identity.xOnlyPublicKey();
13738
15428
  const payment = btcSigner.p2tr(xOnlyPubKey, void 0, network);
13739
- const toSpend = chunkSPDNHPM4_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
15429
+ const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
13740
15430
  const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
13741
15431
  const signed = await identity.sign(toSign, [0]);
13742
15432
  signed.finalizeIdx(0);
@@ -13794,7 +15484,7 @@ function verifyP2TR(message, witnessItems, pkScript, pubkey) {
13794
15484
  if (sighashType !== btcSigner.SigHash.DEFAULT && sighashType !== btcSigner.SigHash.ALL) {
13795
15485
  return false;
13796
15486
  }
13797
- const toSpend = chunkSPDNHPM4_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
15487
+ const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
13798
15488
  const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
13799
15489
  const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
13800
15490
  const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
@@ -13815,7 +15505,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
13815
15505
  }
13816
15506
  const sighashType = sigWithHash[sigWithHash.length - 1];
13817
15507
  const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
13818
- const toSpend = chunkSPDNHPM4_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
15508
+ const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
13819
15509
  const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
13820
15510
  const scriptCode = btcSigner.OutScript.encode({ type: "pkh", hash: addressHash });
13821
15511
  const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
@@ -13868,7 +15558,7 @@ function encodeCompactSize(n) {
13868
15558
  return buf;
13869
15559
  }
13870
15560
  function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
13871
- const tx = new chunkSPDNHPM4_cjs.Transaction({ version: 0 });
15561
+ const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 0 });
13872
15562
  tx.addInput({
13873
15563
  txid: toSpend.id,
13874
15564
  index: 0,
@@ -13882,12 +15572,12 @@ function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
13882
15572
  });
13883
15573
  tx.addOutput({
13884
15574
  amount: 0n,
13885
- script: chunkSPDNHPM4_cjs.OP_RETURN_EMPTY_PKSCRIPT
15575
+ script: chunkX2EQLK4O_cjs.OP_RETURN_EMPTY_PKSCRIPT
13886
15576
  });
13887
15577
  return tx;
13888
15578
  }
13889
15579
  function craftBIP322ToSignSimple(toSpend, pkScript) {
13890
- const tx = new chunkSPDNHPM4_cjs.Transaction({ version: 0 });
15580
+ const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 0 });
13891
15581
  tx.addInput({
13892
15582
  txid: toSpend.id,
13893
15583
  index: 0,
@@ -13899,7 +15589,7 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
13899
15589
  });
13900
15590
  tx.addOutput({
13901
15591
  amount: 0n,
13902
- script: chunkSPDNHPM4_cjs.OP_RETURN_EMPTY_PKSCRIPT
15592
+ script: chunkX2EQLK4O_cjs.OP_RETURN_EMPTY_PKSCRIPT
13903
15593
  });
13904
15594
  return tx;
13905
15595
  }
@@ -13960,7 +15650,7 @@ exports.Unroll = void 0;
13960
15650
  if (virtualTxs.txs.length === 0) {
13961
15651
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
13962
15652
  }
13963
- const tx = chunkSPDNHPM4_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
15653
+ const tx = chunkX2EQLK4O_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
13964
15654
  if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
13965
15655
  const input = tx.getInput(0);
13966
15656
  if (!input) {
@@ -14037,12 +15727,12 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
14037
15727
  if (!exit) {
14038
15728
  throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
14039
15729
  }
14040
- const spendingLeaf = chunkWMIPYZSB_cjs.VtxoScript.decode(vtxo.tapTree).findLeaf(base.hex.encode(exit.script));
15730
+ const spendingLeaf = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).findLeaf(base.hex.encode(exit.script));
14041
15731
  if (!spendingLeaf) {
14042
15732
  throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
14043
15733
  }
14044
15734
  totalAmount += BigInt(vtxo.value);
14045
- const sequence = chunkWMIPYZSB_cjs.timelockToSequence(exit.params.timelock);
15735
+ const sequence = chunkCMPJR3HS_cjs.timelockToSequence(exit.params.timelock);
14046
15736
  inputs.push({
14047
15737
  txid: vtxo.txid,
14048
15738
  index: vtxo.vout,
@@ -14050,7 +15740,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
14050
15740
  sequence,
14051
15741
  witnessUtxo: {
14052
15742
  amount: BigInt(vtxo.value),
14053
- script: chunkWMIPYZSB_cjs.VtxoScript.decode(vtxo.tapTree).pkScript
15743
+ script: chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).pkScript
14054
15744
  },
14055
15745
  sighashType: btcSigner.SigHash.DEFAULT
14056
15746
  });
@@ -14060,7 +15750,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
14060
15750
  btcSigner.TaprootControlBlock.encode(spendingLeaf[0]).length
14061
15751
  );
14062
15752
  }
14063
- const tx = new chunkSPDNHPM4_cjs.Transaction({ version: 2 });
15753
+ const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 2 });
14064
15754
  for (const input of inputs) {
14065
15755
  tx.addInput(input);
14066
15756
  }
@@ -14107,7 +15797,7 @@ function doWait(onchainProvider, txid) {
14107
15797
  };
14108
15798
  }
14109
15799
  function availableExitPath(confirmedAt, current, vtxo) {
14110
- const exits = chunkWMIPYZSB_cjs.VtxoScript.decode(vtxo.tapTree).exitPaths();
15800
+ const exits = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).exitPaths();
14111
15801
  for (const exit of exits) {
14112
15802
  if (exit.params.timelock.type === "blocks") {
14113
15803
  if (current.height >= confirmedAt.height + Number(exit.params.timelock.value)) {
@@ -14146,7 +15836,7 @@ function decodeArkContract(encoded) {
14146
15836
  }
14147
15837
  function contractFromArkContract(encoded, options = {}) {
14148
15838
  const parsed = decodeArkContract(encoded);
14149
- const handler = chunkGYSK5R57_cjs.contractHandlers.get(parsed.type);
15839
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.get(parsed.type);
14150
15840
  if (!handler) {
14151
15841
  throw new Error(`No handler registered for contract type '${parsed.type}'`);
14152
15842
  }
@@ -14160,9 +15850,9 @@ function contractFromArkContract(encoded, options = {}) {
14160
15850
  metadata: options.metadata
14161
15851
  };
14162
15852
  }
14163
- function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix = chunkWMIPYZSB_cjs.DEFAULT_NETWORK.hrp, options = {}) {
15853
+ function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix = chunkCMPJR3HS_cjs.DEFAULT_NETWORK.hrp, options = {}) {
14164
15854
  const parsed = decodeArkContract(encoded);
14165
- const handler = chunkGYSK5R57_cjs.contractHandlers.getOrThrow(parsed.type);
15855
+ const handler = chunkGUTKJMSF_cjs.contractHandlers.getOrThrow(parsed.type);
14166
15856
  const params = parsed.data;
14167
15857
  const vtxoScript = handler.createScript(params);
14168
15858
  return {
@@ -14236,6 +15926,8 @@ exports.WalletRepositoryImpl = WalletRepositoryImpl;
14236
15926
  exports.WsElectrumChainSource = WsElectrumChainSource;
14237
15927
  exports.buildForfeitTx = buildForfeitTx;
14238
15928
  exports.buildOffchainTx = buildOffchainTx;
15929
+ exports.classifyAgainstSignerSet = classifyAgainstSignerSet;
15930
+ exports.classifyContractSigner = classifyContractSigner;
14239
15931
  exports.closeDatabase = closeDatabase;
14240
15932
  exports.combineTapscriptSigs = combineTapscriptSigs;
14241
15933
  exports.contractFromArkContract = contractFromArkContract;
@@ -14251,6 +15943,7 @@ exports.getRandomId = getRandomId;
14251
15943
  exports.hasBoardingTxExpired = hasBoardingTxExpired;
14252
15944
  exports.isArkContract = isArkContract;
14253
15945
  exports.isBatchSignable = isBatchSignable;
15946
+ exports.isCooperativelyMigratable = isCooperativelyMigratable;
14254
15947
  exports.isDiscoverable = isDiscoverable;
14255
15948
  exports.isExpired = isExpired;
14256
15949
  exports.isRecoverable = isRecoverable;
@@ -14269,10 +15962,12 @@ exports.serializeAssets = serializeAssets;
14269
15962
  exports.serializeUtxo = serializeUtxo;
14270
15963
  exports.serializeVtxo = serializeVtxo;
14271
15964
  exports.setupServiceWorker = setupServiceWorker;
15965
+ exports.signerSetFromInfo = signerSetFromInfo;
15966
+ exports.toXOnlySignerHex = toXOnlySignerHex;
14272
15967
  exports.validateConnectorsTxGraph = validateConnectorsTxGraph;
14273
15968
  exports.validateVtxoTxGraph = validateVtxoTxGraph;
14274
15969
  exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
14275
15970
  exports.waitForIncomingFunds = waitForIncomingFunds;
14276
15971
  exports.warnAndFilterVtxosForScript = warnAndFilterVtxosForScript;
14277
- //# sourceMappingURL=chunk-7K3ROJF6.cjs.map
14278
- //# sourceMappingURL=chunk-7K3ROJF6.cjs.map
15972
+ //# sourceMappingURL=chunk-H2LX2KKY.cjs.map
15973
+ //# sourceMappingURL=chunk-H2LX2KKY.cjs.map