@bananapus/nana-sdk-core 6.2.0 → 6.3.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.
- package/dist/cjs/review/confirming.d.ts +8 -0
- package/dist/cjs/review/confirming.d.ts.map +1 -0
- package/dist/cjs/review/confirming.js +11 -0
- package/dist/cjs/review/confirming.js.map +1 -0
- package/dist/cjs/review/locks.d.ts +87 -7
- package/dist/cjs/review/locks.d.ts.map +1 -1
- package/dist/cjs/review/locks.js +328 -79
- package/dist/cjs/review/locks.js.map +1 -1
- package/dist/cjs/review/messages.d.ts +16 -0
- package/dist/cjs/review/messages.d.ts.map +1 -1
- package/dist/cjs/review/messages.js +17 -1
- package/dist/cjs/review/messages.js.map +1 -1
- package/dist/cjs/review/react/ownership.d.ts +30 -0
- package/dist/cjs/review/react/ownership.d.ts.map +1 -0
- package/dist/cjs/review/react/ownership.js +27 -0
- package/dist/cjs/review/react/ownership.js.map +1 -0
- package/dist/cjs/review/react/useReviewedRun.d.ts +3 -1
- package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
- package/dist/cjs/review/react/useReviewedRun.js +428 -50
- package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
- package/dist/cjs/review/relayrSend.d.ts +16 -1
- package/dist/cjs/review/relayrSend.d.ts.map +1 -1
- package/dist/cjs/review/relayrSend.js +25 -2
- package/dist/cjs/review/relayrSend.js.map +1 -1
- package/dist/cjs/review/safeRelayr.d.ts +2 -1
- package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
- package/dist/cjs/review/safeRelayr.js +23 -29
- package/dist/cjs/review/safeRelayr.js.map +1 -1
- package/dist/cjs/review/send.d.ts +31 -12
- package/dist/cjs/review/send.d.ts.map +1 -1
- package/dist/cjs/review/send.js +191 -41
- package/dist/cjs/review/send.js.map +1 -1
- package/dist/cjs/review/sequence.d.ts +25 -0
- package/dist/cjs/review/sequence.d.ts.map +1 -1
- package/dist/cjs/review/sequence.js +45 -7
- package/dist/cjs/review/sequence.js.map +1 -1
- package/dist/cjs/review/tabState.d.ts +4 -0
- package/dist/cjs/review/tabState.d.ts.map +1 -1
- package/dist/cjs/review/tabState.js +4 -0
- package/dist/cjs/review/tabState.js.map +1 -1
- package/dist/cjs/review/wallet.d.ts.map +1 -1
- package/dist/cjs/review/wallet.js +3 -1
- package/dist/cjs/review/wallet.js.map +1 -1
- package/dist/cjs/review/writeHold.d.ts +74 -11
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +65 -16
- package/dist/cjs/review/writeHold.js.map +1 -1
- package/dist/cjs/review/writeRecovery.d.ts +31 -6
- package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
- package/dist/cjs/review/writeRecovery.js +120 -7
- package/dist/cjs/review/writeRecovery.js.map +1 -1
- package/dist/cjs/testing.d.ts +13 -0
- package/dist/cjs/testing.d.ts.map +1 -1
- package/dist/cjs/testing.js +21 -0
- package/dist/cjs/testing.js.map +1 -1
- package/dist/esm/review/confirming.d.ts +8 -0
- package/dist/esm/review/confirming.d.ts.map +1 -0
- package/dist/esm/review/confirming.js +8 -0
- package/dist/esm/review/confirming.js.map +1 -0
- package/dist/esm/review/locks.d.ts +87 -7
- package/dist/esm/review/locks.d.ts.map +1 -1
- package/dist/esm/review/locks.js +321 -78
- package/dist/esm/review/locks.js.map +1 -1
- package/dist/esm/review/messages.d.ts +16 -0
- package/dist/esm/review/messages.d.ts.map +1 -1
- package/dist/esm/review/messages.js +16 -0
- package/dist/esm/review/messages.js.map +1 -1
- package/dist/esm/review/react/ownership.d.ts +30 -0
- package/dist/esm/review/react/ownership.d.ts.map +1 -0
- package/dist/esm/review/react/ownership.js +24 -0
- package/dist/esm/review/react/ownership.js.map +1 -0
- package/dist/esm/review/react/useReviewedRun.d.ts +3 -1
- package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
- package/dist/esm/review/react/useReviewedRun.js +429 -51
- package/dist/esm/review/react/useReviewedRun.js.map +1 -1
- package/dist/esm/review/relayrSend.d.ts +16 -1
- package/dist/esm/review/relayrSend.d.ts.map +1 -1
- package/dist/esm/review/relayrSend.js +28 -5
- package/dist/esm/review/relayrSend.js.map +1 -1
- package/dist/esm/review/safeRelayr.d.ts +2 -1
- package/dist/esm/review/safeRelayr.d.ts.map +1 -1
- package/dist/esm/review/safeRelayr.js +24 -30
- package/dist/esm/review/safeRelayr.js.map +1 -1
- package/dist/esm/review/send.d.ts +31 -12
- package/dist/esm/review/send.d.ts.map +1 -1
- package/dist/esm/review/send.js +193 -43
- package/dist/esm/review/send.js.map +1 -1
- package/dist/esm/review/sequence.d.ts +25 -0
- package/dist/esm/review/sequence.d.ts.map +1 -1
- package/dist/esm/review/sequence.js +43 -7
- package/dist/esm/review/sequence.js.map +1 -1
- package/dist/esm/review/tabState.d.ts +4 -0
- package/dist/esm/review/tabState.d.ts.map +1 -1
- package/dist/esm/review/tabState.js +4 -0
- package/dist/esm/review/tabState.js.map +1 -1
- package/dist/esm/review/wallet.d.ts.map +1 -1
- package/dist/esm/review/wallet.js +3 -1
- package/dist/esm/review/wallet.js.map +1 -1
- package/dist/esm/review/writeHold.d.ts +74 -11
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +65 -16
- package/dist/esm/review/writeHold.js.map +1 -1
- package/dist/esm/review/writeRecovery.d.ts +31 -6
- package/dist/esm/review/writeRecovery.d.ts.map +1 -1
- package/dist/esm/review/writeRecovery.js +118 -7
- package/dist/esm/review/writeRecovery.js.map +1 -1
- package/dist/esm/testing.d.ts +13 -0
- package/dist/esm/testing.d.ts.map +1 -1
- package/dist/esm/testing.js +20 -0
- package/dist/esm/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.REVIEWED_RUN_BUSY_LINE = exports.REVIEWED_RUN_SCOPE_LINE = void 0;
|
|
4
4
|
exports.useReviewedRun = useReviewedRun;
|
|
5
5
|
const lineError_js_1 = require("../lineError.js");
|
|
6
|
+
const locks_js_1 = require("../locks.js");
|
|
6
7
|
const receipt_js_1 = require("../receipt.js");
|
|
7
8
|
const index_js_1 = require("../index.js");
|
|
8
9
|
const react_1 = require("react");
|
|
9
10
|
const heldView_js_1 = require("./heldView.js");
|
|
11
|
+
const ownership_js_1 = require("./ownership.js");
|
|
10
12
|
const IDLE = {
|
|
11
13
|
cancelled: false,
|
|
12
14
|
stopped: false,
|
|
@@ -22,6 +24,7 @@ const IDLE = {
|
|
|
22
24
|
receipt: null,
|
|
23
25
|
settled: null,
|
|
24
26
|
acknowledge: null,
|
|
27
|
+
away: null,
|
|
25
28
|
};
|
|
26
29
|
/** The bytes a call sends, for comparing two actions. */
|
|
27
30
|
const sent = ({ chainId, to, data, value, gas }) => [
|
|
@@ -31,6 +34,28 @@ const sent = ({ chainId, to, data, value, gas }) => [
|
|
|
31
34
|
value ?? 0n,
|
|
32
35
|
gas ?? null,
|
|
33
36
|
];
|
|
37
|
+
/** This form's earlier write of the same calls succeeded: the run closes on it, sending nothing. */
|
|
38
|
+
/**
|
|
39
|
+
* The holds `action`'s steps are held in, each with its step: a resumed
|
|
40
|
+
* sequence's sending step; a Safe app's batch as its one MultiSend
|
|
41
|
+
* proposal, its last step; or each call. The one home for that mapping.
|
|
42
|
+
*/
|
|
43
|
+
function heldSteps(account, safe, action, saved) {
|
|
44
|
+
if (saved?.sending)
|
|
45
|
+
return [
|
|
46
|
+
{ call: saved.calls[saved.sending.step], step: saved.sending.step },
|
|
47
|
+
];
|
|
48
|
+
const total = saved?.calls.length ?? action.calls.length;
|
|
49
|
+
return !saved && action.batch && safe && action.calls.length > 1
|
|
50
|
+
? [{ call: (0, index_js_1.safeBatchCall)(account, action.calls), step: total - 1 }]
|
|
51
|
+
: action.calls.map((call, step) => ({ call, step }));
|
|
52
|
+
}
|
|
53
|
+
class OwnSucceeded extends Error {
|
|
54
|
+
constructor(found) {
|
|
55
|
+
super("succeeded");
|
|
56
|
+
this.found = found;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
34
59
|
exports.REVIEWED_RUN_SCOPE_LINE = "This action has more than one step and no scope to resume it by. Nothing was sent.";
|
|
35
60
|
exports.REVIEWED_RUN_BUSY_LINE = "This action is still running. Wait for it to finish, then try again.";
|
|
36
61
|
/**
|
|
@@ -46,16 +71,29 @@ function useReviewedRun(deps) {
|
|
|
46
71
|
if (cancelling)
|
|
47
72
|
void (0, receipt_js_1.tell)(cancelling.told);
|
|
48
73
|
}, [cancelling]);
|
|
49
|
-
/**
|
|
74
|
+
/** Every shown success's acknowledgement: the next run waits for all. */
|
|
50
75
|
const acknowledged = (0, react_1.useRef)(Promise.resolve());
|
|
51
76
|
// A success is acknowledged only once its done rendered: a reset after
|
|
52
|
-
// that hides nothing, and one before it leaves the success warning.
|
|
77
|
+
// that hides nothing, and one before it leaves the success warning. A
|
|
78
|
+
// later one never drops an earlier one still running from the wait.
|
|
53
79
|
(0, react_1.useEffect)(() => {
|
|
54
80
|
if (state.acknowledge)
|
|
55
|
-
acknowledged.current =
|
|
81
|
+
acknowledged.current = Promise.all([
|
|
82
|
+
acknowledged.current,
|
|
83
|
+
state.acknowledge(),
|
|
84
|
+
]).then(() => undefined);
|
|
56
85
|
}, [state.acknowledge]);
|
|
57
86
|
/** The hold followed after a run, and its chain's client: its view is `held`. */
|
|
58
|
-
const [watched,
|
|
87
|
+
const [watched, setWatchedState] = (0, react_1.useState)(null);
|
|
88
|
+
/**
|
|
89
|
+
* `watched` as last set, for a handler that resets and runs from one
|
|
90
|
+
* render's handle (a "Try again"): never that render's stale copy.
|
|
91
|
+
*/
|
|
92
|
+
const watchedNow = (0, react_1.useRef)(null);
|
|
93
|
+
const setWatched = (next) => {
|
|
94
|
+
watchedNow.current = next;
|
|
95
|
+
setWatchedState(next);
|
|
96
|
+
};
|
|
59
97
|
/** The latest run: an earlier one's late answers change nothing. */
|
|
60
98
|
const latest = (0, react_1.useRef)(0);
|
|
61
99
|
/** The sent step whose hold the latest run's `ended()` awaits. */
|
|
@@ -76,6 +114,101 @@ function useReviewedRun(deps) {
|
|
|
76
114
|
* hold keeps the sent step.
|
|
77
115
|
*/
|
|
78
116
|
const leave = (0, react_1.useRef)(() => { });
|
|
117
|
+
/** Every reservation this runner's runs followed: its own, whatever its calls now. */
|
|
118
|
+
const mine = (0, react_1.useRef)(new Set());
|
|
119
|
+
/**
|
|
120
|
+
* The reservations `calls`' holds hold now (a held write, or a success
|
|
121
|
+
* nothing showed), each with its hold.
|
|
122
|
+
*/
|
|
123
|
+
const holdsOf = (account, calls) => calls.flatMap((call) => {
|
|
124
|
+
if (!deps || !account)
|
|
125
|
+
return [];
|
|
126
|
+
try {
|
|
127
|
+
// A hold's key is its call's, whatever the Safe mode.
|
|
128
|
+
const handle = (0, index_js_1.openStepHold)(deps.hold, account, false, call);
|
|
129
|
+
const held = handle.peek();
|
|
130
|
+
const reservation = held.status !== "clear"
|
|
131
|
+
? held.reservation
|
|
132
|
+
: handle.unacknowledgedSuccess()?.reservation;
|
|
133
|
+
return reservation ? [{ handle, reservation }] : [];
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* The reservations this form's last run left unshown, or the last step
|
|
141
|
+
* `dismiss` closed while it was followed (`run`, whose later showing the
|
|
142
|
+
* user closed, never counts as seen), and the account that sent them.
|
|
143
|
+
* Their success, whoever acknowledged it, closes a later run under the
|
|
144
|
+
* same hold (its intent's, though the calls were refreshed) from the same
|
|
145
|
+
* account. Still pending, the hold refuses that run, which follows it again.
|
|
146
|
+
* One entry per account and reservation: keeping or showing one never
|
|
147
|
+
* touches another's.
|
|
148
|
+
*/
|
|
149
|
+
const own = (0, react_1.useRef)(new Map());
|
|
150
|
+
/**
|
|
151
|
+
* Every change to a form's write, under the account that sent it, goes
|
|
152
|
+
* through {@link mergeOwn}: forward only. A showing (`shown`) changes
|
|
153
|
+
* the reservation's entry whichever account it is under.
|
|
154
|
+
*/
|
|
155
|
+
const changeOwn = (account, reservations, change) => {
|
|
156
|
+
for (const reservation of reservations) {
|
|
157
|
+
if (!reservation)
|
|
158
|
+
continue;
|
|
159
|
+
const keys = [...own.current]
|
|
160
|
+
.filter(([, entry]) => change.kind === "shown" && entry.reservation === reservation)
|
|
161
|
+
.map(([key]) => key);
|
|
162
|
+
const mine = `${account.toLowerCase()}:${reservation}`;
|
|
163
|
+
for (const key of keys.length ? keys : [mine]) {
|
|
164
|
+
const entry = own.current.get(key);
|
|
165
|
+
own.current.set(key, (0, ownership_js_1.mergeOwn)(entry, entry?.account ?? account, reservation, change));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const ownSuccess = (action) => {
|
|
170
|
+
if (!deps?.account ||
|
|
171
|
+
(action.account &&
|
|
172
|
+
action.account.toLowerCase() !== deps.account.toLowerCase()))
|
|
173
|
+
return undefined;
|
|
174
|
+
const kept = [...own.current.values()]
|
|
175
|
+
.filter((entry) => entry.state === "owned" &&
|
|
176
|
+
entry.account.toLowerCase() === deps.account.toLowerCase())
|
|
177
|
+
.map((entry) => entry.reservation);
|
|
178
|
+
if (!kept.length)
|
|
179
|
+
return undefined;
|
|
180
|
+
// Only the action's last step's hold, as `follow` maps it: a success
|
|
181
|
+
// of an earlier step never completes the action. Its Safe mode may not
|
|
182
|
+
// be known yet; a reservation is only ever in its own mode's hold.
|
|
183
|
+
const account = deps.account;
|
|
184
|
+
const lasts = [false, true].map((safe) => {
|
|
185
|
+
const steps = heldSteps(account, safe, action);
|
|
186
|
+
return steps[steps.length - 1];
|
|
187
|
+
});
|
|
188
|
+
for (const { call, step } of lasts) {
|
|
189
|
+
try {
|
|
190
|
+
// A reservation is read only through its own hold: another hold
|
|
191
|
+
// (another intent, or another exact call) never answers for it.
|
|
192
|
+
const handle = (0, index_js_1.openStepHold)(deps.hold, deps.account, false, call);
|
|
193
|
+
for (const reservation of kept) {
|
|
194
|
+
const settled = handle.settlementOf(reservation);
|
|
195
|
+
if (settled?.result === "success")
|
|
196
|
+
return {
|
|
197
|
+
settled: settled,
|
|
198
|
+
handle,
|
|
199
|
+
account,
|
|
200
|
+
call: call,
|
|
201
|
+
step,
|
|
202
|
+
reservation,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
/* A call the hold can't open holds nothing of this form's. */
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
};
|
|
79
212
|
const replaced = () => {
|
|
80
213
|
latest.current += 1;
|
|
81
214
|
life.current.abort();
|
|
@@ -94,9 +227,12 @@ function useReviewedRun(deps) {
|
|
|
94
227
|
};
|
|
95
228
|
}, []);
|
|
96
229
|
/** How a followed step ended, as the run that followed it shows it. */
|
|
97
|
-
const showSettled = (run, step, total, settled, receipt, acknowledge) => {
|
|
230
|
+
const showSettled = (run, step, total, settled, receipt, acknowledge, account, reservation) => {
|
|
98
231
|
if (latest.current !== run)
|
|
99
232
|
return;
|
|
233
|
+
// Shown: its own entry is released, unless `dismiss` closed it while
|
|
234
|
+
// this run followed it (its showing the user closed never counts).
|
|
235
|
+
changeOwn(account, [reservation], { kind: "shown", run });
|
|
100
236
|
const finished = step === total - 1;
|
|
101
237
|
setState((now) => settled.result !== "success"
|
|
102
238
|
? {
|
|
@@ -174,8 +310,18 @@ function useReviewedRun(deps) {
|
|
|
174
310
|
shown.phase === "settled"
|
|
175
311
|
? null
|
|
176
312
|
: shown;
|
|
177
|
-
const { cancelled: _cancelled, stopped: _stopped, acknowledge: _acknowledge, ...shownState } = state;
|
|
178
|
-
const
|
|
313
|
+
const { cancelled: _cancelled, stopped: _stopped, acknowledge: _acknowledge, away, ...shownState } = state;
|
|
314
|
+
const awayShown = away && !held
|
|
315
|
+
? {
|
|
316
|
+
phase: "succeeded",
|
|
317
|
+
state: null,
|
|
318
|
+
line: index_js_1.HELD_AWAY_SUCCESS_LINE,
|
|
319
|
+
exit: false,
|
|
320
|
+
settled: { result: "success", hash: away.hash },
|
|
321
|
+
adopting: false,
|
|
322
|
+
}
|
|
323
|
+
: null;
|
|
324
|
+
const status = (0, index_js_1.reviewedRunStatus)({ ...state, held: held ?? awayShown });
|
|
179
325
|
const reset = () => {
|
|
180
326
|
replaced();
|
|
181
327
|
setState(IDLE);
|
|
@@ -195,16 +341,43 @@ function useReviewedRun(deps) {
|
|
|
195
341
|
...shownState,
|
|
196
342
|
...status,
|
|
197
343
|
busy: status.busy && !state.following,
|
|
198
|
-
held,
|
|
344
|
+
held: held ?? awayShown,
|
|
199
345
|
...(0, heldView_js_1.actionsOf)(shown, held ? view : undefined),
|
|
346
|
+
...(awayShown && {
|
|
347
|
+
sendAgainAction: {
|
|
348
|
+
label: index_js_1.HELD_SEND_AGAIN_LABEL,
|
|
349
|
+
run: async () => {
|
|
350
|
+
await away.handle.acknowledge({
|
|
351
|
+
result: "success",
|
|
352
|
+
hash: away.hash,
|
|
353
|
+
});
|
|
354
|
+
setState((now) => (now.away === away ? { ...now, away: null } : now));
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
}),
|
|
200
358
|
reset,
|
|
201
359
|
dismiss: () => {
|
|
202
360
|
const waiting = awaiting.current;
|
|
203
361
|
if (open.current && !waiting)
|
|
204
362
|
return false;
|
|
363
|
+
/**
|
|
364
|
+
* The followed last step this closes, under the account that sent it,
|
|
365
|
+
* and the run that followed it: a retry of it never sends it again.
|
|
366
|
+
*/
|
|
367
|
+
const last = waiting
|
|
368
|
+
? waiting.last && { ...waiting.owner, run: waiting.run }
|
|
369
|
+
: watched?.step === watched?.total - 1 && watched;
|
|
370
|
+
const closing = () => {
|
|
371
|
+
if (last)
|
|
372
|
+
changeOwn(last.account, [last.reservation], {
|
|
373
|
+
kind: "dismiss",
|
|
374
|
+
run: last.run,
|
|
375
|
+
});
|
|
376
|
+
};
|
|
205
377
|
if (waiting?.last) {
|
|
206
378
|
awaiting.current = null;
|
|
207
379
|
waiting.end();
|
|
380
|
+
closing();
|
|
208
381
|
return true;
|
|
209
382
|
}
|
|
210
383
|
detach(waiting
|
|
@@ -213,9 +386,13 @@ function useReviewedRun(deps) {
|
|
|
213
386
|
handle: watched.handle,
|
|
214
387
|
client: watched.client,
|
|
215
388
|
call: watched.call,
|
|
389
|
+
account: watched.account,
|
|
216
390
|
reservation: watched.reservation,
|
|
217
391
|
});
|
|
392
|
+
// Closing the view keeps what this form sent: a reset forgets no
|
|
393
|
+
// entry, so a later dismiss of the idle view never forgets one.
|
|
218
394
|
reset();
|
|
395
|
+
closing();
|
|
219
396
|
return true;
|
|
220
397
|
},
|
|
221
398
|
preload,
|
|
@@ -235,6 +412,102 @@ function useReviewedRun(deps) {
|
|
|
235
412
|
setWatched(null);
|
|
236
413
|
},
|
|
237
414
|
run: async ({ onCancelled, onStopped, ...action }) => {
|
|
415
|
+
/**
|
|
416
|
+
* The account this run sends from, read once: every continuation of
|
|
417
|
+
* it uses this one, whatever account the app's deps hold by then.
|
|
418
|
+
*/
|
|
419
|
+
const runAccount = deps?.account;
|
|
420
|
+
/**
|
|
421
|
+
* The wallet's Safe mode, only while it still speaks for the run's
|
|
422
|
+
* account: another account's mode never names this run's holds.
|
|
423
|
+
*/
|
|
424
|
+
const runSafe = () => deps && deps.account?.toLowerCase() === runAccount?.toLowerCase()
|
|
425
|
+
? deps.adapter.isSafe()
|
|
426
|
+
: undefined;
|
|
427
|
+
/** This form's unshown write of these calls succeeded: the form closes on it. */
|
|
428
|
+
const close = async (found) => {
|
|
429
|
+
const { settled, handle } = found;
|
|
430
|
+
// Every exit holds the run open, and this form's ownership stays
|
|
431
|
+
// until its success is shown and acknowledged: until then a dismiss
|
|
432
|
+
// is refused and a later run closes on the same success again.
|
|
433
|
+
open.current += 1;
|
|
434
|
+
try {
|
|
435
|
+
// Completed as its run would have, under this run's locks: told to
|
|
436
|
+
// onConfirmed once (unless another completion did), and done only
|
|
437
|
+
// once delivered.
|
|
438
|
+
let failure;
|
|
439
|
+
const told = await load(deps.engine)
|
|
440
|
+
.then((engine) => engine.completeReviewedStep({
|
|
441
|
+
step: found.step,
|
|
442
|
+
held: { call: found.call, reservation: found.reservation },
|
|
443
|
+
}, {
|
|
444
|
+
account: found.account,
|
|
445
|
+
scope: action.scope,
|
|
446
|
+
onConfirmed: action.onConfirmed,
|
|
447
|
+
locks: action.locks,
|
|
448
|
+
attemptScoped: action.attemptScoped,
|
|
449
|
+
}, deps, null, alive, false))
|
|
450
|
+
.catch((error) => {
|
|
451
|
+
failure = error;
|
|
452
|
+
return null;
|
|
453
|
+
});
|
|
454
|
+
if (!told || told.undelivered) {
|
|
455
|
+
failure ??= new locks_js_1.LockLostError(action.locks?.[0]?.name ?? "site");
|
|
456
|
+
const error = (0, index_js_1.errorLine)(failure);
|
|
457
|
+
update({ ...IDLE, error, failure });
|
|
458
|
+
return { status: "failed", error, failure };
|
|
459
|
+
}
|
|
460
|
+
const step = action.calls.length - 1;
|
|
461
|
+
update({
|
|
462
|
+
...IDLE,
|
|
463
|
+
// Shown now: acknowledged once rendered, so it blocks no new send.
|
|
464
|
+
// Rendered: the form's own is released, and a later run waits
|
|
465
|
+
// for this acknowledgement before it sends.
|
|
466
|
+
acknowledge: async () => {
|
|
467
|
+
if (alive.aborted)
|
|
468
|
+
return;
|
|
469
|
+
changeOwn(found.account, [found.reservation], {
|
|
470
|
+
kind: "shown",
|
|
471
|
+
});
|
|
472
|
+
await handle.acknowledge(settled).catch(() => undefined);
|
|
473
|
+
},
|
|
474
|
+
phase: "done",
|
|
475
|
+
step,
|
|
476
|
+
total: action.calls.length,
|
|
477
|
+
hash: settled.hash,
|
|
478
|
+
settled: { step, finished: true },
|
|
479
|
+
});
|
|
480
|
+
return {
|
|
481
|
+
status: "done",
|
|
482
|
+
hashes: told.hashes,
|
|
483
|
+
receipts: [],
|
|
484
|
+
...(told.signatures ? { signatures: told.signatures } : {}),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
open.current -= 1;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
// This form's own write is still followed: a retry of it is refused
|
|
492
|
+
// with the hold's line, and the run following it goes on.
|
|
493
|
+
const now = watchedNow.current;
|
|
494
|
+
// Identity, never bytes: the followed reservation is one of these
|
|
495
|
+
// calls' holds'. A scoped sequence's journal answers for its own.
|
|
496
|
+
const followed = now?.reservation &&
|
|
497
|
+
runAccount &&
|
|
498
|
+
(action.scope === undefined || action.calls.length === 1)
|
|
499
|
+
? now
|
|
500
|
+
: null;
|
|
501
|
+
const ownState = followed?.handle.peek();
|
|
502
|
+
if (followed &&
|
|
503
|
+
ownState?.status !== "clear" &&
|
|
504
|
+
ownState?.reservation === followed.reservation &&
|
|
505
|
+
holdsOf(runAccount, action.calls).some(({ reservation }) => reservation === followed.reservation)) {
|
|
506
|
+
const failure = new index_js_1.WriteHeldError(ownState);
|
|
507
|
+
const error = (0, index_js_1.errorLine)(failure);
|
|
508
|
+
setState((now) => ({ ...now, error, failure }));
|
|
509
|
+
return { status: "failed", error, failure };
|
|
510
|
+
}
|
|
238
511
|
replaced();
|
|
239
512
|
const id = latest.current;
|
|
240
513
|
const { signal: alive } = life.current;
|
|
@@ -248,6 +521,21 @@ function useReviewedRun(deps) {
|
|
|
248
521
|
setState((now) => ({ ...now, ...next }));
|
|
249
522
|
};
|
|
250
523
|
setState({ ...IDLE, phase: "review", total: action.calls.length });
|
|
524
|
+
const succeeded = ownSuccess(action);
|
|
525
|
+
if (succeeded) {
|
|
526
|
+
setWatched(null);
|
|
527
|
+
// close holds the run open itself, from here until it ends.
|
|
528
|
+
open.current -= 1;
|
|
529
|
+
return close(succeeded);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Reservations the action's holds had before this run: another run's
|
|
533
|
+
* (another form's, sharing a hold). This run never follows one.
|
|
534
|
+
*/
|
|
535
|
+
const holds = () => holdsOf(runAccount, runAccount && action.batch && action.calls.length > 1
|
|
536
|
+
? [...action.calls, (0, index_js_1.safeBatchCall)(runAccount, action.calls)]
|
|
537
|
+
: action.calls);
|
|
538
|
+
const before = new Set(holds().map(({ reservation }) => reservation));
|
|
251
539
|
let loaded;
|
|
252
540
|
/** A step of the action was sent before this run, or by it: never a cancel. */
|
|
253
541
|
let sentAny = false;
|
|
@@ -276,9 +564,11 @@ function useReviewedRun(deps) {
|
|
|
276
564
|
let completed = Promise.resolve(null);
|
|
277
565
|
let shown = false;
|
|
278
566
|
const action$ = {
|
|
279
|
-
account:
|
|
567
|
+
account: runAccount,
|
|
280
568
|
scope: action.scope,
|
|
281
569
|
onConfirmed: action.onConfirmed,
|
|
570
|
+
locks: action.locks,
|
|
571
|
+
attemptScoped: action.attemptScoped,
|
|
282
572
|
};
|
|
283
573
|
const finish = (seen, receipt = null) => {
|
|
284
574
|
if (shown || alive.aborted)
|
|
@@ -292,11 +582,30 @@ function useReviewedRun(deps) {
|
|
|
292
582
|
return null;
|
|
293
583
|
}));
|
|
294
584
|
void completed.then((ended) => {
|
|
585
|
+
// Its notice undelivered: the form's own, whichever run shows
|
|
586
|
+
// now (a retry that replaced this one included), so a later
|
|
587
|
+
// run closes on it, telling it then.
|
|
588
|
+
if (ended?.undelivered && target.step === total - 1)
|
|
589
|
+
changeOwn(action$.account, [target.held?.reservation], {
|
|
590
|
+
kind: "keep",
|
|
591
|
+
});
|
|
295
592
|
if (shown || alive.aborted)
|
|
296
593
|
return;
|
|
297
|
-
// Nothing advanced (its receipt unread,
|
|
298
|
-
// for this send yet): it is
|
|
299
|
-
|
|
594
|
+
// Nothing advanced (its receipt unread, its hold not settled
|
|
595
|
+
// for this send yet, or its onConfirmed not delivered): it is
|
|
596
|
+
// tried again, still followed and acknowledged by nothing.
|
|
597
|
+
// ponytail: retried at the receipt's pace while mounted.
|
|
598
|
+
if (unread || (!ended && seen) || ended?.undelivered) {
|
|
599
|
+
if (ended?.undelivered) {
|
|
600
|
+
completed = Promise.resolve(null);
|
|
601
|
+
// While the site's work under its locks runs, its notice may
|
|
602
|
+
// wait for it (kept for a lock this run lost, or another
|
|
603
|
+
// completion's claim queued behind one it holds): the wait
|
|
604
|
+
// ends now, retryable, and is never what holds that work up.
|
|
605
|
+
// Once that work ended, nothing of the caller's is in its way.
|
|
606
|
+
if (action.locks?.some(locks_js_1.lockWorkRunning))
|
|
607
|
+
end?.("queued");
|
|
608
|
+
}
|
|
300
609
|
if (mounted.current)
|
|
301
610
|
setTimeout(() => finish(seen, receipt), index_js_1.RECEIPT_RETRY_MS);
|
|
302
611
|
return;
|
|
@@ -306,8 +615,8 @@ function useReviewedRun(deps) {
|
|
|
306
615
|
shown = true;
|
|
307
616
|
// Shown with the receipt completion checked, a failure's too.
|
|
308
617
|
showSettled(id, target.step, total, ended.settled, ended.receipt, ended.settled.result === "success" && target.step === total - 1
|
|
309
|
-
? acknowledging((0, index_js_1.openStepHold)(deps.hold,
|
|
310
|
-
: null);
|
|
618
|
+
? acknowledging((0, index_js_1.openStepHold)(deps.hold, action$.account, false, target.held.call), ended.settled)
|
|
619
|
+
: null, action$.account, target.held?.reservation);
|
|
311
620
|
setWatched(null);
|
|
312
621
|
end?.(ended);
|
|
313
622
|
});
|
|
@@ -319,9 +628,10 @@ function useReviewedRun(deps) {
|
|
|
319
628
|
* found one, or null when the wallet could not say (its Safe mode unknown).
|
|
320
629
|
*/
|
|
321
630
|
const follow = async (finish, released) => {
|
|
322
|
-
if (latest.current !== id || !deps
|
|
631
|
+
if (latest.current !== id || !deps || !runAccount)
|
|
323
632
|
return false;
|
|
324
|
-
const {
|
|
633
|
+
const { adapter, hold, storage } = deps;
|
|
634
|
+
const account = runAccount;
|
|
325
635
|
// Its Safe mode is read only once the wallet says it is known.
|
|
326
636
|
try {
|
|
327
637
|
await (0, index_js_1.walletReady)(adapter);
|
|
@@ -332,36 +642,55 @@ function useReviewedRun(deps) {
|
|
|
332
642
|
try {
|
|
333
643
|
if (latest.current !== id)
|
|
334
644
|
return false;
|
|
335
|
-
const safe =
|
|
645
|
+
const safe = runSafe();
|
|
646
|
+
if (safe === undefined)
|
|
647
|
+
return null;
|
|
336
648
|
const saved = !action.scope
|
|
337
649
|
? null
|
|
338
650
|
: (0, index_js_1.findReviewedSequence)(action.scope, account, { storage });
|
|
339
651
|
const total = saved?.calls.length ?? action.calls.length;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
call: saved.calls[saved.sending.step],
|
|
345
|
-
step: saved.sending.step,
|
|
346
|
-
},
|
|
347
|
-
]
|
|
348
|
-
: !saved && action.batch && safe && action.calls.length > 1
|
|
349
|
-
? [
|
|
350
|
-
{
|
|
351
|
-
call: (0, index_js_1.safeBatchCall)(account, action.calls),
|
|
352
|
-
step: total - 1,
|
|
353
|
-
},
|
|
354
|
-
]
|
|
355
|
-
: action.calls.map((call, step) => ({ call, step }));
|
|
356
|
-
// A held step, or the last step's success nothing showed.
|
|
652
|
+
const steps = heldSteps(account, safe, action, saved);
|
|
653
|
+
// A held step, or the last step's success nothing showed, of this
|
|
654
|
+
// run (or of the saved sequence it resumes), never another run's.
|
|
655
|
+
let away = null;
|
|
357
656
|
const found = steps
|
|
358
657
|
.map(({ call, step }) => ({
|
|
359
658
|
call,
|
|
360
659
|
step,
|
|
361
660
|
handle: (0, index_js_1.openStepHold)(hold, account, safe, call),
|
|
362
661
|
}))
|
|
363
|
-
.find(({ handle, step }) =>
|
|
364
|
-
|
|
662
|
+
.find(({ handle, step, call }) => {
|
|
663
|
+
const held = handle.peek();
|
|
664
|
+
const success = step === total - 1 ? handle.unacknowledgedSuccess() : undefined;
|
|
665
|
+
if (held.status === "clear" && !success)
|
|
666
|
+
return false;
|
|
667
|
+
const reservation = held.status !== "clear"
|
|
668
|
+
? held.reservation
|
|
669
|
+
: success?.reservation;
|
|
670
|
+
// A write held before this run, under an intent other forms
|
|
671
|
+
// share, is followed only when this runner followed it before,
|
|
672
|
+
// or it is this exact call (or this saved sequence's): another
|
|
673
|
+
// form's write is never adopted.
|
|
674
|
+
if (!reservation ||
|
|
675
|
+
!before.has(reservation) ||
|
|
676
|
+
mine.current.has(reservation) ||
|
|
677
|
+
reservation === saved?.sending?.reservation ||
|
|
678
|
+
!call.intent)
|
|
679
|
+
return true;
|
|
680
|
+
// A success another runner left settled while away, of this
|
|
681
|
+
// call or another form's: only shown, with "Send it again", so
|
|
682
|
+
// the refused form is never stuck. Never followed, so no
|
|
683
|
+
// dismissal makes it this form's to close a changed call on.
|
|
684
|
+
if (held.status === "clear") {
|
|
685
|
+
away = { handle, hash: success.hash };
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
const record = (0, index_js_1.listReviewedWrites)({ chainId: call.chainId, account }, { storage }).find(({ record }) => record.id === reservation)?.record;
|
|
689
|
+
return (!!record &&
|
|
690
|
+
record.call.to.toLowerCase() === call.to.toLowerCase() &&
|
|
691
|
+
record.call.data.toLowerCase() === call.data.toLowerCase() &&
|
|
692
|
+
record.call.value === String(call.value ?? 0n));
|
|
693
|
+
});
|
|
365
694
|
// Its settlement completes it as a send's would: a proposal too.
|
|
366
695
|
const reservation = found?.handle.peek().reservation ??
|
|
367
696
|
found?.handle.unacknowledgedSuccess()?.reservation;
|
|
@@ -370,6 +699,7 @@ function useReviewedRun(deps) {
|
|
|
370
699
|
handle: found.handle,
|
|
371
700
|
client: deps.clientFor(found.call.chainId),
|
|
372
701
|
run: id,
|
|
702
|
+
account,
|
|
373
703
|
call: found.call,
|
|
374
704
|
reservation,
|
|
375
705
|
step: found.step,
|
|
@@ -383,8 +713,9 @@ function useReviewedRun(deps) {
|
|
|
383
713
|
}
|
|
384
714
|
: null);
|
|
385
715
|
// Only its hold is followed now: the confirm can close.
|
|
386
|
-
if (
|
|
387
|
-
|
|
716
|
+
if (reservation)
|
|
717
|
+
mine.current.add(reservation);
|
|
718
|
+
update(found ? { following: true, away: null } : { away });
|
|
388
719
|
return !!found;
|
|
389
720
|
}
|
|
390
721
|
catch {
|
|
@@ -394,13 +725,17 @@ function useReviewedRun(deps) {
|
|
|
394
725
|
};
|
|
395
726
|
const heldStep = (target) => {
|
|
396
727
|
const call = target.held?.call ?? action.calls[target.step];
|
|
397
|
-
if (!deps
|
|
728
|
+
if (!deps || !runAccount || !call)
|
|
398
729
|
return null;
|
|
399
730
|
try {
|
|
731
|
+
const safe = runSafe();
|
|
732
|
+
if (safe === undefined)
|
|
733
|
+
return null;
|
|
400
734
|
return {
|
|
401
|
-
handle: (0, index_js_1.openStepHold)(deps.hold,
|
|
735
|
+
handle: (0, index_js_1.openStepHold)(deps.hold, runAccount, safe, call),
|
|
402
736
|
client: deps.clientFor(call.chainId),
|
|
403
737
|
call,
|
|
738
|
+
account: runAccount,
|
|
404
739
|
reservation: target.held?.reservation,
|
|
405
740
|
};
|
|
406
741
|
}
|
|
@@ -409,10 +744,10 @@ function useReviewedRun(deps) {
|
|
|
409
744
|
}
|
|
410
745
|
};
|
|
411
746
|
try {
|
|
412
|
-
if (!deps
|
|
747
|
+
if (!deps || !runAccount)
|
|
413
748
|
throw new lineError_js_1.ReviewLineError("Connect a wallet first.");
|
|
414
749
|
if (action.account &&
|
|
415
|
-
action.account.toLowerCase() !==
|
|
750
|
+
action.account.toLowerCase() !== runAccount.toLowerCase())
|
|
416
751
|
throw new lineError_js_1.ReviewLineError(index_js_1.REVIEWED_ACCOUNT_CHANGED_LINE);
|
|
417
752
|
if (action.calls.length > 1 && action.scope === undefined)
|
|
418
753
|
throw new lineError_js_1.ReviewLineError(exports.REVIEWED_RUN_SCOPE_LINE);
|
|
@@ -433,7 +768,14 @@ function useReviewedRun(deps) {
|
|
|
433
768
|
const { resumeReviewed, sendReviewed } = loaded;
|
|
434
769
|
// A success already shown is acknowledged before this run checks it.
|
|
435
770
|
await acknowledged.current;
|
|
436
|
-
const
|
|
771
|
+
const settledOwn = () => {
|
|
772
|
+
const settled = ownSuccess(action);
|
|
773
|
+
if (settled)
|
|
774
|
+
throw new OwnSucceeded(settled);
|
|
775
|
+
};
|
|
776
|
+
settledOwn();
|
|
777
|
+
const { guard } = deps;
|
|
778
|
+
const account = runAccount;
|
|
437
779
|
const tracked = {
|
|
438
780
|
...deps,
|
|
439
781
|
onProgress: (progress) => {
|
|
@@ -490,9 +832,20 @@ function useReviewedRun(deps) {
|
|
|
490
832
|
(0, receipt_js_1.tell)(action.follow?.onConfirming, step);
|
|
491
833
|
},
|
|
492
834
|
};
|
|
835
|
+
const assertWriteState = () => {
|
|
836
|
+
settledOwn();
|
|
837
|
+
action.assertWriteState?.();
|
|
838
|
+
};
|
|
839
|
+
const gated = {
|
|
840
|
+
...action,
|
|
841
|
+
guard,
|
|
842
|
+
follow: inPlace,
|
|
843
|
+
onSubmitted,
|
|
844
|
+
assertWriteState,
|
|
845
|
+
};
|
|
493
846
|
const result = saved
|
|
494
|
-
? await resumeReviewed(saved,
|
|
495
|
-
: await sendReviewed({ ...
|
|
847
|
+
? await resumeReviewed(saved, gated, tracked)
|
|
848
|
+
: await sendReviewed({ ...gated, account }, tracked);
|
|
496
849
|
// A wallet that reviews on its own page reports no progress: the
|
|
497
850
|
// result is the run's last word.
|
|
498
851
|
const calls = saved?.calls ?? action.calls;
|
|
@@ -517,6 +870,8 @@ function useReviewedRun(deps) {
|
|
|
517
870
|
return result;
|
|
518
871
|
}
|
|
519
872
|
catch (error) {
|
|
873
|
+
if (error instanceof OwnSucceeded)
|
|
874
|
+
return close(error.found);
|
|
520
875
|
if (error instanceof index_js_1.ReviewedReceiptUnverifiedError) {
|
|
521
876
|
// Still confirming: followed until its hold settles it.
|
|
522
877
|
update({
|
|
@@ -545,7 +900,12 @@ function useReviewedRun(deps) {
|
|
|
545
900
|
leave.current = abandon;
|
|
546
901
|
cancelled?.addEventListener("abort", abandon, { once: true });
|
|
547
902
|
awaiting.current = {
|
|
903
|
+
run: id,
|
|
548
904
|
last: error.step === action.calls.length - 1,
|
|
905
|
+
owner: {
|
|
906
|
+
account: runAccount,
|
|
907
|
+
reservation: error.held?.reservation,
|
|
908
|
+
},
|
|
549
909
|
detached: heldStep(error),
|
|
550
910
|
end: abandon,
|
|
551
911
|
};
|
|
@@ -553,7 +913,6 @@ function useReviewedRun(deps) {
|
|
|
553
913
|
const finish = completing(error, action.calls.length, end);
|
|
554
914
|
// Followed for a later settlement; one already there is read once it attaches.
|
|
555
915
|
void follow(finish, () => end(null)).then(() => finish());
|
|
556
|
-
const before = error.held?.hashes ?? [];
|
|
557
916
|
return {
|
|
558
917
|
status: "receipt",
|
|
559
918
|
step: error.step,
|
|
@@ -564,6 +923,11 @@ function useReviewedRun(deps) {
|
|
|
564
923
|
ended: () => outcome.then(async (ran) => {
|
|
565
924
|
if (!ran)
|
|
566
925
|
return null;
|
|
926
|
+
if (ran === "queued") {
|
|
927
|
+
const failure = new locks_js_1.ConfirmationQueuedError(error.step);
|
|
928
|
+
const line = (0, index_js_1.errorLine)(failure);
|
|
929
|
+
return { status: "failed", error: line, failure };
|
|
930
|
+
}
|
|
567
931
|
const { settled, receipt } = ran;
|
|
568
932
|
if (settled.result !== "success") {
|
|
569
933
|
const line = (0, index_js_1.settledLine)(settled);
|
|
@@ -573,7 +937,8 @@ function useReviewedRun(deps) {
|
|
|
573
937
|
failure: new lineError_js_1.ReviewLineError(line),
|
|
574
938
|
};
|
|
575
939
|
}
|
|
576
|
-
|
|
940
|
+
// The run's own, as its hold saved them with the step.
|
|
941
|
+
const { signatures } = ran;
|
|
577
942
|
if (error.step < action.calls.length - 1) {
|
|
578
943
|
const started = await runLatest.current({
|
|
579
944
|
...action,
|
|
@@ -591,7 +956,7 @@ function useReviewedRun(deps) {
|
|
|
591
956
|
}
|
|
592
957
|
return {
|
|
593
958
|
status: "done",
|
|
594
|
-
hashes:
|
|
959
|
+
hashes: ran.hashes,
|
|
595
960
|
receipts: receipt ? [receipt] : [],
|
|
596
961
|
...(signatures ? { signatures } : {}),
|
|
597
962
|
};
|
|
@@ -600,9 +965,9 @@ function useReviewedRun(deps) {
|
|
|
600
965
|
}
|
|
601
966
|
// A resume can complete a step the wallet sent before the reload.
|
|
602
967
|
try {
|
|
603
|
-
const after = action.scope === undefined || !
|
|
968
|
+
const after = action.scope === undefined || !runAccount
|
|
604
969
|
? null
|
|
605
|
-
: (0, index_js_1.findReviewedSequence)(action.scope,
|
|
970
|
+
: (0, index_js_1.findReviewedSequence)(action.scope, runAccount, {
|
|
606
971
|
storage: deps.storage,
|
|
607
972
|
});
|
|
608
973
|
if (after?.completed.length &&
|
|
@@ -614,6 +979,19 @@ function useReviewedRun(deps) {
|
|
|
614
979
|
catch {
|
|
615
980
|
/* An unreadable sequence adds nothing to what this run saw. */
|
|
616
981
|
}
|
|
982
|
+
// Refused by a write already held (another form's, sharing the
|
|
983
|
+
// hold): this run follows it only as its own exact call.
|
|
984
|
+
const refused = error instanceof index_js_1.WriteHeldError
|
|
985
|
+
? error.state.reservation
|
|
986
|
+
: error instanceof index_js_1.WriteHoldUnacknowledgedSuccessError
|
|
987
|
+
? error.success.reservation
|
|
988
|
+
: undefined;
|
|
989
|
+
if (refused)
|
|
990
|
+
before.add(refused);
|
|
991
|
+
// ponytail: one call only; a sequence's journal keeps its steps.
|
|
992
|
+
const left = holds().filter(({ reservation }) => !before.has(reservation));
|
|
993
|
+
if (action.calls.length === 1 && left.length && runAccount)
|
|
994
|
+
changeOwn(runAccount, left.map(({ reservation }) => reservation), { kind: "keep" });
|
|
617
995
|
const cancelled = error instanceof index_js_1.TransactionReviewCancelledError ||
|
|
618
996
|
(0, index_js_1.isDefiniteWalletRejection)(error);
|
|
619
997
|
const line = cancelled ? null : (0, index_js_1.errorLine)(error);
|