@bananapus/nana-sdk-core 6.1.0 → 6.3.0
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/heldWrite.d.ts +6 -1
- package/dist/cjs/review/heldWrite.d.ts.map +1 -1
- package/dist/cjs/review/heldWrite.js +13 -6
- package/dist/cjs/review/heldWrite.js.map +1 -1
- package/dist/cjs/review/locks.d.ts +141 -2
- package/dist/cjs/review/locks.d.ts.map +1 -1
- package/dist/cjs/review/locks.js +406 -58
- 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 +17 -3
- package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
- package/dist/cjs/review/react/useReviewedRun.js +528 -68
- package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
- package/dist/cjs/review/react/useSavedAttempt.d.ts.map +1 -1
- package/dist/cjs/review/react/useSavedAttempt.js +12 -0
- package/dist/cjs/review/react/useSavedAttempt.js.map +1 -1
- package/dist/cjs/review/relayrSend.d.ts +20 -0
- package/dist/cjs/review/relayrSend.d.ts.map +1 -1
- package/dist/cjs/review/relayrSend.js +150 -105
- package/dist/cjs/review/relayrSend.js.map +1 -1
- package/dist/cjs/review/runStatus.d.ts +6 -2
- package/dist/cjs/review/runStatus.d.ts.map +1 -1
- package/dist/cjs/review/runStatus.js +3 -1
- package/dist/cjs/review/runStatus.js.map +1 -1
- package/dist/cjs/review/safeRelayr.d.ts +12 -2
- package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
- package/dist/cjs/review/safeRelayr.js +226 -156
- package/dist/cjs/review/safeRelayr.js.map +1 -1
- package/dist/cjs/review/savedAttempt.d.ts.map +1 -1
- package/dist/cjs/review/savedAttempt.js +7 -2
- package/dist/cjs/review/savedAttempt.js.map +1 -1
- package/dist/cjs/review/send.d.ts +46 -5
- package/dist/cjs/review/send.d.ts.map +1 -1
- package/dist/cjs/review/send.js +251 -58
- 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/sign.d.ts +6 -1
- package/dist/cjs/review/sign.d.ts.map +1 -1
- package/dist/cjs/review/sign.js +10 -4
- package/dist/cjs/review/sign.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 +16 -4
- package/dist/cjs/review/wallet.d.ts.map +1 -1
- package/dist/cjs/review/wallet.js +62 -17
- package/dist/cjs/review/wallet.js.map +1 -1
- package/dist/cjs/review/writeHold.d.ts +92 -14
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +102 -26
- package/dist/cjs/review/writeHold.js.map +1 -1
- package/dist/cjs/review/writeRecovery.d.ts +42 -7
- package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
- package/dist/cjs/review/writeRecovery.js +183 -21
- 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/heldWrite.d.ts +6 -1
- package/dist/esm/review/heldWrite.d.ts.map +1 -1
- package/dist/esm/review/heldWrite.js +13 -6
- package/dist/esm/review/heldWrite.js.map +1 -1
- package/dist/esm/review/locks.d.ts +141 -2
- package/dist/esm/review/locks.d.ts.map +1 -1
- package/dist/esm/review/locks.js +396 -58
- 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 +17 -3
- package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
- package/dist/esm/review/react/useReviewedRun.js +529 -69
- package/dist/esm/review/react/useReviewedRun.js.map +1 -1
- package/dist/esm/review/react/useSavedAttempt.d.ts.map +1 -1
- package/dist/esm/review/react/useSavedAttempt.js +12 -0
- package/dist/esm/review/react/useSavedAttempt.js.map +1 -1
- package/dist/esm/review/relayrSend.d.ts +20 -0
- package/dist/esm/review/relayrSend.d.ts.map +1 -1
- package/dist/esm/review/relayrSend.js +154 -109
- package/dist/esm/review/relayrSend.js.map +1 -1
- package/dist/esm/review/runStatus.d.ts +6 -2
- package/dist/esm/review/runStatus.d.ts.map +1 -1
- package/dist/esm/review/runStatus.js +3 -1
- package/dist/esm/review/runStatus.js.map +1 -1
- package/dist/esm/review/safeRelayr.d.ts +12 -2
- package/dist/esm/review/safeRelayr.d.ts.map +1 -1
- package/dist/esm/review/safeRelayr.js +226 -156
- package/dist/esm/review/safeRelayr.js.map +1 -1
- package/dist/esm/review/savedAttempt.d.ts.map +1 -1
- package/dist/esm/review/savedAttempt.js +7 -2
- package/dist/esm/review/savedAttempt.js.map +1 -1
- package/dist/esm/review/send.d.ts +46 -5
- package/dist/esm/review/send.d.ts.map +1 -1
- package/dist/esm/review/send.js +252 -59
- 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/sign.d.ts +6 -1
- package/dist/esm/review/sign.d.ts.map +1 -1
- package/dist/esm/review/sign.js +10 -4
- package/dist/esm/review/sign.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 +16 -4
- package/dist/esm/review/wallet.d.ts.map +1 -1
- package/dist/esm/review/wallet.js +62 -17
- package/dist/esm/review/wallet.js.map +1 -1
- package/dist/esm/review/writeHold.d.ts +92 -14
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +102 -26
- package/dist/esm/review/writeHold.js.map +1 -1
- package/dist/esm/review/writeRecovery.d.ts +42 -7
- package/dist/esm/review/writeRecovery.d.ts.map +1 -1
- package/dist/esm/review/writeRecovery.js +180 -22
- 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,12 +3,16 @@ 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,
|
|
14
|
+
stopped: false,
|
|
15
|
+
following: false,
|
|
12
16
|
lastSubmittedStep: null,
|
|
13
17
|
phase: "idle",
|
|
14
18
|
step: 0,
|
|
@@ -19,6 +23,7 @@ const IDLE = {
|
|
|
19
23
|
safeTxHash: null,
|
|
20
24
|
receipt: null,
|
|
21
25
|
settled: null,
|
|
26
|
+
acknowledge: null,
|
|
22
27
|
};
|
|
23
28
|
/** The bytes a call sends, for comparing two actions. */
|
|
24
29
|
const sent = ({ chainId, to, data, value, gas }) => [
|
|
@@ -28,6 +33,28 @@ const sent = ({ chainId, to, data, value, gas }) => [
|
|
|
28
33
|
value ?? 0n,
|
|
29
34
|
gas ?? null,
|
|
30
35
|
];
|
|
36
|
+
/** This form's earlier write of the same calls succeeded: the run closes on it, sending nothing. */
|
|
37
|
+
/**
|
|
38
|
+
* The holds `action`'s steps are held in, each with its step: a resumed
|
|
39
|
+
* sequence's sending step; a Safe app's batch as its one MultiSend
|
|
40
|
+
* proposal, its last step; or each call. The one home for that mapping.
|
|
41
|
+
*/
|
|
42
|
+
function heldSteps(account, safe, action, saved) {
|
|
43
|
+
if (saved?.sending)
|
|
44
|
+
return [
|
|
45
|
+
{ call: saved.calls[saved.sending.step], step: saved.sending.step },
|
|
46
|
+
];
|
|
47
|
+
const total = saved?.calls.length ?? action.calls.length;
|
|
48
|
+
return !saved && action.batch && safe && action.calls.length > 1
|
|
49
|
+
? [{ call: (0, index_js_1.safeBatchCall)(account, action.calls), step: total - 1 }]
|
|
50
|
+
: action.calls.map((call, step) => ({ call, step }));
|
|
51
|
+
}
|
|
52
|
+
class OwnSucceeded extends Error {
|
|
53
|
+
constructor(found) {
|
|
54
|
+
super("succeeded");
|
|
55
|
+
this.found = found;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
31
58
|
exports.REVIEWED_RUN_SCOPE_LINE = "This action has more than one step and no scope to resume it by. Nothing was sent.";
|
|
32
59
|
exports.REVIEWED_RUN_BUSY_LINE = "This action is still running. Wait for it to finish, then try again.";
|
|
33
60
|
/**
|
|
@@ -37,18 +64,46 @@ exports.REVIEWED_RUN_BUSY_LINE = "This action is still running. Wait for it to f
|
|
|
37
64
|
*/
|
|
38
65
|
function useReviewedRun(deps) {
|
|
39
66
|
const [state, setState] = (0, react_1.useState)(IDLE);
|
|
40
|
-
/** A cancel to tell once
|
|
67
|
+
/** A cancel or stop to tell once rendered. */
|
|
41
68
|
const [cancelling, setCancelling] = (0, react_1.useState)(null);
|
|
42
69
|
(0, react_1.useEffect)(() => {
|
|
43
70
|
if (cancelling)
|
|
44
|
-
void (0, receipt_js_1.tell)(cancelling.
|
|
71
|
+
void (0, receipt_js_1.tell)(cancelling.told);
|
|
45
72
|
}, [cancelling]);
|
|
73
|
+
/** Every shown success's acknowledgement: the next run waits for all. */
|
|
74
|
+
const acknowledged = (0, react_1.useRef)(Promise.resolve());
|
|
75
|
+
// A success is acknowledged only once its done rendered: a reset after
|
|
76
|
+
// that hides nothing, and one before it leaves the success warning. A
|
|
77
|
+
// later one never drops an earlier one still running from the wait.
|
|
78
|
+
(0, react_1.useEffect)(() => {
|
|
79
|
+
if (state.acknowledge)
|
|
80
|
+
acknowledged.current = Promise.all([
|
|
81
|
+
acknowledged.current,
|
|
82
|
+
state.acknowledge(),
|
|
83
|
+
]).then(() => undefined);
|
|
84
|
+
}, [state.acknowledge]);
|
|
46
85
|
/** The hold followed after a run, and its chain's client: its view is `held`. */
|
|
47
|
-
const [watched,
|
|
86
|
+
const [watched, setWatchedState] = (0, react_1.useState)(null);
|
|
87
|
+
/**
|
|
88
|
+
* `watched` as last set, for a handler that resets and runs from one
|
|
89
|
+
* render's handle (a "Try again"): never that render's stale copy.
|
|
90
|
+
*/
|
|
91
|
+
const watchedNow = (0, react_1.useRef)(null);
|
|
92
|
+
const setWatched = (next) => {
|
|
93
|
+
watchedNow.current = next;
|
|
94
|
+
setWatchedState(next);
|
|
95
|
+
};
|
|
48
96
|
/** The latest run: an earlier one's late answers change nothing. */
|
|
49
97
|
const latest = (0, react_1.useRef)(0);
|
|
98
|
+
/** The sent step whose hold the latest run's `ended()` awaits. */
|
|
99
|
+
const awaiting = (0, react_1.useRef)(null);
|
|
50
100
|
/** How many runs are open: a discard waits for none. */
|
|
51
101
|
const open = (0, react_1.useRef)(0);
|
|
102
|
+
/**
|
|
103
|
+
* The latest run's life: aborted when it is replaced, reset, dismissed or
|
|
104
|
+
* unmounted, so its completion acknowledges nothing it can no longer show.
|
|
105
|
+
*/
|
|
106
|
+
const life = (0, react_1.useRef)(new AbortController());
|
|
52
107
|
const mounted = (0, react_1.useRef)(false);
|
|
53
108
|
/** This render's `run`: a run's end that continues the action runs it. */
|
|
54
109
|
const runLatest = (0, react_1.useRef)(async () => null);
|
|
@@ -58,8 +113,106 @@ function useReviewedRun(deps) {
|
|
|
58
113
|
* hold keeps the sent step.
|
|
59
114
|
*/
|
|
60
115
|
const leave = (0, react_1.useRef)(() => { });
|
|
116
|
+
/** Every reservation this runner's runs followed: its own, whatever its calls now. */
|
|
117
|
+
const mine = (0, react_1.useRef)(new Set());
|
|
118
|
+
/**
|
|
119
|
+
* The reservations `calls`' holds hold now (a held write, or a success
|
|
120
|
+
* nothing showed), each with its hold.
|
|
121
|
+
*/
|
|
122
|
+
const holdsOf = (account, calls) => calls.flatMap((call) => {
|
|
123
|
+
if (!deps || !account)
|
|
124
|
+
return [];
|
|
125
|
+
try {
|
|
126
|
+
// A hold's key is its call's, whatever the Safe mode.
|
|
127
|
+
const handle = (0, index_js_1.openStepHold)(deps.hold, account, false, call);
|
|
128
|
+
const held = handle.peek();
|
|
129
|
+
const reservation = held.status !== "clear"
|
|
130
|
+
? held.reservation
|
|
131
|
+
: handle.unacknowledgedSuccess()?.reservation;
|
|
132
|
+
return reservation ? [{ handle, reservation }] : [];
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* The reservations this form's last run left unshown, or the last step
|
|
140
|
+
* `dismiss` closed while it was followed (`run`, whose later showing the
|
|
141
|
+
* user closed, never counts as seen), and the account that sent them.
|
|
142
|
+
* Their success, whoever acknowledged it, closes a later run under the
|
|
143
|
+
* same hold (its intent's, though the calls were refreshed) from the same
|
|
144
|
+
* account. Still pending, the hold refuses that run, which follows it again.
|
|
145
|
+
* One entry per account and reservation: keeping or showing one never
|
|
146
|
+
* touches another's.
|
|
147
|
+
*/
|
|
148
|
+
const own = (0, react_1.useRef)(new Map());
|
|
149
|
+
/**
|
|
150
|
+
* Every change to a form's write, under the account that sent it, goes
|
|
151
|
+
* through {@link mergeOwn}: forward only. A showing (`shown`) changes
|
|
152
|
+
* the reservation's entry whichever account it is under.
|
|
153
|
+
*/
|
|
154
|
+
const changeOwn = (account, reservations, change) => {
|
|
155
|
+
for (const reservation of reservations) {
|
|
156
|
+
if (!reservation)
|
|
157
|
+
continue;
|
|
158
|
+
const keys = [...own.current]
|
|
159
|
+
.filter(([, entry]) => change.kind === "shown" && entry.reservation === reservation)
|
|
160
|
+
.map(([key]) => key);
|
|
161
|
+
const mine = `${account.toLowerCase()}:${reservation}`;
|
|
162
|
+
for (const key of keys.length ? keys : [mine]) {
|
|
163
|
+
const entry = own.current.get(key);
|
|
164
|
+
own.current.set(key, (0, ownership_js_1.mergeOwn)(entry, entry?.account ?? account, reservation, change));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const ownSuccess = (action) => {
|
|
169
|
+
if (!deps?.account ||
|
|
170
|
+
(action.account &&
|
|
171
|
+
action.account.toLowerCase() !== deps.account.toLowerCase()))
|
|
172
|
+
return undefined;
|
|
173
|
+
const kept = [...own.current.values()]
|
|
174
|
+
.filter((entry) => entry.state === "owned" &&
|
|
175
|
+
entry.account.toLowerCase() === deps.account.toLowerCase())
|
|
176
|
+
.map((entry) => entry.reservation);
|
|
177
|
+
if (!kept.length)
|
|
178
|
+
return undefined;
|
|
179
|
+
// Only the action's last step's hold, as `follow` maps it: a success
|
|
180
|
+
// of an earlier step never completes the action. Its Safe mode may not
|
|
181
|
+
// be known yet; a reservation is only ever in its own mode's hold.
|
|
182
|
+
const account = deps.account;
|
|
183
|
+
const lasts = [false, true].map((safe) => {
|
|
184
|
+
const steps = heldSteps(account, safe, action);
|
|
185
|
+
return steps[steps.length - 1];
|
|
186
|
+
});
|
|
187
|
+
for (const { call, step } of lasts) {
|
|
188
|
+
try {
|
|
189
|
+
// A reservation is read only through its own hold: another hold
|
|
190
|
+
// (another intent, or another exact call) never answers for it.
|
|
191
|
+
const handle = (0, index_js_1.openStepHold)(deps.hold, deps.account, false, call);
|
|
192
|
+
for (const reservation of kept) {
|
|
193
|
+
const settled = handle.settlementOf(reservation);
|
|
194
|
+
if (settled?.result === "success")
|
|
195
|
+
return {
|
|
196
|
+
settled: settled,
|
|
197
|
+
handle,
|
|
198
|
+
account,
|
|
199
|
+
call: call,
|
|
200
|
+
step,
|
|
201
|
+
reservation,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
/* A call the hold can't open holds nothing of this form's. */
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
};
|
|
61
211
|
const replaced = () => {
|
|
62
212
|
latest.current += 1;
|
|
213
|
+
life.current.abort();
|
|
214
|
+
life.current = new AbortController();
|
|
215
|
+
awaiting.current = null;
|
|
63
216
|
const left = leave.current;
|
|
64
217
|
leave.current = () => { };
|
|
65
218
|
left();
|
|
@@ -73,9 +226,12 @@ function useReviewedRun(deps) {
|
|
|
73
226
|
};
|
|
74
227
|
}, []);
|
|
75
228
|
/** How a followed step ended, as the run that followed it shows it. */
|
|
76
|
-
const showSettled = (run, step, total, settled, receipt) => {
|
|
229
|
+
const showSettled = (run, step, total, settled, receipt, acknowledge, account, reservation) => {
|
|
77
230
|
if (latest.current !== run)
|
|
78
231
|
return;
|
|
232
|
+
// Shown: its own entry is released, unless `dismiss` closed it while
|
|
233
|
+
// this run followed it (its showing the user closed never counts).
|
|
234
|
+
changeOwn(account, [reservation], { kind: "shown", run });
|
|
79
235
|
const finished = step === total - 1;
|
|
80
236
|
setState((now) => settled.result !== "success"
|
|
81
237
|
? {
|
|
@@ -87,12 +243,14 @@ function useReviewedRun(deps) {
|
|
|
87
243
|
: finished
|
|
88
244
|
? {
|
|
89
245
|
...now,
|
|
246
|
+
following: false,
|
|
90
247
|
phase: "done",
|
|
91
248
|
hash: settled.hash,
|
|
92
249
|
receipt,
|
|
93
250
|
error: null,
|
|
94
251
|
failure: null,
|
|
95
252
|
settled: { step, finished },
|
|
253
|
+
acknowledge,
|
|
96
254
|
}
|
|
97
255
|
: {
|
|
98
256
|
...IDLE,
|
|
@@ -113,6 +271,11 @@ function useReviewedRun(deps) {
|
|
|
113
271
|
// never stays proposed, and never shows done unverified.
|
|
114
272
|
onSettled: (settled, receipt) => watched.finish(settled, receipt),
|
|
115
273
|
onReleased: () => {
|
|
274
|
+
// A later reservation's release (another tab's) never ends a
|
|
275
|
+
// write that settled: its settlement completes it.
|
|
276
|
+
if (watched.reservation &&
|
|
277
|
+
watched.handle.settlementOf(watched.reservation))
|
|
278
|
+
return;
|
|
116
279
|
watched.released?.();
|
|
117
280
|
if (latest.current === watched.run)
|
|
118
281
|
setState((now) => ({
|
|
@@ -146,16 +309,68 @@ function useReviewedRun(deps) {
|
|
|
146
309
|
shown.phase === "settled"
|
|
147
310
|
? null
|
|
148
311
|
: shown;
|
|
149
|
-
const { cancelled: _cancelled, ...shownState } = state;
|
|
312
|
+
const { cancelled: _cancelled, stopped: _stopped, acknowledge: _acknowledge, ...shownState } = state;
|
|
313
|
+
const status = (0, index_js_1.reviewedRunStatus)({ ...state, held });
|
|
314
|
+
const reset = () => {
|
|
315
|
+
replaced();
|
|
316
|
+
setState(IDLE);
|
|
317
|
+
setWatched(null);
|
|
318
|
+
};
|
|
319
|
+
const detach = (target) => {
|
|
320
|
+
// ponytail: unbounded; it ends once the hold is clear.
|
|
321
|
+
if (target)
|
|
322
|
+
void (0, index_js_1.followHeldWrite)(target.handle, target.client, {
|
|
323
|
+
signal: new AbortController().signal,
|
|
324
|
+
...(target.reservation ? { reservation: target.reservation } : {}),
|
|
325
|
+
}).then((end) => end?.status === "clear" &&
|
|
326
|
+
end.settled?.result === "success" &&
|
|
327
|
+
(0, receipt_js_1.tell)(deps?.onDismissedSettled, target.call), () => undefined);
|
|
328
|
+
};
|
|
150
329
|
const api = {
|
|
151
330
|
...shownState,
|
|
152
|
-
...
|
|
331
|
+
...status,
|
|
332
|
+
busy: status.busy && !state.following,
|
|
153
333
|
held,
|
|
154
334
|
...(0, heldView_js_1.actionsOf)(shown, held ? view : undefined),
|
|
155
|
-
reset
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
335
|
+
reset,
|
|
336
|
+
dismiss: () => {
|
|
337
|
+
const waiting = awaiting.current;
|
|
338
|
+
if (open.current && !waiting)
|
|
339
|
+
return false;
|
|
340
|
+
/**
|
|
341
|
+
* The followed last step this closes, under the account that sent it,
|
|
342
|
+
* and the run that followed it: a retry of it never sends it again.
|
|
343
|
+
*/
|
|
344
|
+
const last = waiting
|
|
345
|
+
? waiting.last && { ...waiting.owner, run: waiting.run }
|
|
346
|
+
: watched?.step === watched?.total - 1 && watched;
|
|
347
|
+
const closing = () => {
|
|
348
|
+
if (last)
|
|
349
|
+
changeOwn(last.account, [last.reservation], {
|
|
350
|
+
kind: "dismiss",
|
|
351
|
+
run: last.run,
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
if (waiting?.last) {
|
|
355
|
+
awaiting.current = null;
|
|
356
|
+
waiting.end();
|
|
357
|
+
closing();
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
detach(waiting
|
|
361
|
+
? waiting.detached
|
|
362
|
+
: watched && {
|
|
363
|
+
handle: watched.handle,
|
|
364
|
+
client: watched.client,
|
|
365
|
+
call: watched.call,
|
|
366
|
+
account: watched.account,
|
|
367
|
+
reservation: watched.reservation,
|
|
368
|
+
});
|
|
369
|
+
// Closing the view keeps what this form sent: a reset forgets no
|
|
370
|
+
// entry, so a later dismiss of the idle view never forgets one.
|
|
371
|
+
reset();
|
|
372
|
+
closing();
|
|
373
|
+
return true;
|
|
159
374
|
},
|
|
160
375
|
preload,
|
|
161
376
|
discard: async (scope, account = deps?.account) => {
|
|
@@ -173,9 +388,106 @@ function useReviewedRun(deps) {
|
|
|
173
388
|
setState(IDLE);
|
|
174
389
|
setWatched(null);
|
|
175
390
|
},
|
|
176
|
-
run: async ({ onCancelled, ...action }) => {
|
|
391
|
+
run: async ({ onCancelled, onStopped, ...action }) => {
|
|
392
|
+
/**
|
|
393
|
+
* The account this run sends from, read once: every continuation of
|
|
394
|
+
* it uses this one, whatever account the app's deps hold by then.
|
|
395
|
+
*/
|
|
396
|
+
const runAccount = deps?.account;
|
|
397
|
+
/**
|
|
398
|
+
* The wallet's Safe mode, only while it still speaks for the run's
|
|
399
|
+
* account: another account's mode never names this run's holds.
|
|
400
|
+
*/
|
|
401
|
+
const runSafe = () => deps && deps.account?.toLowerCase() === runAccount?.toLowerCase()
|
|
402
|
+
? deps.adapter.isSafe()
|
|
403
|
+
: undefined;
|
|
404
|
+
/** This form's unshown write of these calls succeeded: the form closes on it. */
|
|
405
|
+
const close = async (found) => {
|
|
406
|
+
const { settled, handle } = found;
|
|
407
|
+
// Every exit holds the run open, and this form's ownership stays
|
|
408
|
+
// until its success is shown and acknowledged: until then a dismiss
|
|
409
|
+
// is refused and a later run closes on the same success again.
|
|
410
|
+
open.current += 1;
|
|
411
|
+
try {
|
|
412
|
+
// Completed as its run would have, under this run's locks: told to
|
|
413
|
+
// onConfirmed once (unless another completion did), and done only
|
|
414
|
+
// once delivered.
|
|
415
|
+
let failure;
|
|
416
|
+
const told = await load(deps.engine)
|
|
417
|
+
.then((engine) => engine.completeReviewedStep({
|
|
418
|
+
step: found.step,
|
|
419
|
+
held: { call: found.call, reservation: found.reservation },
|
|
420
|
+
}, {
|
|
421
|
+
account: found.account,
|
|
422
|
+
scope: action.scope,
|
|
423
|
+
onConfirmed: action.onConfirmed,
|
|
424
|
+
locks: action.locks,
|
|
425
|
+
attemptScoped: action.attemptScoped,
|
|
426
|
+
}, deps, null, alive, false))
|
|
427
|
+
.catch((error) => {
|
|
428
|
+
failure = error;
|
|
429
|
+
return null;
|
|
430
|
+
});
|
|
431
|
+
if (!told || told.undelivered) {
|
|
432
|
+
failure ??= new locks_js_1.LockLostError(action.locks?.[0]?.name ?? "site");
|
|
433
|
+
const error = (0, index_js_1.errorLine)(failure);
|
|
434
|
+
update({ ...IDLE, error, failure });
|
|
435
|
+
return { status: "failed", error, failure };
|
|
436
|
+
}
|
|
437
|
+
const step = action.calls.length - 1;
|
|
438
|
+
update({
|
|
439
|
+
...IDLE,
|
|
440
|
+
// Shown now: acknowledged once rendered, so it blocks no new send.
|
|
441
|
+
// Rendered: the form's own is released, and a later run waits
|
|
442
|
+
// for this acknowledgement before it sends.
|
|
443
|
+
acknowledge: async () => {
|
|
444
|
+
if (alive.aborted)
|
|
445
|
+
return;
|
|
446
|
+
changeOwn(found.account, [found.reservation], {
|
|
447
|
+
kind: "shown",
|
|
448
|
+
});
|
|
449
|
+
await handle.acknowledge(settled).catch(() => undefined);
|
|
450
|
+
},
|
|
451
|
+
phase: "done",
|
|
452
|
+
step,
|
|
453
|
+
total: action.calls.length,
|
|
454
|
+
hash: settled.hash,
|
|
455
|
+
settled: { step, finished: true },
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
status: "done",
|
|
459
|
+
hashes: told.hashes,
|
|
460
|
+
receipts: [],
|
|
461
|
+
...(told.signatures ? { signatures: told.signatures } : {}),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
finally {
|
|
465
|
+
open.current -= 1;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
// This form's own write is still followed: a retry of it is refused
|
|
469
|
+
// with the hold's line, and the run following it goes on.
|
|
470
|
+
const now = watchedNow.current;
|
|
471
|
+
// Identity, never bytes: the followed reservation is one of these
|
|
472
|
+
// calls' holds'. A scoped sequence's journal answers for its own.
|
|
473
|
+
const followed = now?.reservation &&
|
|
474
|
+
runAccount &&
|
|
475
|
+
(action.scope === undefined || action.calls.length === 1)
|
|
476
|
+
? now
|
|
477
|
+
: null;
|
|
478
|
+
const ownState = followed?.handle.peek();
|
|
479
|
+
if (followed &&
|
|
480
|
+
ownState?.status !== "clear" &&
|
|
481
|
+
ownState?.reservation === followed.reservation &&
|
|
482
|
+
holdsOf(runAccount, action.calls).some(({ reservation }) => reservation === followed.reservation)) {
|
|
483
|
+
const failure = new index_js_1.WriteHeldError(ownState);
|
|
484
|
+
const error = (0, index_js_1.errorLine)(failure);
|
|
485
|
+
setState((now) => ({ ...now, error, failure }));
|
|
486
|
+
return { status: "failed", error, failure };
|
|
487
|
+
}
|
|
177
488
|
replaced();
|
|
178
489
|
const id = latest.current;
|
|
490
|
+
const { signal: alive } = life.current;
|
|
179
491
|
// A step still confirming is followed in place, in the same review,
|
|
180
492
|
// until a reset or a later run ends it.
|
|
181
493
|
const following = new AbortController();
|
|
@@ -186,6 +498,21 @@ function useReviewedRun(deps) {
|
|
|
186
498
|
setState((now) => ({ ...now, ...next }));
|
|
187
499
|
};
|
|
188
500
|
setState({ ...IDLE, phase: "review", total: action.calls.length });
|
|
501
|
+
const succeeded = ownSuccess(action);
|
|
502
|
+
if (succeeded) {
|
|
503
|
+
setWatched(null);
|
|
504
|
+
// close holds the run open itself, from here until it ends.
|
|
505
|
+
open.current -= 1;
|
|
506
|
+
return close(succeeded);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Reservations the action's holds had before this run: another run's
|
|
510
|
+
* (another form's, sharing a hold). This run never follows one.
|
|
511
|
+
*/
|
|
512
|
+
const holds = () => holdsOf(runAccount, runAccount && action.batch && action.calls.length > 1
|
|
513
|
+
? [...action.calls, (0, index_js_1.safeBatchCall)(runAccount, action.calls)]
|
|
514
|
+
: action.calls);
|
|
515
|
+
const before = new Set(holds().map(({ reservation }) => reservation));
|
|
189
516
|
let loaded;
|
|
190
517
|
/** A step of the action was sent before this run, or by it: never a cancel. */
|
|
191
518
|
let sentAny = false;
|
|
@@ -197,36 +524,65 @@ function useReviewedRun(deps) {
|
|
|
197
524
|
update({ lastSubmittedStep: step });
|
|
198
525
|
};
|
|
199
526
|
setWatched(null);
|
|
527
|
+
/** Acknowledges a success this run showed, unless the run is gone. */
|
|
528
|
+
const acknowledging = (handle, settled) => async () => {
|
|
529
|
+
if (!alive.aborted)
|
|
530
|
+
await handle.acknowledge(settled).catch(() => undefined);
|
|
531
|
+
};
|
|
200
532
|
/**
|
|
201
533
|
* Completes a sent step once its hold settled it, as the run would
|
|
202
534
|
* have (`completeReviewedStep`: the journal, `onConfirmed` once with
|
|
203
|
-
* the receipt its hold verified
|
|
204
|
-
*
|
|
535
|
+
* the receipt its hold verified), then shows how it ended, once. A
|
|
536
|
+
* last step's success is acknowledged once that rendered; an earlier
|
|
537
|
+
* step's at once, its sequence's journal keeping it from sending again. Until it can (the receipt unread, or the hold not yet
|
|
205
538
|
* settled for this send), it is tried again, never shown.
|
|
206
539
|
*/
|
|
207
540
|
const completing = (target, total, end) => {
|
|
208
541
|
let completed = Promise.resolve(null);
|
|
209
542
|
let shown = false;
|
|
210
543
|
const action$ = {
|
|
211
|
-
account:
|
|
544
|
+
account: runAccount,
|
|
212
545
|
scope: action.scope,
|
|
213
546
|
onConfirmed: action.onConfirmed,
|
|
547
|
+
locks: action.locks,
|
|
548
|
+
attemptScoped: action.attemptScoped,
|
|
214
549
|
};
|
|
215
550
|
const finish = (seen, receipt = null) => {
|
|
551
|
+
if (shown || alive.aborted)
|
|
552
|
+
return;
|
|
216
553
|
let unread = false;
|
|
217
554
|
completed = completed.then((prior) => prior ??
|
|
218
555
|
(loaded ? Promise.resolve(loaded) : load(deps.engine))
|
|
219
|
-
.then((engine) => engine.completeReviewedStep(target, action$, deps, receipt))
|
|
556
|
+
.then((engine) => engine.completeReviewedStep(target, action$, deps, receipt, alive, target.step < total - 1))
|
|
220
557
|
.catch(() => {
|
|
221
558
|
unread = true;
|
|
222
559
|
return null;
|
|
223
560
|
}));
|
|
224
561
|
void completed.then((ended) => {
|
|
225
|
-
|
|
562
|
+
// Its notice undelivered: the form's own, whichever run shows
|
|
563
|
+
// now (a retry that replaced this one included), so a later
|
|
564
|
+
// run closes on it, telling it then.
|
|
565
|
+
if (ended?.undelivered && target.step === total - 1)
|
|
566
|
+
changeOwn(action$.account, [target.held?.reservation], {
|
|
567
|
+
kind: "keep",
|
|
568
|
+
});
|
|
569
|
+
if (shown || alive.aborted)
|
|
226
570
|
return;
|
|
227
|
-
// Nothing advanced (its receipt unread,
|
|
228
|
-
// for this send yet): it is
|
|
229
|
-
|
|
571
|
+
// Nothing advanced (its receipt unread, its hold not settled
|
|
572
|
+
// for this send yet, or its onConfirmed not delivered): it is
|
|
573
|
+
// tried again, still followed and acknowledged by nothing.
|
|
574
|
+
// ponytail: retried at the receipt's pace while mounted.
|
|
575
|
+
if (unread || (!ended && seen) || ended?.undelivered) {
|
|
576
|
+
if (ended?.undelivered) {
|
|
577
|
+
completed = Promise.resolve(null);
|
|
578
|
+
// While the site's work under its locks runs, its notice may
|
|
579
|
+
// wait for it (kept for a lock this run lost, or another
|
|
580
|
+
// completion's claim queued behind one it holds): the wait
|
|
581
|
+
// ends now, retryable, and is never what holds that work up.
|
|
582
|
+
// Once that work ended, nothing of the caller's is in its way.
|
|
583
|
+
if (action.locks?.some(locks_js_1.lockWorkRunning))
|
|
584
|
+
end?.("queued");
|
|
585
|
+
}
|
|
230
586
|
if (mounted.current)
|
|
231
587
|
setTimeout(() => finish(seen, receipt), index_js_1.RECEIPT_RETRY_MS);
|
|
232
588
|
return;
|
|
@@ -235,7 +591,9 @@ function useReviewedRun(deps) {
|
|
|
235
591
|
return;
|
|
236
592
|
shown = true;
|
|
237
593
|
// Shown with the receipt completion checked, a failure's too.
|
|
238
|
-
showSettled(id, target.step, total, ended.settled, ended.receipt
|
|
594
|
+
showSettled(id, target.step, total, ended.settled, ended.receipt, ended.settled.result === "success" && target.step === total - 1
|
|
595
|
+
? acknowledging((0, index_js_1.openStepHold)(deps.hold, action$.account, false, target.held.call), ended.settled)
|
|
596
|
+
: null, action$.account, target.held?.reservation);
|
|
239
597
|
setWatched(null);
|
|
240
598
|
end?.(ended);
|
|
241
599
|
});
|
|
@@ -247,9 +605,10 @@ function useReviewedRun(deps) {
|
|
|
247
605
|
* found one, or null when the wallet could not say (its Safe mode unknown).
|
|
248
606
|
*/
|
|
249
607
|
const follow = async (finish, released) => {
|
|
250
|
-
if (latest.current !== id || !deps
|
|
608
|
+
if (latest.current !== id || !deps || !runAccount)
|
|
251
609
|
return false;
|
|
252
|
-
const {
|
|
610
|
+
const { adapter, hold, storage } = deps;
|
|
611
|
+
const account = runAccount;
|
|
253
612
|
// Its Safe mode is read only once the wallet says it is known.
|
|
254
613
|
try {
|
|
255
614
|
await (0, index_js_1.walletReady)(adapter);
|
|
@@ -260,36 +619,48 @@ function useReviewedRun(deps) {
|
|
|
260
619
|
try {
|
|
261
620
|
if (latest.current !== id)
|
|
262
621
|
return false;
|
|
263
|
-
const safe =
|
|
622
|
+
const safe = runSafe();
|
|
623
|
+
if (safe === undefined)
|
|
624
|
+
return null;
|
|
264
625
|
const saved = !action.scope
|
|
265
626
|
? null
|
|
266
627
|
: (0, index_js_1.findReviewedSequence)(action.scope, account, { storage });
|
|
267
628
|
const total = saved?.calls.length ?? action.calls.length;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
{
|
|
272
|
-
call: saved.calls[saved.sending.step],
|
|
273
|
-
step: saved.sending.step,
|
|
274
|
-
},
|
|
275
|
-
]
|
|
276
|
-
: !saved && action.batch && safe && action.calls.length > 1
|
|
277
|
-
? [
|
|
278
|
-
{
|
|
279
|
-
call: (0, index_js_1.safeBatchCall)(account, action.calls),
|
|
280
|
-
step: total - 1,
|
|
281
|
-
},
|
|
282
|
-
]
|
|
283
|
-
: action.calls.map((call, step) => ({ call, step }));
|
|
284
|
-
// A held step, or the last step's success nothing showed.
|
|
629
|
+
const steps = heldSteps(account, safe, action, saved);
|
|
630
|
+
// A held step, or the last step's success nothing showed, of this
|
|
631
|
+
// run (or of the saved sequence it resumes), never another run's.
|
|
285
632
|
const found = steps
|
|
286
633
|
.map(({ call, step }) => ({
|
|
287
634
|
call,
|
|
288
635
|
step,
|
|
289
636
|
handle: (0, index_js_1.openStepHold)(hold, account, safe, call),
|
|
290
637
|
}))
|
|
291
|
-
.find(({ handle, step }) =>
|
|
292
|
-
|
|
638
|
+
.find(({ handle, step, call }) => {
|
|
639
|
+
const held = handle.peek();
|
|
640
|
+
const success = step === total - 1 ? handle.unacknowledgedSuccess() : undefined;
|
|
641
|
+
if (held.status === "clear" && !success)
|
|
642
|
+
return false;
|
|
643
|
+
const reservation = held.status !== "clear"
|
|
644
|
+
? held.reservation
|
|
645
|
+
: success?.reservation;
|
|
646
|
+
// A write held before this run, under an intent other forms
|
|
647
|
+
// share, is followed only when this runner followed it before,
|
|
648
|
+
// or it is this exact call (or this saved sequence's): another
|
|
649
|
+
// form's write is never adopted.
|
|
650
|
+
if (!reservation ||
|
|
651
|
+
!before.has(reservation) ||
|
|
652
|
+
mine.current.has(reservation) ||
|
|
653
|
+
reservation === saved?.sending?.reservation ||
|
|
654
|
+
!call.intent)
|
|
655
|
+
return true;
|
|
656
|
+
if (held.status === "clear")
|
|
657
|
+
return false;
|
|
658
|
+
const record = (0, index_js_1.listReviewedWrites)({ chainId: call.chainId, account }, { storage }).find(({ record }) => record.id === reservation)?.record;
|
|
659
|
+
return (!!record &&
|
|
660
|
+
record.call.to.toLowerCase() === call.to.toLowerCase() &&
|
|
661
|
+
record.call.data.toLowerCase() === call.data.toLowerCase() &&
|
|
662
|
+
record.call.value === String(call.value ?? 0n));
|
|
663
|
+
});
|
|
293
664
|
// Its settlement completes it as a send's would: a proposal too.
|
|
294
665
|
const reservation = found?.handle.peek().reservation ??
|
|
295
666
|
found?.handle.unacknowledgedSuccess()?.reservation;
|
|
@@ -298,6 +669,9 @@ function useReviewedRun(deps) {
|
|
|
298
669
|
handle: found.handle,
|
|
299
670
|
client: deps.clientFor(found.call.chainId),
|
|
300
671
|
run: id,
|
|
672
|
+
account,
|
|
673
|
+
call: found.call,
|
|
674
|
+
reservation,
|
|
301
675
|
step: found.step,
|
|
302
676
|
total,
|
|
303
677
|
finish: finish ??
|
|
@@ -308,6 +682,11 @@ function useReviewedRun(deps) {
|
|
|
308
682
|
...(released ? { released } : {}),
|
|
309
683
|
}
|
|
310
684
|
: null);
|
|
685
|
+
// Only its hold is followed now: the confirm can close.
|
|
686
|
+
if (reservation)
|
|
687
|
+
mine.current.add(reservation);
|
|
688
|
+
if (found)
|
|
689
|
+
update({ following: true });
|
|
311
690
|
return !!found;
|
|
312
691
|
}
|
|
313
692
|
catch {
|
|
@@ -315,11 +694,31 @@ function useReviewedRun(deps) {
|
|
|
315
694
|
return false;
|
|
316
695
|
}
|
|
317
696
|
};
|
|
697
|
+
const heldStep = (target) => {
|
|
698
|
+
const call = target.held?.call ?? action.calls[target.step];
|
|
699
|
+
if (!deps || !runAccount || !call)
|
|
700
|
+
return null;
|
|
701
|
+
try {
|
|
702
|
+
const safe = runSafe();
|
|
703
|
+
if (safe === undefined)
|
|
704
|
+
return null;
|
|
705
|
+
return {
|
|
706
|
+
handle: (0, index_js_1.openStepHold)(deps.hold, runAccount, safe, call),
|
|
707
|
+
client: deps.clientFor(call.chainId),
|
|
708
|
+
call,
|
|
709
|
+
account: runAccount,
|
|
710
|
+
reservation: target.held?.reservation,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
};
|
|
318
717
|
try {
|
|
319
|
-
if (!deps
|
|
718
|
+
if (!deps || !runAccount)
|
|
320
719
|
throw new lineError_js_1.ReviewLineError("Connect a wallet first.");
|
|
321
720
|
if (action.account &&
|
|
322
|
-
action.account.toLowerCase() !==
|
|
721
|
+
action.account.toLowerCase() !== runAccount.toLowerCase())
|
|
323
722
|
throw new lineError_js_1.ReviewLineError(index_js_1.REVIEWED_ACCOUNT_CHANGED_LINE);
|
|
324
723
|
if (action.calls.length > 1 && action.scope === undefined)
|
|
325
724
|
throw new lineError_js_1.ReviewLineError(exports.REVIEWED_RUN_SCOPE_LINE);
|
|
@@ -338,7 +737,16 @@ function useReviewedRun(deps) {
|
|
|
338
737
|
}),
|
|
339
738
|
]).finally(() => signal?.removeEventListener("abort", withdraw));
|
|
340
739
|
const { resumeReviewed, sendReviewed } = loaded;
|
|
341
|
-
|
|
740
|
+
// A success already shown is acknowledged before this run checks it.
|
|
741
|
+
await acknowledged.current;
|
|
742
|
+
const settledOwn = () => {
|
|
743
|
+
const settled = ownSuccess(action);
|
|
744
|
+
if (settled)
|
|
745
|
+
throw new OwnSucceeded(settled);
|
|
746
|
+
};
|
|
747
|
+
settledOwn();
|
|
748
|
+
const { guard } = deps;
|
|
749
|
+
const account = runAccount;
|
|
342
750
|
const tracked = {
|
|
343
751
|
...deps,
|
|
344
752
|
onProgress: (progress) => {
|
|
@@ -395,30 +803,46 @@ function useReviewedRun(deps) {
|
|
|
395
803
|
(0, receipt_js_1.tell)(action.follow?.onConfirming, step);
|
|
396
804
|
},
|
|
397
805
|
};
|
|
806
|
+
const assertWriteState = () => {
|
|
807
|
+
settledOwn();
|
|
808
|
+
action.assertWriteState?.();
|
|
809
|
+
};
|
|
810
|
+
const gated = {
|
|
811
|
+
...action,
|
|
812
|
+
guard,
|
|
813
|
+
follow: inPlace,
|
|
814
|
+
onSubmitted,
|
|
815
|
+
assertWriteState,
|
|
816
|
+
};
|
|
398
817
|
const result = saved
|
|
399
|
-
? await resumeReviewed(saved,
|
|
400
|
-
: await sendReviewed({ ...
|
|
818
|
+
? await resumeReviewed(saved, gated, tracked)
|
|
819
|
+
: await sendReviewed({ ...gated, account }, tracked);
|
|
401
820
|
// A wallet that reviews on its own page reports no progress: the
|
|
402
821
|
// result is the run's last word.
|
|
403
|
-
|
|
822
|
+
const calls = saved?.calls ?? action.calls;
|
|
823
|
+
const last = result.status === "done"
|
|
824
|
+
? (0, index_js_1.openStepHold)(deps.hold, account, false, calls[calls.length - 1])
|
|
825
|
+
: null;
|
|
826
|
+
const success = last?.unacknowledgedSuccess();
|
|
827
|
+
update({
|
|
828
|
+
phase: result.status,
|
|
829
|
+
// Shown, its success blocks no later run.
|
|
830
|
+
acknowledge: last &&
|
|
831
|
+
success &&
|
|
832
|
+
result.status === "done" &&
|
|
833
|
+
result.hashes.some((h) => h.toLowerCase() === success.hash)
|
|
834
|
+
? acknowledging(last, success)
|
|
835
|
+
: null,
|
|
836
|
+
});
|
|
404
837
|
// Every step of a done run has its hash, a resumed one's too.
|
|
405
|
-
submit(result.status === "done"
|
|
406
|
-
? (saved?.calls ?? action.calls).length - 1
|
|
407
|
-
: result.step);
|
|
838
|
+
submit(result.status === "done" ? calls.length - 1 : result.step);
|
|
408
839
|
if (result.status === "proposed")
|
|
409
840
|
void follow();
|
|
410
|
-
else if (mounted.current && latest.current === id) {
|
|
411
|
-
// Shown: its success blocks no later run.
|
|
412
|
-
const calls = saved?.calls ?? action.calls;
|
|
413
|
-
const handle = (0, index_js_1.openStepHold)(deps.hold, account, false, calls[calls.length - 1]);
|
|
414
|
-
const success = handle.unacknowledgedSuccess();
|
|
415
|
-
if (success &&
|
|
416
|
-
result.hashes.some((h) => h.toLowerCase() === success.hash))
|
|
417
|
-
await handle.acknowledge(success).catch(() => undefined);
|
|
418
|
-
}
|
|
419
841
|
return result;
|
|
420
842
|
}
|
|
421
843
|
catch (error) {
|
|
844
|
+
if (error instanceof OwnSucceeded)
|
|
845
|
+
return close(error.found);
|
|
422
846
|
if (error instanceof index_js_1.ReviewedReceiptUnverifiedError) {
|
|
423
847
|
// Still confirming: followed until its hold settles it.
|
|
424
848
|
update({
|
|
@@ -427,12 +851,15 @@ function useReviewedRun(deps) {
|
|
|
427
851
|
hash: error.hash,
|
|
428
852
|
error: null,
|
|
429
853
|
failure: null,
|
|
854
|
+
following: true,
|
|
430
855
|
});
|
|
431
856
|
let end;
|
|
432
857
|
const cancelled = action.follow?.signal;
|
|
433
858
|
const abandon = () => end(null);
|
|
434
859
|
const outcome = new Promise((resolve) => (end = (ran) => {
|
|
435
860
|
cancelled?.removeEventListener("abort", abandon);
|
|
861
|
+
if (awaiting.current?.end === abandon)
|
|
862
|
+
awaiting.current = null;
|
|
436
863
|
resolve(ran);
|
|
437
864
|
}));
|
|
438
865
|
// Replaced, or the caller's follow ended (a cancel, unlike its
|
|
@@ -443,11 +870,20 @@ function useReviewedRun(deps) {
|
|
|
443
870
|
else {
|
|
444
871
|
leave.current = abandon;
|
|
445
872
|
cancelled?.addEventListener("abort", abandon, { once: true });
|
|
873
|
+
awaiting.current = {
|
|
874
|
+
run: id,
|
|
875
|
+
last: error.step === action.calls.length - 1,
|
|
876
|
+
owner: {
|
|
877
|
+
account: runAccount,
|
|
878
|
+
reservation: error.held?.reservation,
|
|
879
|
+
},
|
|
880
|
+
detached: heldStep(error),
|
|
881
|
+
end: abandon,
|
|
882
|
+
};
|
|
446
883
|
}
|
|
447
884
|
const finish = completing(error, action.calls.length, end);
|
|
448
885
|
// Followed for a later settlement; one already there is read once it attaches.
|
|
449
886
|
void follow(finish, () => end(null)).then(() => finish());
|
|
450
|
-
const before = error.held?.hashes ?? [];
|
|
451
887
|
return {
|
|
452
888
|
status: "receipt",
|
|
453
889
|
step: error.step,
|
|
@@ -458,6 +894,11 @@ function useReviewedRun(deps) {
|
|
|
458
894
|
ended: () => outcome.then(async (ran) => {
|
|
459
895
|
if (!ran)
|
|
460
896
|
return null;
|
|
897
|
+
if (ran === "queued") {
|
|
898
|
+
const failure = new locks_js_1.ConfirmationQueuedError(error.step);
|
|
899
|
+
const line = (0, index_js_1.errorLine)(failure);
|
|
900
|
+
return { status: "failed", error: line, failure };
|
|
901
|
+
}
|
|
461
902
|
const { settled, receipt } = ran;
|
|
462
903
|
if (settled.result !== "success") {
|
|
463
904
|
const line = (0, index_js_1.settledLine)(settled);
|
|
@@ -467,11 +908,13 @@ function useReviewedRun(deps) {
|
|
|
467
908
|
failure: new lineError_js_1.ReviewLineError(line),
|
|
468
909
|
};
|
|
469
910
|
}
|
|
470
|
-
|
|
911
|
+
// The run's own, as its hold saved them with the step.
|
|
912
|
+
const { signatures } = ran;
|
|
471
913
|
if (error.step < action.calls.length - 1) {
|
|
472
914
|
const started = await runLatest.current({
|
|
473
915
|
...action,
|
|
474
916
|
...(onCancelled ? { onCancelled } : {}),
|
|
917
|
+
...(onStopped ? { onStopped } : {}),
|
|
475
918
|
});
|
|
476
919
|
const next = started?.status === "receipt"
|
|
477
920
|
? await started.ended()
|
|
@@ -484,7 +927,7 @@ function useReviewedRun(deps) {
|
|
|
484
927
|
}
|
|
485
928
|
return {
|
|
486
929
|
status: "done",
|
|
487
|
-
hashes:
|
|
930
|
+
hashes: ran.hashes,
|
|
488
931
|
receipts: receipt ? [receipt] : [],
|
|
489
932
|
...(signatures ? { signatures } : {}),
|
|
490
933
|
};
|
|
@@ -493,9 +936,9 @@ function useReviewedRun(deps) {
|
|
|
493
936
|
}
|
|
494
937
|
// A resume can complete a step the wallet sent before the reload.
|
|
495
938
|
try {
|
|
496
|
-
const after = action.scope === undefined || !
|
|
939
|
+
const after = action.scope === undefined || !runAccount
|
|
497
940
|
? null
|
|
498
|
-
: (0, index_js_1.findReviewedSequence)(action.scope,
|
|
941
|
+
: (0, index_js_1.findReviewedSequence)(action.scope, runAccount, {
|
|
499
942
|
storage: deps.storage,
|
|
500
943
|
});
|
|
501
944
|
if (after?.completed.length &&
|
|
@@ -507,6 +950,19 @@ function useReviewedRun(deps) {
|
|
|
507
950
|
catch {
|
|
508
951
|
/* An unreadable sequence adds nothing to what this run saw. */
|
|
509
952
|
}
|
|
953
|
+
// Refused by a write already held (another form's, sharing the
|
|
954
|
+
// hold): this run follows it only as its own exact call.
|
|
955
|
+
const refused = error instanceof index_js_1.WriteHeldError
|
|
956
|
+
? error.state.reservation
|
|
957
|
+
: error instanceof index_js_1.WriteHoldUnacknowledgedSuccessError
|
|
958
|
+
? error.success.reservation
|
|
959
|
+
: undefined;
|
|
960
|
+
if (refused)
|
|
961
|
+
before.add(refused);
|
|
962
|
+
// ponytail: one call only; a sequence's journal keeps its steps.
|
|
963
|
+
const left = holds().filter(({ reservation }) => !before.has(reservation));
|
|
964
|
+
if (action.calls.length === 1 && left.length && runAccount)
|
|
965
|
+
changeOwn(runAccount, left.map(({ reservation }) => reservation), { kind: "keep" });
|
|
510
966
|
const cancelled = error instanceof index_js_1.TransactionReviewCancelledError ||
|
|
511
967
|
(0, index_js_1.isDefiniteWalletRejection)(error);
|
|
512
968
|
const line = cancelled ? null : (0, index_js_1.errorLine)(error);
|
|
@@ -523,9 +979,13 @@ function useReviewedRun(deps) {
|
|
|
523
979
|
return;
|
|
524
980
|
if (found === null)
|
|
525
981
|
setTimeout(settle, index_js_1.RECEIPT_RETRY_MS);
|
|
526
|
-
else if (cancelled && !
|
|
527
|
-
|
|
528
|
-
|
|
982
|
+
else if (cancelled && !found) {
|
|
983
|
+
const step = submitted ?? 0;
|
|
984
|
+
update(sentAny ? { stopped: true } : { cancelled: true });
|
|
985
|
+
const told = sentAny
|
|
986
|
+
? onStopped && (() => onStopped(step))
|
|
987
|
+
: onCancelled;
|
|
988
|
+
setCancelling(told ? { told } : null);
|
|
529
989
|
}
|
|
530
990
|
});
|
|
531
991
|
settle();
|