@bsv/wallet-toolbox 2.6.5 → 2.7.1

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 (215) hide show
  1. package/CHANGELOG.md +77 -8
  2. package/README.md +72 -18
  3. package/docs/action-batch-planning.md +80 -37
  4. package/docs/managed-change-liquidity.md +251 -0
  5. package/out/src/Setup.d.ts +9 -0
  6. package/out/src/Setup.d.ts.map +1 -1
  7. package/out/src/Setup.js +2 -0
  8. package/out/src/Setup.js.map +1 -1
  9. package/out/src/SetupClient.d.ts +3 -0
  10. package/out/src/SetupClient.d.ts.map +1 -1
  11. package/out/src/SetupClient.js +1 -0
  12. package/out/src/SetupClient.js.map +1 -1
  13. package/out/src/SetupWallet.d.ts +3 -0
  14. package/out/src/SetupWallet.d.ts.map +1 -1
  15. package/out/src/Wallet.d.ts +4 -3
  16. package/out/src/Wallet.d.ts.map +1 -1
  17. package/out/src/Wallet.js +4 -3
  18. package/out/src/Wallet.js.map +1 -1
  19. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  20. package/out/src/WalletPermissionsManager.js +5 -8
  21. package/out/src/WalletPermissionsManager.js.map +1 -1
  22. package/out/src/mockchain/MockServices.d.ts.map +1 -1
  23. package/out/src/mockchain/MockServices.js +2 -6
  24. package/out/src/mockchain/MockServices.js.map +1 -1
  25. package/out/src/monitor/Monitor.d.ts.map +1 -1
  26. package/out/src/monitor/Monitor.js +5 -4
  27. package/out/src/monitor/Monitor.js.map +1 -1
  28. package/out/src/monitor/tasks/TaskArcSSE.d.ts +4 -1
  29. package/out/src/monitor/tasks/TaskArcSSE.d.ts.map +1 -1
  30. package/out/src/monitor/tasks/TaskArcSSE.js +92 -65
  31. package/out/src/monitor/tasks/TaskArcSSE.js.map +1 -1
  32. package/out/src/monitor/tasks/TaskReconcilePendingTransactions.d.ts +27 -0
  33. package/out/src/monitor/tasks/TaskReconcilePendingTransactions.d.ts.map +1 -0
  34. package/out/src/monitor/tasks/TaskReconcilePendingTransactions.js +236 -0
  35. package/out/src/monitor/tasks/TaskReconcilePendingTransactions.js.map +1 -0
  36. package/out/src/monitor/tasks/TaskReviewDoubleSpends.d.ts.map +1 -1
  37. package/out/src/monitor/tasks/TaskReviewDoubleSpends.js +15 -3
  38. package/out/src/monitor/tasks/TaskReviewDoubleSpends.js.map +1 -1
  39. package/out/src/monitor/tasks/TaskReviewUtxos.d.ts +29 -2
  40. package/out/src/monitor/tasks/TaskReviewUtxos.d.ts.map +1 -1
  41. package/out/src/monitor/tasks/TaskReviewUtxos.js +155 -7
  42. package/out/src/monitor/tasks/TaskReviewUtxos.js.map +1 -1
  43. package/out/src/monitor/tasks/index.all.d.ts +1 -0
  44. package/out/src/monitor/tasks/index.all.d.ts.map +1 -1
  45. package/out/src/monitor/tasks/index.all.js +1 -0
  46. package/out/src/monitor/tasks/index.all.js.map +1 -1
  47. package/out/src/sdk/ActionBatch.interfaces.d.ts +19 -0
  48. package/out/src/sdk/ActionBatch.interfaces.d.ts.map +1 -1
  49. package/out/src/sdk/WERR_errors.d.ts +23 -0
  50. package/out/src/sdk/WERR_errors.d.ts.map +1 -1
  51. package/out/src/sdk/WERR_errors.js +44 -1
  52. package/out/src/sdk/WERR_errors.js.map +1 -1
  53. package/out/src/sdk/WalletError.d.ts.map +1 -1
  54. package/out/src/sdk/WalletError.js +7 -21
  55. package/out/src/sdk/WalletError.js.map +1 -1
  56. package/out/src/sdk/WalletErrorFromJson.d.ts.map +1 -1
  57. package/out/src/sdk/WalletErrorFromJson.js +9 -1
  58. package/out/src/sdk/WalletErrorFromJson.js.map +1 -1
  59. package/out/src/sdk/WalletServices.interfaces.d.ts +14 -1
  60. package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  61. package/out/src/sdk/WalletStorage.interfaces.d.ts +3 -1
  62. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  63. package/out/src/sdk/types.d.ts +3 -2
  64. package/out/src/sdk/types.d.ts.map +1 -1
  65. package/out/src/sdk/types.js +3 -2
  66. package/out/src/sdk/types.js.map +1 -1
  67. package/out/src/services/Services.d.ts.map +1 -1
  68. package/out/src/services/Services.js +73 -15
  69. package/out/src/services/Services.js.map +1 -1
  70. package/out/src/services/classifyOutputUtxo.d.ts +25 -0
  71. package/out/src/services/classifyOutputUtxo.d.ts.map +1 -0
  72. package/out/src/services/classifyOutputUtxo.js +87 -0
  73. package/out/src/services/classifyOutputUtxo.js.map +1 -0
  74. package/out/src/services/createDefaultWalletServicesOptions.d.ts +16 -3
  75. package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  76. package/out/src/services/createDefaultWalletServicesOptions.js +38 -8
  77. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  78. package/out/src/services/providers/ArcSSEClient.d.ts +13 -4
  79. package/out/src/services/providers/ArcSSEClient.d.ts.map +1 -1
  80. package/out/src/services/providers/ArcSSEClient.js +30 -7
  81. package/out/src/services/providers/ArcSSEClient.js.map +1 -1
  82. package/out/src/services/providers/Arcade.d.ts +10 -1
  83. package/out/src/services/providers/Arcade.d.ts.map +1 -1
  84. package/out/src/services/providers/Arcade.js +63 -0
  85. package/out/src/services/providers/Arcade.js.map +1 -1
  86. package/out/src/services/providers/arcadeStatus.d.ts +18 -0
  87. package/out/src/services/providers/arcadeStatus.d.ts.map +1 -0
  88. package/out/src/services/providers/arcadeStatus.js +56 -0
  89. package/out/src/services/providers/arcadeStatus.js.map +1 -0
  90. package/out/src/signer/actionBatch/ActionBatchPlanner.d.ts.map +1 -1
  91. package/out/src/signer/actionBatch/ActionBatchPlanner.js +25 -3
  92. package/out/src/signer/actionBatch/ActionBatchPlanner.js.map +1 -1
  93. package/out/src/signer/actionBatch/ActionBatchWorkspace.d.ts +2 -0
  94. package/out/src/signer/actionBatch/ActionBatchWorkspace.d.ts.map +1 -1
  95. package/out/src/signer/actionBatch/ActionBatchWorkspace.js +135 -16
  96. package/out/src/signer/actionBatch/ActionBatchWorkspace.js.map +1 -1
  97. package/out/src/storage/StorageIdb.d.ts +2 -0
  98. package/out/src/storage/StorageIdb.d.ts.map +1 -1
  99. package/out/src/storage/StorageIdb.js +24 -1
  100. package/out/src/storage/StorageIdb.js.map +1 -1
  101. package/out/src/storage/StorageKnex.d.ts +1 -1
  102. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  103. package/out/src/storage/StorageKnex.js +4 -4
  104. package/out/src/storage/StorageKnex.js.map +1 -1
  105. package/out/src/storage/StorageProvider.d.ts +18 -2
  106. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  107. package/out/src/storage/StorageProvider.js +45 -14
  108. package/out/src/storage/StorageProvider.js.map +1 -1
  109. package/out/src/storage/StorageReaderWriter.d.ts.map +1 -1
  110. package/out/src/storage/StorageReaderWriter.js +3 -2
  111. package/out/src/storage/StorageReaderWriter.js.map +1 -1
  112. package/out/src/storage/WalletStorageManager.d.ts +12 -0
  113. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  114. package/out/src/storage/WalletStorageManager.js +22 -2
  115. package/out/src/storage/WalletStorageManager.js.map +1 -1
  116. package/out/src/storage/adminServer/adminServer.d.ts +2 -0
  117. package/out/src/storage/adminServer/adminServer.d.ts.map +1 -1
  118. package/out/src/storage/adminServer/adminServer.js +25 -8
  119. package/out/src/storage/adminServer/adminServer.js.map +1 -1
  120. package/out/src/storage/adminServer/adminUi.d.ts.map +1 -1
  121. package/out/src/storage/adminServer/adminUi.js +32 -9
  122. package/out/src/storage/adminServer/adminUi.js.map +1 -1
  123. package/out/src/storage/adminServer/reviewUtxos.d.ts +27 -0
  124. package/out/src/storage/adminServer/reviewUtxos.d.ts.map +1 -0
  125. package/out/src/storage/adminServer/reviewUtxos.js +62 -0
  126. package/out/src/storage/adminServer/reviewUtxos.js.map +1 -0
  127. package/out/src/storage/index.all.d.ts +1 -0
  128. package/out/src/storage/index.all.d.ts.map +1 -1
  129. package/out/src/storage/index.all.js +1 -0
  130. package/out/src/storage/index.all.js.map +1 -1
  131. package/out/src/storage/index.client.d.ts +1 -0
  132. package/out/src/storage/index.client.d.ts.map +1 -1
  133. package/out/src/storage/index.client.js +1 -0
  134. package/out/src/storage/index.client.js.map +1 -1
  135. package/out/src/storage/index.mobile.d.ts +1 -0
  136. package/out/src/storage/index.mobile.d.ts.map +1 -1
  137. package/out/src/storage/index.mobile.js +1 -0
  138. package/out/src/storage/index.mobile.js.map +1 -1
  139. package/out/src/storage/methods/ListOutputsSpecOp.d.ts.map +1 -1
  140. package/out/src/storage/methods/ListOutputsSpecOp.js +4 -44
  141. package/out/src/storage/methods/ListOutputsSpecOp.js.map +1 -1
  142. package/out/src/storage/methods/actionBatch.d.ts +9 -2
  143. package/out/src/storage/methods/actionBatch.d.ts.map +1 -1
  144. package/out/src/storage/methods/actionBatch.js +258 -66
  145. package/out/src/storage/methods/actionBatch.js.map +1 -1
  146. package/out/src/storage/methods/actionBatchBlobs.js +7 -4
  147. package/out/src/storage/methods/actionBatchBlobs.js.map +1 -1
  148. package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
  149. package/out/src/storage/methods/attemptToPostReqsToNetwork.js +2 -66
  150. package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +1 -1
  151. package/out/src/storage/methods/availableManagedChange.d.ts +8 -1
  152. package/out/src/storage/methods/availableManagedChange.d.ts.map +1 -1
  153. package/out/src/storage/methods/availableManagedChange.js.map +1 -1
  154. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  155. package/out/src/storage/methods/createAction.js +176 -58
  156. package/out/src/storage/methods/createAction.js.map +1 -1
  157. package/out/src/storage/methods/generateChange.d.ts +13 -1
  158. package/out/src/storage/methods/generateChange.d.ts.map +1 -1
  159. package/out/src/storage/methods/generateChange.js +169 -12
  160. package/out/src/storage/methods/generateChange.js.map +1 -1
  161. package/out/src/storage/methods/listActionsIdb.d.ts.map +1 -1
  162. package/out/src/storage/methods/listActionsIdb.js +4 -0
  163. package/out/src/storage/methods/listActionsIdb.js.map +1 -1
  164. package/out/src/storage/methods/listActionsKnex.d.ts.map +1 -1
  165. package/out/src/storage/methods/listActionsKnex.js +4 -0
  166. package/out/src/storage/methods/listActionsKnex.js.map +1 -1
  167. package/out/src/storage/methods/listOutputsIdb.d.ts.map +1 -1
  168. package/out/src/storage/methods/listOutputsIdb.js +2 -1
  169. package/out/src/storage/methods/listOutputsIdb.js.map +1 -1
  170. package/out/src/storage/methods/managedChangePolicy.d.ts +44 -0
  171. package/out/src/storage/methods/managedChangePolicy.d.ts.map +1 -0
  172. package/out/src/storage/methods/managedChangePolicy.js +58 -0
  173. package/out/src/storage/methods/managedChangePolicy.js.map +1 -0
  174. package/out/src/storage/methods/reconcileFailedTransactionInputs.d.ts +32 -0
  175. package/out/src/storage/methods/reconcileFailedTransactionInputs.d.ts.map +1 -0
  176. package/out/src/storage/methods/reconcileFailedTransactionInputs.js +142 -0
  177. package/out/src/storage/methods/reconcileFailedTransactionInputs.js.map +1 -0
  178. package/out/src/storage/methods/reviewUtxoOutputs.d.ts +37 -0
  179. package/out/src/storage/methods/reviewUtxoOutputs.d.ts.map +1 -0
  180. package/out/src/storage/methods/reviewUtxoOutputs.js +150 -0
  181. package/out/src/storage/methods/reviewUtxoOutputs.js.map +1 -0
  182. package/out/src/storage/portable/index.d.ts.map +1 -1
  183. package/out/src/storage/portable/index.js +4 -2
  184. package/out/src/storage/portable/index.js.map +1 -1
  185. package/out/src/storage/remoting/StorageClientBase.d.ts +2 -1
  186. package/out/src/storage/remoting/StorageClientBase.d.ts.map +1 -1
  187. package/out/src/storage/remoting/StorageClientBase.js +3 -0
  188. package/out/src/storage/remoting/StorageClientBase.js.map +1 -1
  189. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  190. package/out/src/storage/remoting/StorageServer.js +4 -1
  191. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  192. package/out/src/storage/schema/KnexMigrations.d.ts +1 -0
  193. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  194. package/out/src/storage/schema/KnexMigrations.js +29 -3
  195. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  196. package/out/src/storage/schema/entities/EntityOutputBasket.d.ts.map +1 -1
  197. package/out/src/storage/schema/entities/EntityOutputBasket.js +5 -2
  198. package/out/src/storage/schema/entities/EntityOutputBasket.js.map +1 -1
  199. package/out/src/utility/brc153ReferenceLabels.d.ts +21 -0
  200. package/out/src/utility/brc153ReferenceLabels.d.ts.map +1 -0
  201. package/out/src/utility/brc153ReferenceLabels.js +45 -0
  202. package/out/src/utility/brc153ReferenceLabels.js.map +1 -0
  203. package/out/src/utility/index.all.d.ts +1 -0
  204. package/out/src/utility/index.all.d.ts.map +1 -1
  205. package/out/src/utility/index.all.js +1 -0
  206. package/out/src/utility/index.all.js.map +1 -1
  207. package/out/src/utility/index.client.d.ts +1 -0
  208. package/out/src/utility/index.client.d.ts.map +1 -1
  209. package/out/src/utility/index.client.js +1 -0
  210. package/out/src/utility/index.client.js.map +1 -1
  211. package/out/src/utility/utilityHelpers.d.ts +1 -1
  212. package/out/src/utility/utilityHelpers.d.ts.map +1 -1
  213. package/out/src/utility/utilityHelpers.js +2 -1
  214. package/out/src/utility/utilityHelpers.js.map +1 -1
  215. package/package.json +3 -3
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ACTION_BATCH_MAX_RESERVATION_EXTENSION_OUTPUTS = exports.ACTION_BATCH_HARD_LIFETIME_MS = exports.ACTION_BATCH_LEASE_MS = void 0;
3
+ exports.ACTION_BATCH_MAX_RESERVED_OUTPUTS = exports.ACTION_BATCH_MAX_RESERVATION_EXTENSION_OUTPUTS = exports.ACTION_BATCH_HARD_LIFETIME_MS = exports.ACTION_BATCH_LEASE_MS = void 0;
4
4
  exports.getActionBatchCapabilities = getActionBatchCapabilities;
