@bsv/wallet-toolbox-mobile 2.4.3 → 2.4.4
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.
- package/out/src/CWIStyleWalletManager.d.ts +52 -5
- package/out/src/CWIStyleWalletManager.d.ts.map +1 -1
- package/out/src/CWIStyleWalletManager.js +500 -128
- package/out/src/CWIStyleWalletManager.js.map +1 -1
- package/out/src/Wallet.d.ts +8 -0
- package/out/src/Wallet.d.ts.map +1 -1
- package/out/src/Wallet.js +23 -9
- package/out/src/Wallet.js.map +1 -1
- package/out/src/WalletAuthenticationManager.d.ts +20 -6
- package/out/src/WalletAuthenticationManager.d.ts.map +1 -1
- package/out/src/WalletAuthenticationManager.js +214 -34
- package/out/src/WalletAuthenticationManager.js.map +1 -1
- package/out/src/index.mobile.d.ts +1 -0
- package/out/src/index.mobile.d.ts.map +1 -1
- package/out/src/index.mobile.js +1 -0
- package/out/src/index.mobile.js.map +1 -1
- package/out/src/monitor/Monitor.d.ts.map +1 -1
- package/out/src/monitor/Monitor.js +4 -3
- package/out/src/monitor/Monitor.js.map +1 -1
- package/out/src/monitor/tasks/TaskCleanupActionBatches.d.ts +13 -0
- package/out/src/monitor/tasks/TaskCleanupActionBatches.d.ts.map +1 -0
- package/out/src/monitor/tasks/TaskCleanupActionBatches.js +24 -0
- package/out/src/monitor/tasks/TaskCleanupActionBatches.js.map +1 -0
- package/out/src/sdk/ActionBatch.interfaces.d.ts +105 -0
- package/out/src/sdk/ActionBatch.interfaces.d.ts.map +1 -0
- package/out/src/sdk/ActionBatch.interfaces.js +3 -0
- package/out/src/sdk/ActionBatch.interfaces.js.map +1 -0
- package/out/src/sdk/WalletStorage.interfaces.d.ts +18 -1
- package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
- package/out/src/sdk/index.d.ts +1 -0
- package/out/src/sdk/index.d.ts.map +1 -1
- package/out/src/sdk/index.js +1 -0
- package/out/src/sdk/index.js.map +1 -1
- package/out/src/signer/actionBatch/ActionBatchPlanner.d.ts +28 -0
- package/out/src/signer/actionBatch/ActionBatchPlanner.d.ts.map +1 -0
- package/out/src/signer/actionBatch/ActionBatchPlanner.js +313 -0
- package/out/src/signer/actionBatch/ActionBatchPlanner.js.map +1 -0
- package/out/src/signer/actionBatch/ActionBatchWorkspace.d.ts +33 -0
- package/out/src/signer/actionBatch/ActionBatchWorkspace.d.ts.map +1 -0
- package/out/src/signer/actionBatch/ActionBatchWorkspace.js +693 -0
- package/out/src/signer/actionBatch/ActionBatchWorkspace.js.map +1 -0
- package/out/src/signer/methods/buildSignableTransaction.d.ts.map +1 -1
- package/out/src/signer/methods/buildSignableTransaction.js +2 -1
- package/out/src/signer/methods/buildSignableTransaction.js.map +1 -1
- package/out/src/signer/methods/createAction.d.ts.map +1 -1
- package/out/src/signer/methods/createAction.js +4 -1
- package/out/src/signer/methods/createAction.js.map +1 -1
- package/out/src/storage/StorageProvider.d.ts +28 -0
- package/out/src/storage/StorageProvider.d.ts.map +1 -1
- package/out/src/storage/StorageProvider.js +49 -0
- package/out/src/storage/StorageProvider.js.map +1 -1
- package/out/src/storage/WalletStorageManager.d.ts +8 -0
- package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/out/src/storage/WalletStorageManager.js +24 -0
- package/out/src/storage/WalletStorageManager.js.map +1 -1
- package/out/src/storage/methods/actionBatch.d.ts +14 -0
- package/out/src/storage/methods/actionBatch.d.ts.map +1 -0
- package/out/src/storage/methods/actionBatch.js +644 -0
- package/out/src/storage/methods/actionBatch.js.map +1 -0
- package/out/src/storage/methods/actionBatchBlobs.d.ts +12 -0
- package/out/src/storage/methods/actionBatchBlobs.d.ts.map +1 -0
- package/out/src/storage/methods/actionBatchBlobs.js +121 -0
- package/out/src/storage/methods/actionBatchBlobs.js.map +1 -0
- package/out/src/storage/methods/actionBatchValidation.d.ts +15 -0
- package/out/src/storage/methods/actionBatchValidation.d.ts.map +1 -0
- package/out/src/storage/methods/actionBatchValidation.js +310 -0
- package/out/src/storage/methods/actionBatchValidation.js.map +1 -0
- package/out/src/storage/methods/actionPlanning.d.ts +12 -0
- package/out/src/storage/methods/actionPlanning.d.ts.map +1 -0
- package/out/src/storage/methods/actionPlanning.js +48 -0
- package/out/src/storage/methods/actionPlanning.js.map +1 -0
- package/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/out/src/storage/methods/createAction.js +9 -25
- package/out/src/storage/methods/createAction.js.map +1 -1
- package/out/src/storage/remoting/StorageClientBase.d.ts +9 -0
- package/out/src/storage/remoting/StorageClientBase.d.ts.map +1 -1
- package/out/src/storage/remoting/StorageClientBase.js +36 -1
- package/out/src/storage/remoting/StorageClientBase.js.map +1 -1
- package/out/src/storage/schema/tables/TableActionBatch.d.ts +24 -0
- package/out/src/storage/schema/tables/TableActionBatch.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableActionBatch.js +3 -0
- package/out/src/storage/schema/tables/TableActionBatch.js.map +1 -0
- package/out/src/storage/schema/tables/index.d.ts +1 -0
- package/out/src/storage/schema/tables/index.d.ts.map +1 -1
- package/out/src/storage/schema/tables/index.js +1 -0
- package/out/src/storage/schema/tables/index.js.map +1 -1
- package/out/src/utility/actionBatchDigest.d.ts +9 -0
- package/out/src/utility/actionBatchDigest.d.ts.map +1 -0
- package/out/src/utility/actionBatchDigest.js +48 -0
- package/out/src/utility/actionBatchDigest.js.map +1 -0
- package/out/src/utility/beefForTxids.d.ts +9 -0
- package/out/src/utility/beefForTxids.d.ts.map +1 -0
- package/out/src/utility/beefForTxids.js +35 -0
- package/out/src/utility/beefForTxids.js.map +1 -0
- package/out/src/wab-client/WABClient.d.ts +36 -90
- package/out/src/wab-client/WABClient.d.ts.map +1 -1
- package/out/src/wab-client/WABClient.js +103 -151
- package/out/src/wab-client/WABClient.js.map +1 -1
- package/out/src/wab-client/WABTransport.d.ts +53 -0
- package/out/src/wab-client/WABTransport.d.ts.map +1 -0
- package/out/src/wab-client/WABTransport.js +300 -0
- package/out/src/wab-client/WABTransport.js.map +1 -0
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts +10 -10
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts.map +1 -1
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js +16 -21
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js.map +1 -1
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts +2 -1
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts.map +1 -1
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js +14 -0
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js.map +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ActionBatchController = void 0;
|
|
4
|
+
exports.additionalFundingTarget = additionalFundingTarget;
|
|
5
|
+
exports.fundingRunwayExtension = fundingRunwayExtension;
|
|
6
|
+
const sdk_1 = require("@bsv/sdk");
|
|
7
|
+
const WERR_errors_1 = require("../../sdk/WERR_errors");
|
|
8
|
+
const utilityHelpers_1 = require("../../utility/utilityHelpers");
|
|
9
|
+
const utilityHelpers_noBuffer_1 = require("../../utility/utilityHelpers.noBuffer");
|
|
10
|
+
const types_1 = require("../../sdk/types");
|
|
11
|
+
const actionBatchDigest_1 = require("../../utility/actionBatchDigest");
|
|
12
|
+
const ActionBatchPlanner_1 = require("./ActionBatchPlanner");
|
|
13
|
+
const beefForTxids_1 = require("../../utility/beefForTxids");
|
|
14
|
+
function mergeUnique(values) {
|
|
15
|
+
return [...new Set(values)];
|
|
16
|
+
}
|
|
17
|
+
function nextGeometricTarget(value) {
|
|
18
|
+
return Math.min(Number.MAX_SAFE_INTEGER, value * 2);
|
|
19
|
+
}
|
|
20
|
+
function additionalFundingTarget(error) {
|
|
21
|
+
return error.moreSatoshisNeeded;
|
|
22
|
+
}
|
|
23
|
+
function fundingRunwayExtension(runwayTarget, ewmaConfirmedInputs, ewmaConfirmedSatoshis, availableConfirmed) {
|
|
24
|
+
const availableConfirmedCount = availableConfirmed.length;
|
|
25
|
+
const availableConfirmedSatoshis = availableConfirmed
|
|
26
|
+
.reduce((sum, output) => sum + output.satoshis, 0);
|
|
27
|
+
const predictedByInputs = availableConfirmedCount / Math.max(1, ewmaConfirmedInputs);
|
|
28
|
+
const predictedBySatoshis = ewmaConfirmedSatoshis > 0
|
|
29
|
+
? availableConfirmedSatoshis / ewmaConfirmedSatoshis
|
|
30
|
+
: Number.POSITIVE_INFINITY;
|
|
31
|
+
if (Math.min(predictedByInputs, predictedBySatoshis) >= 2)
|
|
32
|
+
return undefined;
|
|
33
|
+
const nextRunwayTarget = nextGeometricTarget(runwayTarget);
|
|
34
|
+
return {
|
|
35
|
+
nextRunwayTarget,
|
|
36
|
+
requestedOutputs: Math.max(1, Math.ceil(nextRunwayTarget * ewmaConfirmedInputs) - availableConfirmedCount),
|
|
37
|
+
targetSatoshis: Math.max(1, nextRunwayTarget * ewmaConfirmedSatoshis - availableConfirmedSatoshis)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function makePlannerState(begin, firstAction) {
|
|
41
|
+
const sharedBeef = new sdk_1.Beef();
|
|
42
|
+
if (begin.inputBeef != null)
|
|
43
|
+
sharedBeef.mergeBeef(begin.inputBeef);
|
|
44
|
+
if (firstAction.inputBEEF != null)
|
|
45
|
+
sharedBeef.mergeBeef(firstAction.inputBEEF);
|
|
46
|
+
for (const output of [...begin.reservedOutputs, ...begin.explicitOutputs]) {
|
|
47
|
+
if (output.sourceTransaction != null)
|
|
48
|
+
sharedBeef.mergeRawTx(output.sourceTransaction);
|
|
49
|
+
}
|
|
50
|
+
const state = {
|
|
51
|
+
begin,
|
|
52
|
+
sharedBeef,
|
|
53
|
+
reserved: new Map(),
|
|
54
|
+
explicit: new Map(),
|
|
55
|
+
staged: new Map(),
|
|
56
|
+
discardedStagedTxids: new Set(),
|
|
57
|
+
consumed: new Set(),
|
|
58
|
+
estimatedChangeCount: begin.availableChangeCount
|
|
59
|
+
};
|
|
60
|
+
(0, ActionBatchPlanner_1.addPlannerOutputs)(state.reserved, begin.reservedOutputs, begin.changeBasket.name);
|
|
61
|
+
(0, ActionBatchPlanner_1.addPlannerOutputs)(state.explicit, begin.explicitOutputs);
|
|
62
|
+
return state;
|
|
63
|
+
}
|
|
64
|
+
class ActionBatchWorkspace {
|
|
65
|
+
wallet;
|
|
66
|
+
capabilities;
|
|
67
|
+
batchId;
|
|
68
|
+
state;
|
|
69
|
+
planned = new Map();
|
|
70
|
+
actions = [];
|
|
71
|
+
lockingScripts = new Map();
|
|
72
|
+
ewmaConfirmedInputs = 1;
|
|
73
|
+
ewmaConfirmedSatoshis = 0;
|
|
74
|
+
hasConfirmedSample = false;
|
|
75
|
+
runwayTarget = 4;
|
|
76
|
+
extension;
|
|
77
|
+
renewal;
|
|
78
|
+
expiresAt;
|
|
79
|
+
committed = false;
|
|
80
|
+
constructor(wallet, begin, firstAction, capabilities) {
|
|
81
|
+
this.wallet = wallet;
|
|
82
|
+
this.capabilities = capabilities;
|
|
83
|
+
this.batchId = begin.batchId;
|
|
84
|
+
this.state = makePlannerState(begin, firstAction);
|
|
85
|
+
this.expiresAt = Date.parse(begin.expiresAt);
|
|
86
|
+
}
|
|
87
|
+
ownsReference(reference) {
|
|
88
|
+
if (this.planned.has(reference))
|
|
89
|
+
return true;
|
|
90
|
+
if (this.actions.some(action => action.reference === reference || action.txid === reference))
|
|
91
|
+
return true;
|
|
92
|
+
return Object.entries(this.wallet.pendingSignActions)
|
|
93
|
+
.some(([pendingReference, pending]) => this.planned.has(pendingReference) && pending.tx.id('hex') === reference);
|
|
94
|
+
}
|
|
95
|
+
get isEmpty() {
|
|
96
|
+
return this.planned.size === 0 && this.actions.length === 0;
|
|
97
|
+
}
|
|
98
|
+
overlayListActions(persisted, args) {
|
|
99
|
+
if (args.labels.includes(types_1.specOpFailedActions))
|
|
100
|
+
return persisted;
|
|
101
|
+
const controlLabels = new Set(args.labels.includes(types_1.specOpNoSendActions) ? ['abort'] : []);
|
|
102
|
+
const ordinaryLabels = args.labels.filter(label => !(0, types_1.isListActionsSpecOp)(label) && !controlLabels.has(label));
|
|
103
|
+
const staged = this.actions
|
|
104
|
+
.filter(action => {
|
|
105
|
+
if (ordinaryLabels.length === 0)
|
|
106
|
+
return true;
|
|
107
|
+
const matches = ordinaryLabels.filter(label => action.metadata.labels.includes(label)).length;
|
|
108
|
+
return args.labelQueryMode === 'all' ? matches === ordinaryLabels.length : matches > 0;
|
|
109
|
+
})
|
|
110
|
+
.map(action => {
|
|
111
|
+
const tx = this.state.sharedBeef.findTxid(action.txid)?.tx;
|
|
112
|
+
if (tx == null)
|
|
113
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`missing staged transaction ${action.txid}`);
|
|
114
|
+
const managedInputSatoshis = action.plan.inputs
|
|
115
|
+
.filter(input => input.vin >= action.metadata.inputs.length)
|
|
116
|
+
.reduce((sum, input) => sum + input.sourceSatoshis, 0);
|
|
117
|
+
const changeOutputSatoshis = action.plan.outputs
|
|
118
|
+
.filter(output => output.purpose === 'change')
|
|
119
|
+
.reduce((sum, output) => sum + output.satoshis, 0);
|
|
120
|
+
return {
|
|
121
|
+
txid: action.txid,
|
|
122
|
+
satoshis: changeOutputSatoshis - managedInputSatoshis,
|
|
123
|
+
status: 'nosend',
|
|
124
|
+
isOutgoing: true,
|
|
125
|
+
description: action.metadata.description,
|
|
126
|
+
labels: args.includeLabels ? action.metadata.labels : undefined,
|
|
127
|
+
version: tx.version,
|
|
128
|
+
lockTime: tx.lockTime,
|
|
129
|
+
inputs: args.includeInputs
|
|
130
|
+
? action.plan.inputs.map(input => ({
|
|
131
|
+
sourceOutpoint: `${input.sourceTxid}.${input.sourceVout}`,
|
|
132
|
+
sourceSatoshis: input.sourceSatoshis,
|
|
133
|
+
sourceLockingScript: args.includeInputSourceLockingScripts ? input.sourceLockingScript : undefined,
|
|
134
|
+
unlockingScript: args.includeInputUnlockingScripts
|
|
135
|
+
? tx.inputs[input.vin]?.unlockingScript?.toHex()
|
|
136
|
+
: undefined,
|
|
137
|
+
inputDescription: input.spendingDescription ?? '',
|
|
138
|
+
sequenceNumber: tx.inputs[input.vin]?.sequence ?? 0
|
|
139
|
+
}))
|
|
140
|
+
: undefined,
|
|
141
|
+
outputs: args.includeOutputs
|
|
142
|
+
? action.plan.outputs.map(output => ({
|
|
143
|
+
satoshis: output.satoshis,
|
|
144
|
+
lockingScript: args.includeOutputLockingScripts
|
|
145
|
+
? tx.outputs[output.vout]?.lockingScript.toHex()
|
|
146
|
+
: undefined,
|
|
147
|
+
spendable: output.purpose !== 'storage-commission',
|
|
148
|
+
customInstructions: undefined,
|
|
149
|
+
tags: output.tags,
|
|
150
|
+
outputIndex: output.vout,
|
|
151
|
+
outputDescription: output.outputDescription,
|
|
152
|
+
basket: output.basket ?? ''
|
|
153
|
+
}))
|
|
154
|
+
: undefined
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
const actions = [...persisted.actions, ...staged];
|
|
158
|
+
return {
|
|
159
|
+
totalActions: persisted.totalActions + staged.length,
|
|
160
|
+
actions: actions.slice(args.offset, args.offset + args.limit)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
overlayListOutputs(persisted, args) {
|
|
164
|
+
const isBalance = args.basket === types_1.specOpWalletBalance || args.tags.includes(types_1.specOpWalletBalance);
|
|
165
|
+
const managedOnly = args.basket === types_1.specOpWalletManagedUtxos || isBalance;
|
|
166
|
+
const consumed = this.state.consumed;
|
|
167
|
+
const stagedLabels = new Map(this.actions.map(action => [action.txid, action.metadata.labels]));
|
|
168
|
+
const staged = [...this.state.staged.entries()]
|
|
169
|
+
.filter(([outpoint, output]) => {
|
|
170
|
+
if (consumed.has(outpoint))
|
|
171
|
+
return false;
|
|
172
|
+
if (managedOnly)
|
|
173
|
+
return output.change;
|
|
174
|
+
if (output.basketName !== args.basket)
|
|
175
|
+
return false;
|
|
176
|
+
const tags = output.tags ?? [];
|
|
177
|
+
if (args.tags.length === 0)
|
|
178
|
+
return true;
|
|
179
|
+
const matches = args.tags.filter(tag => tags.includes(tag)).length;
|
|
180
|
+
return args.tagQueryMode === 'all' ? matches === args.tags.length : matches > 0;
|
|
181
|
+
})
|
|
182
|
+
.map(([outpoint, output]) => ({
|
|
183
|
+
satoshis: output.satoshis,
|
|
184
|
+
lockingScript: args.includeLockingScripts
|
|
185
|
+
? (0, utilityHelpers_noBuffer_1.asString)((0, ActionBatchPlanner_1.plannerOutputLockingScript)(this.state, output))
|
|
186
|
+
: undefined,
|
|
187
|
+
spendable: true,
|
|
188
|
+
customInstructions: args.includeCustomInstructions ? output.customInstructions : undefined,
|
|
189
|
+
tags: args.includeTags ? output.tags ?? [] : undefined,
|
|
190
|
+
outpoint,
|
|
191
|
+
labels: args.includeLabels && output.txid != null ? stagedLabels.get(output.txid) ?? [] : undefined
|
|
192
|
+
}));
|
|
193
|
+
if (isBalance) {
|
|
194
|
+
const consumedConfirmed = [...this.state.reserved.entries()]
|
|
195
|
+
.filter(([outpoint]) => consumed.has(outpoint))
|
|
196
|
+
.reduce((sum, [, output]) => sum + output.satoshis, 0);
|
|
197
|
+
return {
|
|
198
|
+
totalOutputs: persisted.totalOutputs - consumedConfirmed + staged.reduce((sum, output) => sum + output.satoshis, 0),
|
|
199
|
+
outputs: []
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const persistedUnconsumed = persisted.outputs.filter(output => !consumed.has(output.outpoint));
|
|
203
|
+
const outputs = [
|
|
204
|
+
...persistedUnconsumed,
|
|
205
|
+
...staged
|
|
206
|
+
];
|
|
207
|
+
const ordered = args.offset < 0 ? [...outputs].reverse() : outputs;
|
|
208
|
+
const offset = args.offset < 0 ? Math.max(0, -args.offset - 1) : args.offset;
|
|
209
|
+
const result = {
|
|
210
|
+
totalOutputs: persisted.totalOutputs - (persisted.outputs.length - persistedUnconsumed.length) + staged.length,
|
|
211
|
+
outputs: ordered.slice(offset, offset + args.limit)
|
|
212
|
+
};
|
|
213
|
+
if (args.includeTransactions) {
|
|
214
|
+
const beef = new sdk_1.Beef();
|
|
215
|
+
if (persisted.BEEF != null)
|
|
216
|
+
beef.mergeBeef(persisted.BEEF);
|
|
217
|
+
beef.mergeBeef(this.state.sharedBeef);
|
|
218
|
+
for (const txid of args.knownTxids)
|
|
219
|
+
if (beef.findTxid(txid) != null)
|
|
220
|
+
beef.makeTxidOnly(txid);
|
|
221
|
+
result.BEEF = beef.toUint8Array();
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
mergeExtension(extension) {
|
|
226
|
+
for (const output of [...extension.reservedOutputs, ...extension.explicitOutputs]) {
|
|
227
|
+
if (output.sourceTransaction != null) {
|
|
228
|
+
this.state.sharedBeef.mergeRawTx(output.sourceTransaction);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
(0, ActionBatchPlanner_1.addPlannerOutputs)(this.state.reserved, extension.reservedOutputs, this.state.begin.changeBasket.name);
|
|
232
|
+
(0, ActionBatchPlanner_1.addPlannerOutputs)(this.state.explicit, extension.explicitOutputs);
|
|
233
|
+
if (extension.inputBeef != null) {
|
|
234
|
+
this.state.sharedBeef.mergeBeef(extension.inputBeef);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
missingExplicitOutpoints(args) {
|
|
238
|
+
return [...args.inputs.map(input => input.outpoint), ...args.options.noSendChange]
|
|
239
|
+
.filter(outpoint => {
|
|
240
|
+
const key = `${outpoint.txid}.${outpoint.vout}`;
|
|
241
|
+
return !this.state.staged.has(key) && !this.state.explicit.has(key) &&
|
|
242
|
+
!this.state.reserved.has(key) && (this.state.discardedStagedTxids.has(outpoint.txid) ||
|
|
243
|
+
this.state.sharedBeef.findTxid(outpoint.txid)?.tx == null);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async extend(targetSatoshis, requestedOutputs, explicitOutpoints, includeSourceTransactions) {
|
|
247
|
+
const extension = await this.wallet.storage.extendActionBatch({
|
|
248
|
+
batchId: this.batchId,
|
|
249
|
+
targetSatoshis: Math.max(1, Math.ceil(targetSatoshis)),
|
|
250
|
+
requestedOutputs: Math.max(1, Math.ceil(requestedOutputs)),
|
|
251
|
+
explicitOutpoints,
|
|
252
|
+
includeSourceTransactions
|
|
253
|
+
});
|
|
254
|
+
this.mergeExtension(extension);
|
|
255
|
+
this.expiresAt = Date.parse(extension.expiresAt);
|
|
256
|
+
return {
|
|
257
|
+
outputCount: extension.reservedOutputs.length,
|
|
258
|
+
satoshis: extension.reservedOutputs.reduce((sum, output) => sum + output.satoshis, 0)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async renewIfNeeded() {
|
|
262
|
+
if (Date.now() >= this.expiresAt)
|
|
263
|
+
return;
|
|
264
|
+
const renewAt = this.expiresAt - this.capabilities.leaseMs * 0.2;
|
|
265
|
+
if (Date.now() < renewAt)
|
|
266
|
+
return;
|
|
267
|
+
this.renewal ??= this.wallet.storage.renewActionBatch(this.batchId)
|
|
268
|
+
.then(result => { this.expiresAt = Date.parse(result.expiresAt); })
|
|
269
|
+
.finally(() => { this.renewal = undefined; });
|
|
270
|
+
await this.renewal;
|
|
271
|
+
}
|
|
272
|
+
async plan(args) {
|
|
273
|
+
if (this.committed)
|
|
274
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION('action batch is already committed');
|
|
275
|
+
await this.renewIfNeeded();
|
|
276
|
+
if (this.extension != null)
|
|
277
|
+
await this.extension;
|
|
278
|
+
if (args.inputBEEF != null) {
|
|
279
|
+
this.state.sharedBeef.mergeBeef(args.inputBEEF);
|
|
280
|
+
}
|
|
281
|
+
const missing = this.missingExplicitOutpoints(args);
|
|
282
|
+
if (missing.length > 0)
|
|
283
|
+
await this.extend(1, 1, missing, args.isSignAction);
|
|
284
|
+
let planned;
|
|
285
|
+
let requestedOutputs = 8;
|
|
286
|
+
for (;;) {
|
|
287
|
+
try {
|
|
288
|
+
planned = await (0, ActionBatchPlanner_1.planAction)(this.state, args);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (!(error instanceof WERR_errors_1.WERR_INSUFFICIENT_FUNDS))
|
|
293
|
+
throw error;
|
|
294
|
+
// generateChangeSdk has already credited every unconsumed reservation
|
|
295
|
+
// supplied by planFunding. Only the reported shortfall is additional;
|
|
296
|
+
// requesting totalSatoshisNeeded here double-counts the held pool.
|
|
297
|
+
const target = additionalFundingTarget(error);
|
|
298
|
+
const added = await this.extend(target, requestedOutputs, [], args.isSignAction);
|
|
299
|
+
if (added.outputCount === 0 || added.satoshis === 0)
|
|
300
|
+
throw error;
|
|
301
|
+
requestedOutputs = nextGeometricTarget(requestedOutputs);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (planned == null)
|
|
305
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION('unable to plan action batch transaction');
|
|
306
|
+
this.planned.set(planned.dcr.reference, { args, planned });
|
|
307
|
+
return planned.dcr;
|
|
308
|
+
}
|
|
309
|
+
updateEwma(plan) {
|
|
310
|
+
const confirmed = plan.planned.consumedOutpoints
|
|
311
|
+
.map(outpoint => this.state.reserved.get(outpoint))
|
|
312
|
+
.filter((output) => output != null);
|
|
313
|
+
if (confirmed.length === 0)
|
|
314
|
+
return;
|
|
315
|
+
const satoshis = confirmed.reduce((sum, output) => sum + output.satoshis, 0);
|
|
316
|
+
if (!this.hasConfirmedSample) {
|
|
317
|
+
this.ewmaConfirmedInputs = confirmed.length;
|
|
318
|
+
this.ewmaConfirmedSatoshis = satoshis;
|
|
319
|
+
this.hasConfirmedSample = true;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
this.ewmaConfirmedInputs = 0.5 * confirmed.length + 0.5 * this.ewmaConfirmedInputs;
|
|
323
|
+
this.ewmaConfirmedSatoshis = 0.5 * satoshis + 0.5 * this.ewmaConfirmedSatoshis;
|
|
324
|
+
}
|
|
325
|
+
stage(prior, processArgs) {
|
|
326
|
+
if (this.actions.some(action => action.reference === prior.reference))
|
|
327
|
+
return;
|
|
328
|
+
const pending = this.planned.get(prior.reference);
|
|
329
|
+
if (pending == null)
|
|
330
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION('signed action does not belong to active action batch');
|
|
331
|
+
const txid = prior.tx.id('hex');
|
|
332
|
+
const lockingScriptDigests = prior.dcr.outputs.map(output => {
|
|
333
|
+
if (output.lockingScript.length === 0)
|
|
334
|
+
return undefined;
|
|
335
|
+
const digest = (0, actionBatchDigest_1.actionBatchBlobDigest)((0, utilityHelpers_noBuffer_1.asArray)(output.lockingScript));
|
|
336
|
+
this.lockingScripts.set(digest, output.lockingScript);
|
|
337
|
+
return digest;
|
|
338
|
+
});
|
|
339
|
+
const compactPlan = {
|
|
340
|
+
...prior.dcr,
|
|
341
|
+
inputBeef: undefined,
|
|
342
|
+
inputs: prior.dcr.inputs.map(input => ({ ...input, sourceTransaction: undefined })),
|
|
343
|
+
outputs: prior.dcr.outputs.map(output => ({ ...output, lockingScript: '' }))
|
|
344
|
+
};
|
|
345
|
+
const action = {
|
|
346
|
+
reference: prior.reference,
|
|
347
|
+
txid,
|
|
348
|
+
lockingScriptDigests,
|
|
349
|
+
plan: compactPlan,
|
|
350
|
+
metadata: {
|
|
351
|
+
description: pending.args.description,
|
|
352
|
+
labels: pending.args.labels,
|
|
353
|
+
isNoSend: processArgs.isNoSend,
|
|
354
|
+
isDelayed: processArgs.isDelayed,
|
|
355
|
+
inputs: pending.args.inputs,
|
|
356
|
+
outputs: pending.args.outputs.map(output => ({ ...output, lockingScript: '' }))
|
|
357
|
+
},
|
|
358
|
+
commissionKeyOffset: pending.planned.commissionKeyOffset
|
|
359
|
+
};
|
|
360
|
+
this.actions.push(action);
|
|
361
|
+
this.updateEwma(pending);
|
|
362
|
+
(0, ActionBatchPlanner_1.stageTransactionOutputs)(this.state, prior.tx, prior.dcr);
|
|
363
|
+
(0, ActionBatchPlanner_1.mergePlannerBeef)(this.state, prior.tx);
|
|
364
|
+
this.planned.delete(prior.reference);
|
|
365
|
+
this.scheduleExtensionIfNeeded();
|
|
366
|
+
}
|
|
367
|
+
scheduleExtensionIfNeeded() {
|
|
368
|
+
if (this.extension != null)
|
|
369
|
+
return;
|
|
370
|
+
const availableConfirmed = [...this.state.reserved.entries()]
|
|
371
|
+
.filter(([outpoint]) => !this.state.consumed.has(outpoint));
|
|
372
|
+
const request = fundingRunwayExtension(this.runwayTarget, this.ewmaConfirmedInputs, this.ewmaConfirmedSatoshis, availableConfirmed.map(([, output]) => output));
|
|
373
|
+
if (request == null)
|
|
374
|
+
return;
|
|
375
|
+
this.extension = this.extend(request.targetSatoshis, request.requestedOutputs, [], false)
|
|
376
|
+
.then(added => {
|
|
377
|
+
// A failed or partial extension must not geometrically inflate the
|
|
378
|
+
// next request against a runway that the wallet never acquired.
|
|
379
|
+
if (added.outputCount >= request.requestedOutputs &&
|
|
380
|
+
added.satoshis >= request.targetSatoshis) {
|
|
381
|
+
this.runwayTarget = request.nextRunwayTarget;
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
.catch(() => { })
|
|
385
|
+
.finally(() => { this.extension = undefined; });
|
|
386
|
+
}
|
|
387
|
+
buildManifest(sendWith, isDelayed) {
|
|
388
|
+
const stagedTxids = new Set(this.actions.map(action => action.txid));
|
|
389
|
+
const externalTxids = this.actions.flatMap(action => action.plan.inputs)
|
|
390
|
+
.map(input => input.sourceTxid)
|
|
391
|
+
.filter(txid => !stagedTxids.has(txid));
|
|
392
|
+
const dependencyBytes = (0, utilityHelpers_noBuffer_1.asArray)((0, beefForTxids_1.beefForTxids)(this.state.sharedBeef, externalTxids).toUint8Array());
|
|
393
|
+
const rawBytes = this.actions.map(action => {
|
|
394
|
+
const tx = this.state.sharedBeef.findTxid(action.txid)?.tx;
|
|
395
|
+
if (tx == null)
|
|
396
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`missing staged transaction ${action.txid}`);
|
|
397
|
+
return (0, utilityHelpers_noBuffer_1.asArray)(tx.toUint8Array());
|
|
398
|
+
});
|
|
399
|
+
const blobs = new Map();
|
|
400
|
+
const addBlob = (bytes) => {
|
|
401
|
+
const digest = (0, actionBatchDigest_1.actionBatchBlobDigest)(bytes);
|
|
402
|
+
if (!blobs.has(digest))
|
|
403
|
+
blobs.set(digest, bytes);
|
|
404
|
+
return digest;
|
|
405
|
+
};
|
|
406
|
+
const actions = this.actions.map((action, index) => {
|
|
407
|
+
return { ...action, rawTx: undefined, rawTxDigest: addBlob(rawBytes[index]) };
|
|
408
|
+
});
|
|
409
|
+
const dependencyBeefDigest = addBlob(dependencyBytes);
|
|
410
|
+
for (const [digest, script] of this.lockingScripts) {
|
|
411
|
+
if (!blobs.has(digest))
|
|
412
|
+
blobs.set(digest, (0, utilityHelpers_noBuffer_1.asArray)(script));
|
|
413
|
+
}
|
|
414
|
+
const totalBytes = [...blobs.values()].reduce((sum, bytes) => sum + bytes.length, 0);
|
|
415
|
+
const useUploads = totalBytes > this.capabilities.maxInlineBytes;
|
|
416
|
+
const uploadBlobs = new Map();
|
|
417
|
+
const blobChunks = {};
|
|
418
|
+
if (useUploads) {
|
|
419
|
+
const chunkBytes = Math.max(1, this.capabilities.maxBlobBytes);
|
|
420
|
+
for (const [digest, bytes] of blobs) {
|
|
421
|
+
if (bytes.length <= chunkBytes) {
|
|
422
|
+
uploadBlobs.set(digest, bytes);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const chunks = [];
|
|
426
|
+
for (let offset = 0; offset < bytes.length; offset += chunkBytes) {
|
|
427
|
+
const chunk = bytes.slice(offset, Math.min(bytes.length, offset + chunkBytes));
|
|
428
|
+
const chunkDigest = (0, actionBatchDigest_1.actionBatchBlobDigest)(chunk);
|
|
429
|
+
chunks.push(chunkDigest);
|
|
430
|
+
if (!uploadBlobs.has(chunkDigest))
|
|
431
|
+
uploadBlobs.set(chunkDigest, chunk);
|
|
432
|
+
}
|
|
433
|
+
blobChunks[digest] = chunks;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const withoutDigest = {
|
|
437
|
+
batchId: this.batchId,
|
|
438
|
+
actions,
|
|
439
|
+
dependencyBeefDigest,
|
|
440
|
+
inlineBlobs: useUploads
|
|
441
|
+
? undefined
|
|
442
|
+
: Object.fromEntries([...blobs].map(([digest, bytes]) => [digest, Uint8Array.from(bytes)])),
|
|
443
|
+
blobChunks: Object.keys(blobChunks).length === 0 ? undefined : blobChunks,
|
|
444
|
+
sendWith: mergeUnique(sendWith),
|
|
445
|
+
isDelayed
|
|
446
|
+
};
|
|
447
|
+
return {
|
|
448
|
+
manifest: { ...withoutDigest, digest: (0, actionBatchDigest_1.actionBatchManifestDigest)(withoutDigest) },
|
|
449
|
+
uploads: [...uploadBlobs].map(([digest, bytes]) => ({ digest, bytes }))
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
async uploadMissing(manifest, uploads) {
|
|
453
|
+
const prepared = await this.wallet.storage.prepareActionBatchCommit(manifest);
|
|
454
|
+
const missing = new Set(prepared.missingDigests);
|
|
455
|
+
const pending = uploads.filter(upload => missing.has(upload.digest));
|
|
456
|
+
const concurrency = Math.max(1, Math.min(prepared.maxConcurrentUploads, this.capabilities.maxConcurrentUploads, pending.length));
|
|
457
|
+
let cursor = 0;
|
|
458
|
+
await Promise.all(Array.from({ length: concurrency }, async () => {
|
|
459
|
+
for (;;) {
|
|
460
|
+
const index = cursor++;
|
|
461
|
+
if (index >= pending.length)
|
|
462
|
+
return;
|
|
463
|
+
const upload = pending[index];
|
|
464
|
+
if (upload.bytes.length > prepared.maxBlobBytes) {
|
|
465
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`action batch blob ${upload.digest} exceeds provider limit`);
|
|
466
|
+
}
|
|
467
|
+
await this.wallet.storage.putActionBatchBlob({
|
|
468
|
+
batchId: this.batchId,
|
|
469
|
+
digest: upload.digest,
|
|
470
|
+
bytes: upload.bytes
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
async commit(sendWith, isDelayed) {
|
|
476
|
+
if (this.extension != null)
|
|
477
|
+
await this.extension;
|
|
478
|
+
await this.renewIfNeeded();
|
|
479
|
+
if (this.planned.size > 0)
|
|
480
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION('all two-step actions must be signed before batch commit');
|
|
481
|
+
const { manifest, uploads } = this.buildManifest(sendWith, isDelayed);
|
|
482
|
+
if (uploads.length > 0)
|
|
483
|
+
await this.uploadMissing(manifest, uploads);
|
|
484
|
+
const result = await this.wallet.storage.commitActionBatch(manifest);
|
|
485
|
+
this.committed = true;
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
abortAction(referenceOrTxid) {
|
|
489
|
+
let pendingReference;
|
|
490
|
+
if (this.planned.has(referenceOrTxid)) {
|
|
491
|
+
pendingReference = referenceOrTxid;
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
pendingReference = Object.entries(this.wallet.pendingSignActions)
|
|
495
|
+
.find(([reference, pending]) => this.planned.has(reference) && pending.tx.id('hex') === referenceOrTxid)?.[0];
|
|
496
|
+
}
|
|
497
|
+
const actionIndex = this.actions.findIndex(action => action.reference === referenceOrTxid || action.txid === referenceOrTxid);
|
|
498
|
+
const action = actionIndex < 0 ? undefined : this.actions[actionIndex];
|
|
499
|
+
const targetTxid = action?.txid ??
|
|
500
|
+
(pendingReference == null ? undefined : this.wallet.pendingSignActions[pendingReference]?.tx.id('hex'));
|
|
501
|
+
if (targetTxid != null && this.hasDependentAction(targetTxid, action?.reference ?? pendingReference)) {
|
|
502
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`cannot abort action ${referenceOrTxid} while another staged action depends on it`);
|
|
503
|
+
}
|
|
504
|
+
if (action != null) {
|
|
505
|
+
this.actions.splice(actionIndex, 1);
|
|
506
|
+
}
|
|
507
|
+
else if (pendingReference != null) {
|
|
508
|
+
this.planned.delete(pendingReference);
|
|
509
|
+
Reflect.deleteProperty(this.wallet.pendingSignActions, pendingReference);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
if (targetTxid != null)
|
|
515
|
+
this.state.discardedStagedTxids.add(targetTxid);
|
|
516
|
+
this.rebuildStagedState();
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
hasDependentAction(txid, excludedReference) {
|
|
520
|
+
if (this.actions.some(action => action.reference !== excludedReference &&
|
|
521
|
+
action.plan.inputs.some(input => input.sourceTxid === txid)))
|
|
522
|
+
return true;
|
|
523
|
+
return [...this.planned.entries()].some(([reference, pending]) => reference !== excludedReference &&
|
|
524
|
+
pending.planned.dcr.inputs.some(input => input.sourceTxid === txid));
|
|
525
|
+
}
|
|
526
|
+
rebuildStagedState() {
|
|
527
|
+
this.state.consumed.clear();
|
|
528
|
+
this.state.staged.clear();
|
|
529
|
+
this.state.estimatedChangeCount = this.state.begin.availableChangeCount;
|
|
530
|
+
this.lockingScripts.clear();
|
|
531
|
+
for (const action of this.actions) {
|
|
532
|
+
const tx = this.state.sharedBeef.findTxid(action.txid)?.tx;
|
|
533
|
+
if (tx == null)
|
|
534
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`missing staged transaction ${action.txid}`);
|
|
535
|
+
for (const input of action.plan.inputs) {
|
|
536
|
+
this.state.consumed.add(`${input.sourceTxid}.${input.sourceVout}`);
|
|
537
|
+
}
|
|
538
|
+
const managedInputCount = action.plan.inputs.length - action.metadata.inputs.length;
|
|
539
|
+
const changeOutputCount = action.plan.outputs.filter(output => output.purpose === 'change').length;
|
|
540
|
+
this.state.estimatedChangeCount += changeOutputCount - managedInputCount;
|
|
541
|
+
(0, ActionBatchPlanner_1.stageTransactionOutputs)(this.state, tx, action.plan);
|
|
542
|
+
const lockingScriptDigests = action.lockingScriptDigests ?? [];
|
|
543
|
+
lockingScriptDigests.forEach((digest, outputIndex) => {
|
|
544
|
+
if (digest == null)
|
|
545
|
+
return;
|
|
546
|
+
const vout = action.plan.outputs[outputIndex]?.vout;
|
|
547
|
+
const script = vout == null ? undefined : tx.outputs[vout]?.lockingScript;
|
|
548
|
+
if (script == null)
|
|
549
|
+
throw new WERR_errors_1.WERR_INVALID_OPERATION(`missing staged output script for ${action.txid}.${vout}`);
|
|
550
|
+
this.lockingScripts.set(digest, script.toHex());
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
for (const pending of this.planned.values()) {
|
|
554
|
+
for (const outpoint of pending.planned.consumedOutpoints)
|
|
555
|
+
this.state.consumed.add(outpoint);
|
|
556
|
+
const managedInputCount = pending.planned.dcr.inputs.length - pending.args.inputs.length;
|
|
557
|
+
const changeOutputCount = pending.planned.dcr.outputs.filter(output => output.purpose === 'change').length;
|
|
558
|
+
this.state.estimatedChangeCount += changeOutputCount - managedInputCount;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async abort() {
|
|
562
|
+
if (this.committed)
|
|
563
|
+
return;
|
|
564
|
+
await this.wallet.storage.abortActionBatch(this.batchId);
|
|
565
|
+
for (const reference of this.planned.keys())
|
|
566
|
+
delete this.wallet.pendingSignActions[reference];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
class ActionBatchController {
|
|
570
|
+
wallet;
|
|
571
|
+
mode;
|
|
572
|
+
workspace;
|
|
573
|
+
capabilities = new Map();
|
|
574
|
+
serial = Promise.resolve();
|
|
575
|
+
constructor(wallet, mode) {
|
|
576
|
+
this.wallet = wallet;
|
|
577
|
+
this.mode = mode;
|
|
578
|
+
}
|
|
579
|
+
get hasWorkspace() { return this.workspace != null; }
|
|
580
|
+
overlayListActions(persisted, args) {
|
|
581
|
+
return this.workspace?.overlayListActions(persisted, args) ?? persisted;
|
|
582
|
+
}
|
|
583
|
+
overlayListOutputs(persisted, args) {
|
|
584
|
+
return this.workspace?.overlayListOutputs(persisted, args) ?? persisted;
|
|
585
|
+
}
|
|
586
|
+
async runExclusive(operation) {
|
|
587
|
+
const run = this.serial.then(operation);
|
|
588
|
+
this.serial = run.then(() => { }, () => { });
|
|
589
|
+
return await run;
|
|
590
|
+
}
|
|
591
|
+
async negotiate() {
|
|
592
|
+
if (this.mode === 'legacy')
|
|
593
|
+
return undefined;
|
|
594
|
+
const activeStore = this.wallet.storage.getActiveStore();
|
|
595
|
+
let capabilities = this.capabilities.get(activeStore);
|
|
596
|
+
if (capabilities == null) {
|
|
597
|
+
capabilities = this.wallet.storage.getCapabilities()
|
|
598
|
+
.then(result => result.actionBatch?.version === 1 ? result.actionBatch : undefined)
|
|
599
|
+
.catch(() => undefined);
|
|
600
|
+
this.capabilities.set(activeStore, capabilities);
|
|
601
|
+
}
|
|
602
|
+
return await capabilities;
|
|
603
|
+
}
|
|
604
|
+
async begin(args) {
|
|
605
|
+
if (args.inputs.length > 8)
|
|
606
|
+
return undefined;
|
|
607
|
+
const capabilities = await this.negotiate();
|
|
608
|
+
if (capabilities == null)
|
|
609
|
+
return undefined;
|
|
610
|
+
const batchId = (0, utilityHelpers_1.randomBytesBase64)(24);
|
|
611
|
+
const begin = await this.wallet.storage.beginActionBatch({
|
|
612
|
+
batchId,
|
|
613
|
+
firstAction: { ...args, logger: undefined }
|
|
614
|
+
});
|
|
615
|
+
this.workspace = new ActionBatchWorkspace(this.wallet, begin, args, capabilities);
|
|
616
|
+
return this.workspace;
|
|
617
|
+
}
|
|
618
|
+
async plan(args) {
|
|
619
|
+
return await this.runExclusive(async () => {
|
|
620
|
+
if (!args.isNewTx)
|
|
621
|
+
return undefined;
|
|
622
|
+
let workspace = this.workspace;
|
|
623
|
+
let beganWorkspace = false;
|
|
624
|
+
if (workspace == null) {
|
|
625
|
+
if (!args.isNoSend)
|
|
626
|
+
return undefined;
|
|
627
|
+
workspace = await this.begin(args);
|
|
628
|
+
if (workspace == null)
|
|
629
|
+
return undefined;
|
|
630
|
+
beganWorkspace = true;
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
return await workspace.plan(args);
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
if (beganWorkspace) {
|
|
637
|
+
await workspace.abort();
|
|
638
|
+
this.workspace = undefined;
|
|
639
|
+
}
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async process(prior, args) {
|
|
645
|
+
return await this.runExclusive(async () => {
|
|
646
|
+
const workspace = this.workspace;
|
|
647
|
+
if (workspace == null)
|
|
648
|
+
return undefined;
|
|
649
|
+
if (prior != null && !workspace.ownsReference(prior.reference))
|
|
650
|
+
return undefined;
|
|
651
|
+
if (prior != null)
|
|
652
|
+
workspace.stage(prior, args);
|
|
653
|
+
const shouldCommit = args.isSendWith || (prior != null && !args.isNoSend);
|
|
654
|
+
if (!shouldCommit)
|
|
655
|
+
return { sendWithResults: [] };
|
|
656
|
+
const sendWith = [...args.options.sendWith];
|
|
657
|
+
if (prior != null && !args.isNoSend && !sendWith.includes(prior.tx.id('hex')))
|
|
658
|
+
sendWith.push(prior.tx.id('hex'));
|
|
659
|
+
const result = await workspace.commit(sendWith, args.isDelayed);
|
|
660
|
+
this.workspace = undefined;
|
|
661
|
+
return result;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
ownsReference(reference) {
|
|
665
|
+
return this.workspace?.ownsReference(reference) ?? false;
|
|
666
|
+
}
|
|
667
|
+
async abort() {
|
|
668
|
+
return await this.runExclusive(async () => {
|
|
669
|
+
if (this.workspace == null)
|
|
670
|
+
return false;
|
|
671
|
+
await this.workspace.abort();
|
|
672
|
+
this.workspace = undefined;
|
|
673
|
+
return true;
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
async abortAction(referenceOrTxid) {
|
|
677
|
+
return await this.runExclusive(async () => {
|
|
678
|
+
const workspace = this.workspace;
|
|
679
|
+
if (workspace == null)
|
|
680
|
+
return false;
|
|
681
|
+
const aborted = workspace.abortAction(referenceOrTxid);
|
|
682
|
+
if (!aborted)
|
|
683
|
+
return false;
|
|
684
|
+
if (workspace.isEmpty) {
|
|
685
|
+
await workspace.abort();
|
|
686
|
+
this.workspace = undefined;
|
|
687
|
+
}
|
|
688
|
+
return true;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
exports.ActionBatchController = ActionBatchController;
|
|
693
|
+
//# sourceMappingURL=ActionBatchWorkspace.js.map
|