5
5
  exports.cleanupExpiredActionBatches = cleanupExpiredActionBatches;
6
6
  exports.beginActionBatch = beginActionBatch;
7
7
  exports.extendActionBatch = extendActionBatch;
8
8
  exports.renewActionBatch = renewActionBatch;
9
+ exports.resumeActionBatch = resumeActionBatch;
9
10
  exports.commitActionBatch = commitActionBatch;
10
11
  exports.commitActionBatchByDigest = commitActionBatchByDigest;
11
12
  exports.abortActionBatch = abortActionBatch;
@@ -29,7 +30,14 @@ exports.ACTION_BATCH_HARD_LIFETIME_MS = 60 * 60 * 1000;
29
30
  const INITIAL_RESERVATION_LIMIT = 8;
30
31
  const INITIAL_EXTRA_OUTPUTS = 3;
31
32
  exports.ACTION_BATCH_MAX_RESERVATION_EXTENSION_OUTPUTS = 64;
32
- function getActionBatchCapabilities() {
33
+ exports.ACTION_BATCH_MAX_RESERVED_OUTPUTS = 256;
34
+ function isValidOutpoint(outpoint) {
35
+ // Storage keys and canonical transaction IDs are lowercase. Rejecting an
36
+ // alternate spelling here is preferable to validating it and then silently
37
+ // missing the case-sensitive lookup below.
38
+ return /^[0-9a-f]{64}$/.test(outpoint.txid) && Number.isSafeInteger(outpoint.vout) && outpoint.vout >= 0;
39
+ }
40
+ function getActionBatchCapabilities(maxReservedOutputs = exports.ACTION_BATCH_MAX_RESERVED_OUTPUTS, supportsResume = false) {
33
41
  return {
34
42
  actionBatch: {
35
43
  version: 1,
@@ -38,6 +46,8 @@ function getActionBatchCapabilities() {
38
46
  maxConcurrentUploads: actionBatchBlobs_1.ACTION_BATCH_MAX_CONCURRENT_UPLOADS,
39
47
  leaseMs: exports.ACTION_BATCH_LEASE_MS,
40
48
  hardLifetimeMs: exports.ACTION_BATCH_HARD_LIFETIME_MS,
49
+ resume: supportsResume ? true : undefined,
50
+ maxReservedOutputs,
41
51
  compactBegin: true,
42
52
  manifestVersion: 2,
43
53
  commitByDigest: true,
@@ -55,13 +65,28 @@ function getActionBatchCapabilities() {
55
65
  };
56
66
  }
57
67
  function activeExpiry(batch, now = new Date()) {
58
- return batch.status !== 'active' && batch.status !== 'prepared' ||
68
+ return ((batch.status !== 'active' && batch.status !== 'prepared') ||
59
69
  batch.expiresAt.getTime() <= now.getTime() ||
60
- batch.hardExpiresAt.getTime() <= now.getTime();
70
+ batch.hardExpiresAt.getTime() <= now.getTime());
71
+ }
72
+ function actionBatchErrorState(batch, now = new Date()) {
73
+ if (batch == null)
74
+ return 'missing';
75
+ if (batch.hardExpiresAt.getTime() <= now.getTime())
76
+ return 'hard-expired';
77
+ if (batch.status === 'aborted')
78
+ return 'aborted';
79
+ if (batch.status === 'committed')
80
+ return 'committed';
81
+ if (batch.status === 'expired' || batch.expiresAt.getTime() <= now.getTime())
82
+ return 'expired';
83
+ if (batch.status !== 'active' && batch.status !== 'prepared')
84
+ return 'inactive';
85
+ return undefined;
61
86
  }
62
87
  function canExpire(batch, now = new Date()) {
63
- return (batch.status === 'active' || batch.status === 'prepared') &&
64
- (batch.expiresAt.getTime() <= now.getTime() || batch.hardExpiresAt.getTime() <= now.getTime());
88
+ return ((batch.status === 'active' || batch.status === 'prepared') &&
89
+ (batch.expiresAt.getTime() <= now.getTime() || batch.hardExpiresAt.getTime() <= now.getTime()));
65
90
  }
66
91
  async function releaseBatchState(storage, batch, status, trx) {
67
92
  await storage.deleteActionBatchOutputReservations(batch.actionBatchId, trx);
@@ -92,7 +117,10 @@ function sourceOutputFromBeef(beef, outpoint) {
92
117
  const output = tx?.outputs[outpoint.vout];
93
118
  if (output == null)
94
119
  return undefined;
95
- return { satoshis: sdk_1.Validation.validateSatoshis(output.satoshis, 'source output satoshis'), lockingScript: output.lockingScript };
120
+ return {
121
+ satoshis: sdk_1.Validation.validateSatoshis(output.satoshis, 'source output satoshis'),
122
+ lockingScript: output.lockingScript
123
+ };
96
124
  }
97
125
  async function resolveExplicitOutputs(storage, userId, args, allowDeferredProofs) {
98
126
  const byOutpoint = await storage.findOutputsByOutpoints(userId, args.inputs.map(input => input.outpoint));
@@ -142,12 +170,19 @@ function estimateFirstActionTarget(storage, args, inputSatoshis, outputScriptLen
142
170
  outputLengths.push(25);
143
171
  outputLengths.push(25);
144
172
  const fee = (0, StorageProvider_1.validateStorageFeeModel)(storage.feeModel).value ?? 0;
145
- const minFee = Math.ceil((0, utils_1.transactionSize)([...inputLengths, 107], outputLengths) * fee / 1000);
173
+ const minFee = Math.ceil(((0, utils_1.transactionSize)([...inputLengths, 107], outputLengths) * fee) / 1000);
146
174
  return Math.max(1, outputSatoshis + minFee - inputSatoshis);
147
175
  }
176
+ function selectReservationChange(candidates, targetSatoshis) {
177
+ for (const status of ['completed', 'unproven', 'sending']) {
178
+ const selected = (0, actionPlanning_1.selectCanonicalChange)(candidates.filter(candidate => candidate.transactionStatus === status), targetSatoshis);
179
+ if (selected != null)
180
+ return selected;
181
+ }
182
+ return undefined;
183
+ }
148
184
  function chooseReservationPool(candidates, targetSatoshis, limit, extras, fillLimit, planningCosts) {
149
- const remaining = candidates
150
- .filter(output => output.satoshis > planningCosts.marginalInputFee);
185
+ const remaining = candidates.filter(output => output.satoshis > planningCosts.marginalInputFee);
151
186
  const chosen = [];
152
187
  // A reservation is useful to generateChangeSdk only after it also covers
153
188
  // the marginal funding-input fee and leaves an economically viable first
@@ -155,7 +190,7 @@ function chooseReservationPool(candidates, targetSatoshis, limit, extras, fillLi
155
190
  // that satisfies the nominal deficit but can never close the real plan.
156
191
  let deficit = targetSatoshis + planningCosts.firstChangeCost;
157
192
  while (deficit > 0 && chosen.length < limit) {
158
- const output = (0, actionPlanning_1.selectCanonicalChange)(remaining, deficit + planningCosts.marginalInputFee);
193
+ const output = selectReservationChange(remaining, deficit + planningCosts.marginalInputFee);
159
194
  if (output == null)
160
195
  break;
161
196
  chosen.push(output);
@@ -164,7 +199,7 @@ function chooseReservationPool(candidates, targetSatoshis, limit, extras, fillLi
164
199
  }
165
200
  const desiredCount = fillLimit ? limit : Math.min(limit, chosen.length + extras);
166
201
  while (remaining.length > 0 && chosen.length < desiredCount) {
167
- const output = (0, actionPlanning_1.selectCanonicalChange)(remaining, targetSatoshis);
202
+ const output = selectReservationChange(remaining, targetSatoshis);
168
203
  if (output == null)
169
204
  break;
170
205
  chosen.push(output);
@@ -172,17 +207,24 @@ function chooseReservationPool(candidates, targetSatoshis, limit, extras, fillLi
172
207
  }
173
208
  return chosen;
174
209
  }
175
- function reservationPlanningCosts(storage, basket) {
210
+ function reservationOutput(candidate) {
211
+ const { transactionStatus: _transactionStatus, ...output } = candidate;
212
+ return output;
213
+ }
214
+ function reservationPlanningCosts(storage, _basket) {
176
215
  const satsPerKb = (0, StorageProvider_1.validateStorageFeeModel)(storage.feeModel).value ?? 0;
177
216
  const minimumSpendSize = (0, utils_1.transactionSize)([107], [25]);
178
- const minimumSpendFee = Math.ceil(minimumSpendSize * satsPerKb / 1000);
217
+ const minimumSpendFee = Math.ceil((minimumSpendSize * satsPerKb) / 1000);
179
218
  const dustFloor = Math.max(1, minimumSpendFee * 2);
180
219
  const marginalInputSize = (0, utils_1.transactionSize)([107], []) - (0, utils_1.transactionSize)([], []);
181
220
  const marginalOutputSize = (0, utils_1.transactionSize)([], [25]) - (0, utils_1.transactionSize)([], []);
182
- const desiredFirstChange = Math.max(dustFloor, Math.max(1, Math.round(basket.minimumDesiredUTXOValue / 4)));
221
+ // Reserving inputs is compulsory-funding work, not pool shaping. Requiring
222
+ // the preferred liquidity value here could consume reservation capacity or
223
+ // reject a batch solely to manufacture optional change.
224
+ const desiredFirstChange = dustFloor;
183
225
  return {
184
- firstChangeCost: desiredFirstChange + Math.ceil(marginalOutputSize * satsPerKb / 1000),
185
- marginalInputFee: Math.ceil(marginalInputSize * satsPerKb / 1000)
226
+ firstChangeCost: desiredFirstChange + Math.ceil((marginalOutputSize * satsPerKb) / 1000),
227
+ marginalInputFee: Math.ceil((marginalInputSize * satsPerKb) / 1000)
186
228
  };
187
229
  }
188
230
  async function reserveOutputs(storage, batch, outputs, trx) {
@@ -251,33 +293,48 @@ function newBatch(userId, batchId) {
251
293
  async function beginActionBatch(storage, auth, args) {
252
294
  const userId = (0, utilityHelpers_1.verifyId)(auth.userId);
253
295
  await cleanupExpiredActionBatches(storage);
254
- if (await storage.findActionBatch(userId, args.batchId) != null) {
296
+ if ((await storage.findActionBatch(userId, args.batchId)) != null) {
255
297
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('batchId', 'unique');
256
298
  }
257
299
  const outputScriptLengths = args.firstActionOutputScriptLengths;
258
- if (outputScriptLengths != null && (outputScriptLengths.length !== args.firstAction.outputs.length ||
259
- outputScriptLengths.some(length => !Number.isSafeInteger(length) || length < 0) ||
260
- args.firstAction.outputs.some((output, index) => output.lockingScript.length > 0 &&
261
- output.lockingScript.length / 2 !== outputScriptLengths[index]))) {
300
+ if (outputScriptLengths != null &&
301
+ (outputScriptLengths.length !== args.firstAction.outputs.length ||
302
+ outputScriptLengths.some(length => !Number.isSafeInteger(length) || length < 0) ||
303
+ args.firstAction.outputs.some((output, index) => output.lockingScript.length > 0 && output.lockingScript.length / 2 !== outputScriptLengths[index]))) {
262
304
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('firstActionOutputScriptLengths', 'valid byte lengths aligned with firstAction outputs');
263
305
  }
264
306
  const changeBasket = (0, utilityHelpers_1.verifyOne)(await storage.findOutputBaskets({ partial: { userId, name: 'default' } }));
265
307
  const explicit = await resolveExplicitOutputs(storage, userId, args.firstAction, outputScriptLengths != null);
266
308
  const noSendChange = await resolveNoSendChangeOutputs(storage, userId, args.firstAction);
267
309
  const fixedOutputIds = new Set([...explicit.outputs, ...noSendChange.outputs].map(output => output.outputId));
268
- const available = (await storage.findAvailableManagedChangeInputs(userId, changeBasket.basketId, !args.firstAction.isDelayed)).filter(output => !fixedOutputIds.has(output.outputId));
310
+ const availableOutputs = (await storage.findAvailableManagedChangeInputs(userId, changeBasket.basketId, false)).filter(output => !fixedOutputIds.has(output.outputId));
311
+ const availableStatuses = await storage.findTransactionStatusesByIds(userId, availableOutputs.map(output => output.transactionId));
312
+ const available = availableOutputs.map(output => ({
313
+ ...output,
314
+ // An orphaned/missing status is never promoted ahead of known settled
315
+ // ancestry. Retain it only in the final last-resort reservation tier.
316
+ transactionStatus: availableStatuses.get(output.transactionId) ?? 'sending'
317
+ }));
269
318
  const target = estimateFirstActionTarget(storage, args.firstAction, explicit.inputSatoshis + noSendChange.inputSatoshis, outputScriptLengths);
270
- const fixedOutputs = [...explicit.outputs, ...noSendChange.outputs];
271
- const requiredCapacity = Math.max(0, INITIAL_RESERVATION_LIMIT - fixedOutputs.length);
319
+ const fixedOutputs = [
320
+ ...new Map([...explicit.outputs, ...noSendChange.outputs].map(output => [output.outputId, output])).values()
321
+ ];
322
+ const maxReservedOutputs = storage.actionBatchMaxReservedOutputs;
323
+ if (maxReservedOutputs >= 0 && fixedOutputs.length > maxReservedOutputs) {
324
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('firstAction', `no more than ${maxReservedOutputs} persisted inputs`);
325
+ }
326
+ const initialCapacity = maxReservedOutputs < 0 ? INITIAL_RESERVATION_LIMIT : Math.min(INITIAL_RESERVATION_LIMIT, maxReservedOutputs);
327
+ const requiredCapacity = Math.max(0, initialCapacity - fixedOutputs.length);
272
328
  const funding = chooseReservationPool(available, target, requiredCapacity, INITIAL_EXTRA_OUTPUTS, false, reservationPlanningCosts(storage, changeBasket));
329
+ const fundingOutputs = funding.map(reservationOutput);
273
330
  const batch = newBatch(userId, args.batchId);
274
331
  await storage.transaction(async (trx) => {
275
332
  await storage.insertActionBatch(batch, trx);
276
- await reserveOutputs(storage, batch, [...fixedOutputs, ...funding], trx);
333
+ await reserveOutputs(storage, batch, [...fixedOutputs, ...fundingOutputs], trx);
277
334
  });
278
335
  let fundingResult;
279
336
  try {
280
- fundingResult = await makeFundingResult(storage, args.firstAction, [...funding, ...fixedOutputs]);
337
+ fundingResult = await makeFundingResult(storage, args.firstAction, [...fundingOutputs, ...fixedOutputs]);
281
338
  }
282
339
  catch (error) {
283
340
  await storage.transaction(async (trx) => await releaseBatchState(storage, batch, 'aborted', trx));
@@ -292,55 +349,110 @@ async function beginActionBatch(storage, auth, args) {
292
349
  feeModel: (0, StorageProvider_1.validateStorageFeeModel)(storage.feeModel),
293
350
  commissionSatoshis: storage.commissionSatoshis,
294
351
  commissionPubKeyHex: storage.commissionPubKeyHex,
295
- availableChangeCount: available.length,
296
- reservedOutputs: fundingResult.outputs.filter(output => funding.some(candidate => candidate.outputId === output.outputId)),
352
+ availableChangeCount: available.filter(output => output.satoshis >= Math.max(1, changeBasket.minimumDesiredUTXOValue)).length,
353
+ managedChangePolicy: {
354
+ maxOutputsPerAction: storage.managedChangePolicy.maxOutputsPerAction,
355
+ migrationInputsPerAction: storage.managedChangePolicy.migrationInputsPerAction
356
+ },
357
+ reservedOutputs: fundingResult.outputs.filter(output => fundingOutputs.some(candidate => candidate.outputId === output.outputId)),
297
358
  explicitOutputs: fundingResult.outputs.filter(output => explicitIds.has(output.outputId)),
298
359
  inputBeef: fundingResult.beef
299
360
  };
300
361
  }
301
- function requireLiveBatch(batch) {
302
- if (batch == null || activeExpiry(batch))
303
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch is not active');
362
+ function requireLiveBatch(batch, batchId) {
363
+ const state = actionBatchErrorState(batch);
364
+ if (state != null)
365
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE(state, batchId ?? batch?.batchId);
366
+ if (batch == null)
367
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('missing', batchId);
304
368
  return batch;
305
369
  }
306
370
  async function extendActionBatch(storage, auth, args) {
307
371
  const userId = (0, utilityHelpers_1.verifyId)(auth.userId);
308
372
  await cleanupExpiredActionBatches(storage);
309
- const batch = requireLiveBatch(await storage.findActionBatch(userId, args.batchId));
373
+ const batch = requireLiveBatch(await storage.findActionBatch(userId, args.batchId), args.batchId);
310
374
  const basket = (0, utilityHelpers_1.verifyOne)(await storage.findOutputBaskets({ partial: { userId, name: 'default' } }));
311
375
  const alreadyReserved = await storage.findActionBatchOutputIds(batch.actionBatchId);
312
- const available = await storage.findAvailableManagedChangeInputs(userId, basket.basketId, false);
313
376
  if (!Number.isSafeInteger(args.requestedOutputs) || args.requestedOutputs < 0) {
314
377
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('requestedOutputs', 'non-negative safe integer');
315
378
  }
316
- const requestedCount = Math.min(args.requestedOutputs, exports.ACTION_BATCH_MAX_RESERVATION_EXTENSION_OUTPUTS);
317
- const funding = chooseReservationPool(available, Math.max(1, args.targetSatoshis), requestedCount, 0, true, reservationPlanningCosts(storage, basket));
379
+ if (!Number.isSafeInteger(args.targetSatoshis) || args.targetSatoshis < 0) {
380
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('targetSatoshis', 'non-negative safe integer');
381
+ }
382
+ const maxReservedOutputs = storage.actionBatchMaxReservedOutputs;
383
+ if ((maxReservedOutputs >= 0 && args.explicitOutpoints.length > maxReservedOutputs) ||
384
+ new Set(args.explicitOutpoints.map(outpoint => `${outpoint.txid}.${outpoint.vout}`)).size !==
385
+ args.explicitOutpoints.length ||
386
+ args.explicitOutpoints.some(outpoint => !isValidOutpoint(outpoint))) {
387
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('explicitOutpoints', `at most ${maxReservedOutputs < 0 ? 'the configured request capacity' : maxReservedOutputs} valid outpoints`);
388
+ }
389
+ const remainingCapacity = maxReservedOutputs < 0 ? Number.MAX_SAFE_INTEGER : maxReservedOutputs - alreadyReserved.length;
390
+ if (remainingCapacity <= 0 && (args.requestedOutputs > 0 || args.explicitOutpoints.length > 0)) {
391
+ throw new WERR_errors_1.WERR_INVALID_OPERATION(`action batch already holds the maximum of ${maxReservedOutputs} outputs`);
392
+ }
318
393
  const explicitByOutpoint = await storage.findOutputsByOutpoints(userId, args.explicitOutpoints);
319
- const explicit = Object.values(explicitByOutpoint)
320
- .filter(output => !alreadyReserved.includes(output.outputId));
394
+ const explicit = [
395
+ ...new Map(Object.values(explicitByOutpoint)
396
+ .filter(output => !alreadyReserved.includes(output.outputId))
397
+ .map(output => [output.outputId, output])).values()
398
+ ];
399
+ if (explicit.length > remainingCapacity) {
400
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('explicitOutpoints', `no more than ${remainingCapacity} additional reserved outputs`);
401
+ }
402
+ const explicitIds = new Set(explicit.map(output => output.outputId));
403
+ const availableOutputs = (await storage.findAvailableManagedChangeInputs(userId, basket.basketId, false)).filter(output => !explicitIds.has(output.outputId));
404
+ const availableStatuses = await storage.findTransactionStatusesByIds(userId, availableOutputs.map(output => output.transactionId));
405
+ const available = availableOutputs.map(output => ({
406
+ ...output,
407
+ transactionStatus: availableStatuses.get(output.transactionId) ?? 'sending'
408
+ }));
409
+ const requestedCount = Math.min(args.requestedOutputs, exports.ACTION_BATCH_MAX_RESERVATION_EXTENSION_OUTPUTS, remainingCapacity - explicit.length);
410
+ const funding = chooseReservationPool(available, Math.max(1, args.targetSatoshis), requestedCount, 0, true, reservationPlanningCosts(storage, basket));
411
+ const fundingOutputs = funding.map(reservationOutput);
321
412
  const fundingShape = argsToFundingShape(args.includeSourceTransactions);
322
- const fundingResult = await makeFundingResult(storage, fundingShape, [...funding, ...explicit]);
413
+ const fundingResult = await makeFundingResult(storage, fundingShape, [...fundingOutputs, ...explicit]);
323
414
  const expiresAt = new Date(Math.min(batch.hardExpiresAt.getTime(), Date.now() + exports.ACTION_BATCH_LEASE_MS));
324
415
  await storage.transaction(async (trx) => {
325
- const current = requireLiveBatch(await storage.findActionBatchForUpdate(userId, args.batchId, trx));
326
- await reserveOutputs(storage, current, [...funding, ...explicit], trx);
416
+ const current = requireLiveBatch(await storage.findActionBatchForUpdate(userId, args.batchId, trx), args.batchId);
417
+ const additions = [...new Map([...fundingOutputs, ...explicit].map(output => [output.outputId, output])).values()];
418
+ const currentReserved = new Set(await storage.findActionBatchOutputIds(current.actionBatchId, trx));
419
+ const newAdditions = additions.filter(output => !currentReserved.has(output.outputId));
420
+ if (maxReservedOutputs >= 0 && currentReserved.size + newAdditions.length > maxReservedOutputs) {
421
+ throw new WERR_errors_1.WERR_INVALID_OPERATION(`action batch cannot reserve more than ${maxReservedOutputs} outputs`);
422
+ }
423
+ await reserveOutputs(storage, current, newAdditions, trx);
327
424
  await storage.updateActionBatch(current.actionBatchId, { expiresAt }, trx);
328
425
  });
329
426
  return {
330
427
  expiresAt: expiresAt.toISOString(),
331
- reservedOutputs: fundingResult.outputs.filter(output => funding.some(candidate => candidate.outputId === output.outputId)),
428
+ reservedOutputs: fundingResult.outputs.filter(output => fundingOutputs.some(candidate => candidate.outputId === output.outputId)),
332
429
  explicitOutputs: fundingResult.outputs.filter(output => explicit.some(candidate => candidate.outputId === output.outputId)),
333
430
  inputBeef: fundingResult.beef
334
431
  };
335
432
  }
336
433
  function argsToFundingShape(includeSourceTransactions) {
337
434
  return {
338
- inputs: [], outputs: [], labels: [], description: 'action batch extension', version: 1, lockTime: 0,
435
+ inputs: [],
436
+ outputs: [],
437
+ labels: [],
438
+ description: 'action batch extension',
439
+ version: 1,
440
+ lockTime: 0,
339
441
  options: {
340
- acceptDelayedBroadcast: true, returnTXIDOnly: false, noSend: true, sendWith: [], signAndProcess: true,
341
- knownTxids: [], noSendChange: [], randomizeOutputs: true
442
+ acceptDelayedBroadcast: true,
443
+ returnTXIDOnly: false,
444
+ noSend: true,
445
+ sendWith: [],
446
+ signAndProcess: true,
447
+ knownTxids: [],
448
+ noSendChange: [],
449
+ randomizeOutputs: true
342
450
  },
343
- isSendWith: false, isNewTx: true, isRemixChange: false, isNoSend: true, isDelayed: true,
451
+ isSendWith: false,
452
+ isNewTx: true,
453
+ isRemixChange: false,
454
+ isNoSend: true,
455
+ isDelayed: true,
344
456
  isTestWerrReviewActions: false,
345
457
  isSignAction: includeSourceTransactions,
346
458
  includeAllSourceTransactions: includeSourceTransactions
@@ -349,24 +461,95 @@ function argsToFundingShape(includeSourceTransactions) {
349
461
  async function renewActionBatch(storage, auth, batchId) {
350
462
  const userId = (0, utilityHelpers_1.verifyId)(auth.userId);
351
463
  return await storage.transaction(async (trx) => {
352
- const batch = requireLiveBatch(await storage.findActionBatchForUpdate(userId, batchId, trx));
464
+ const batch = requireLiveBatch(await storage.findActionBatchForUpdate(userId, batchId, trx), batchId);
353
465
  const expiresAt = new Date(Math.min(batch.hardExpiresAt.getTime(), Date.now() + exports.ACTION_BATCH_LEASE_MS));
354
466
  await storage.updateActionBatch(batch.actionBatchId, { expiresAt }, trx);
355
467
  return { expiresAt: expiresAt.toISOString() };
356
468
  });
357
469
  }
470
+ function validateResumeOutpoints(storage, args) {
471
+ const unique = new Set(args.outpoints.map(outpoint => `${outpoint.txid}.${outpoint.vout}`));
472
+ const maxReservedOutputs = storage.actionBatchMaxReservedOutputs;
473
+ if ((maxReservedOutputs >= 0 && args.outpoints.length > maxReservedOutputs) ||
474
+ unique.size !== args.outpoints.length ||
475
+ args.outpoints.some(outpoint => !isValidOutpoint(outpoint))) {
476
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('outpoints', `at most ${maxReservedOutputs < 0 ? 'the configured request capacity' : maxReservedOutputs} unique valid outpoints`);
477
+ }
478
+ }
479
+ /**
480
+ * Reacquire exactly the persisted inputs owned by one expired client
481
+ * workspace. This is deliberately explicit: another action may not become a
482
+ * member merely because it happens to use the same Wallet instance.
483
+ */
484
+ async function resumeActionBatch(storage, auth, args) {
485
+ const userId = (0, utilityHelpers_1.verifyId)(auth.userId);
486
+ validateResumeOutpoints(storage, args);
487
+ await cleanupExpiredActionBatches(storage);
488
+ return await storage.transaction(async (trx) => {
489
+ const batch = await storage.findActionBatchForUpdate(userId, args.batchId, trx);
490
+ const state = actionBatchErrorState(batch);
491
+ if (state != null && state !== 'expired') {
492
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE(state, args.batchId);
493
+ }
494
+ if (batch == null)
495
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('missing', args.batchId);
496
+ const expiresAt = new Date(Math.min(batch.hardExpiresAt.getTime(), Date.now() + exports.ACTION_BATCH_LEASE_MS));
497
+ if (state == null) {
498
+ const reserved = new Set(await storage.findActionBatchOutputIds(batch.actionBatchId, trx));
499
+ const current = await storage.findOutputsByOutpointsForUpdate(userId, args.outpoints, trx);
500
+ if (Object.values(current).some(output => !reserved.has(output.outputId)) ||
501
+ Object.keys(current).length !== args.outpoints.length ||
502
+ reserved.size !== args.outpoints.length) {
503
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('conflicted', args.batchId);
504
+ }
505
+ await storage.updateActionBatch(batch.actionBatchId, { expiresAt }, trx);
506
+ return { expiresAt: expiresAt.toISOString() };
507
+ }
508
+ const stored = await storage.findOutputsByOutpointsForUpdate(userId, args.outpoints, trx);
509
+ const outputs = args.outpoints.map(outpoint => stored[`${outpoint.txid}.${outpoint.vout}`]);
510
+ if (outputs.some(output => output == null || !output.spendable || output.spentBy != null)) {
511
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('conflicted', args.batchId);
512
+ }
513
+ await storage.deleteActionBatchOutputReservations(batch.actionBatchId, trx);
514
+ const exactOutputs = outputs;
515
+ const conflicts = await storage.findReservedActionBatchOutputIds(exactOutputs.map(output => output.outputId), trx);
516
+ if (conflicts.length > 0)
517
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('conflicted', args.batchId);
518
+ const now = new Date();
519
+ await storage.reserveActionBatchOutputs(exactOutputs.map(output => ({
520
+ actionBatchId: batch.actionBatchId,
521
+ outputId: output.outputId,
522
+ created_at: now,
523
+ updated_at: now
524
+ })), trx);
525
+ await storage.updateActionBatch(batch.actionBatchId, {
526
+ status: 'active',
527
+ expiresAt,
528
+ manifest: undefined,
529
+ manifestDigest: undefined,
530
+ uploadDigests: undefined
531
+ }, trx);
532
+ return { expiresAt: expiresAt.toISOString() };
533
+ });
534
+ }
358
535
  async function reacquireManifestInputs(storage, userId, batch, validated, trx) {
359
536
  if (batch.hardExpiresAt.getTime() <= Date.now()) {
360
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch hard lifetime has expired');
537
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('hard-expired', batch.batchId);
361
538
  }
362
539
  const stagedTxids = new Set(validated.actions.map(({ action }) => action.txid));
363
- const outpoints = [...new Map(validated.actions.flatMap(({ action }) => action.plan.inputs)
540
+ const outpoints = [
541
+ ...new Map(validated.actions
542
+ .flatMap(({ action }) => action.plan.inputs)
364
543
  .filter(input => !stagedTxids.has(input.sourceTxid))
365
- .map(input => [`${input.sourceTxid}.${input.sourceVout}`, {
544
+ .map(input => [
545
+ `${input.sourceTxid}.${input.sourceVout}`,
546
+ {
366
547
  txid: input.sourceTxid,
367
548
  vout: input.sourceVout,
368
549
  providedBy: input.providedBy
369
- }])).values()];
550
+ }
551
+ ])).values()
552
+ ];
370
553
  const stored = await storage.findOutputsByOutpointsForUpdate(userId, outpoints, trx);
371
554
  await storage.deleteActionBatchOutputReservations(batch.actionBatchId, trx);
372
555
  const outputs = [];
@@ -545,7 +728,7 @@ async function persistManifestAtomically(storage, userId, batch, manifest, valid
545
728
  return await storage.transaction(async (trx) => {
546
729
  const current = await storage.findActionBatchForUpdate(userId, batch.batchId, trx);
547
730
  if (current == null)
548
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch was not found');
731
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('missing', batch.batchId);
549
732
  if (current.status === 'committed') {
550
733
  if (current.manifestDigest !== manifest.digest) {
551
734
  throw new WERR_errors_1.WERR_INVALID_OPERATION('batch committed with another manifest');
@@ -553,30 +736,36 @@ async function persistManifestAtomically(storage, userId, batch, manifest, valid
553
736
  return { batch: current, alreadyCommitted: true };
554
737
  }
555
738
  if (current.status === 'aborted')
556
- throw new WERR_errors_1.WERR_INVALID_OPERATION('aborted action batch cannot be committed');
739
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('aborted', batch.batchId);
557
740
  if (current.manifestDigest != null && current.manifestDigest !== manifest.digest) {
558
741
  throw new WERR_errors_1.WERR_INVALID_OPERATION('batch prepared with another manifest');
559
742
  }
560
743
  const needsReacquire = current.status === 'expired' || activeExpiry(current);
561
744
  if (needsReacquire) {
562
745
  if (current.hardExpiresAt.getTime() <= Date.now()) {
563
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch hard lifetime has expired');
746
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('hard-expired', batch.batchId);
564
747
  }
565
748
  await reacquireManifestInputs(storage, userId, current, validated, trx);
566
749
  }
567
750
  else {
568
- requireLiveBatch(current);
751
+ requireLiveBatch(current, batch.batchId);
569
752
  }
570
753
  const reservedOutputIds = new Set(await storage.findActionBatchOutputIds(current.actionBatchId, trx));
571
754
  const stagedTxids = new Set(validated.actions.map(({ action }) => action.txid));
572
- const inputOutpoints = [...new Map(validated.actions.flatMap(({ action }) => action.plan.inputs)
755
+ const inputOutpoints = [
756
+ ...new Map(validated.actions
757
+ .flatMap(({ action }) => action.plan.inputs)
573
758
  .filter(input => !stagedTxids.has(input.sourceTxid))
574
- .map(input => [`${input.sourceTxid}.${input.sourceVout}`, {
759
+ .map(input => [
760
+ `${input.sourceTxid}.${input.sourceVout}`,
761
+ {
575
762
  txid: input.sourceTxid,
576
763
  vout: input.sourceVout
577
- }])).values()];
764
+ }
765
+ ])).values()
766
+ ];
578
767
  const storedByOutpoint = await storage.findOutputsByOutpointsForUpdate(userId, inputOutpoints, trx);
579
- const allBasketNames = validated.actions.flatMap(({ action }) => action.plan.outputs.flatMap(output => output.basket == null ? [] : [output.basket]));
768
+ const allBasketNames = validated.actions.flatMap(({ action }) => action.plan.outputs.flatMap(output => (output.basket == null ? [] : [output.basket])));
580
769
  if (validated.actions.some(({ action }) => action.plan.outputs.some(output => output.purpose === 'change')))
581
770
  allBasketNames.push('default');
582
771
  const baskets = await storage.findOrInsertOutputBasketsBulk(userId, [...new Set(allBasketNames)], trx);
@@ -651,19 +840,19 @@ async function completeCommittedBatch(storage, userId, batch, manifest, alreadyC
651
840
  async function commitActionBatchOnce(storage, userId, manifest) {
652
841
  const batch = await storage.findActionBatch(userId, manifest.batchId);
653
842
  if (batch == null)
654
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch was not found');
843
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('missing', manifest.batchId);
655
844
  if (batch.status === 'committed') {
656
845
  if (batch.manifestDigest !== manifest.digest)
657
846
  throw new WERR_errors_1.WERR_INVALID_OPERATION('batch committed with another manifest');
658
847
  return await completeCommittedBatch(storage, userId, batch, manifest, true);
659
848
  }
660
849
  if (batch.status === 'aborted')
661
- throw new WERR_errors_1.WERR_INVALID_OPERATION('aborted action batch cannot be committed');
850
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('aborted', manifest.batchId);
662
851
  const needsReacquire = batch.status === 'expired' || activeExpiry(batch);
663
852
  if (!needsReacquire)
664
- requireLiveBatch(batch);
853
+ requireLiveBatch(batch, manifest.batchId);
665
854
  else if (batch.hardExpiresAt.getTime() <= Date.now()) {
666
- throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch hard lifetime has expired');
855
+ throw new WERR_errors_1.WERR_ACTION_BATCH_STATE('hard-expired', manifest.batchId);
667
856
  }
668
857
  if (batch.manifestDigest != null && batch.manifestDigest !== manifest.digest) {
669
858
  throw new WERR_errors_1.WERR_INVALID_OPERATION('batch prepared with another manifest');
@@ -692,8 +881,9 @@ async function commitActionBatch(storage, auth, manifest) {
692
881
  }
693
882
  return await active.promise;
694
883
  }
695
- const promise = commitActionBatchOnce(storage, userId, manifest)
696
- .finally(() => { commits?.delete(key); });
884
+ const promise = commitActionBatchOnce(storage, userId, manifest).finally(() => {
885
+ commits?.delete(key);
886
+ });
697
887
  commits.set(key, { digest: manifest.digest, promise });
698
888
  return await promise;
699
889
  }
@@ -704,7 +894,9 @@ async function commitActionBatchByDigest(storage, auth, args) {
704
894
  throw new WERR_errors_1.WERR_INVALID_OPERATION('prepared action batch manifest was not found');
705
895
  }
706
896
  const manifest = JSON.parse(batch.manifest);
707
- if (manifest.format !== 2 || manifest.batchId !== args.batchId || manifest.digest !== args.digest ||
897
+ if (manifest.format !== 2 ||
898
+ manifest.batchId !== args.batchId ||
899
+ manifest.digest !== args.digest ||
708
900
  !(0, actionBatchDigest_1.verifyActionBatchManifestDigest)(manifest)) {
709
901
  throw new WERR_errors_1.WERR_INVALID_OPERATION('prepared action batch manifest is invalid');
710
902
  }