@bananapus/nana-sdk-core 6.2.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.
Files changed (109) hide show
  1. package/dist/cjs/review/confirming.d.ts +8 -0
  2. package/dist/cjs/review/confirming.d.ts.map +1 -0
  3. package/dist/cjs/review/confirming.js +11 -0
  4. package/dist/cjs/review/confirming.js.map +1 -0
  5. package/dist/cjs/review/locks.d.ts +87 -7
  6. package/dist/cjs/review/locks.d.ts.map +1 -1
  7. package/dist/cjs/review/locks.js +328 -79
  8. package/dist/cjs/review/locks.js.map +1 -1
  9. package/dist/cjs/review/messages.d.ts +16 -0
  10. package/dist/cjs/review/messages.d.ts.map +1 -1
  11. package/dist/cjs/review/messages.js +17 -1
  12. package/dist/cjs/review/messages.js.map +1 -1
  13. package/dist/cjs/review/react/ownership.d.ts +30 -0
  14. package/dist/cjs/review/react/ownership.d.ts.map +1 -0
  15. package/dist/cjs/review/react/ownership.js +27 -0
  16. package/dist/cjs/review/react/ownership.js.map +1 -0
  17. package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
  18. package/dist/cjs/review/react/useReviewedRun.js +394 -45
  19. package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
  20. package/dist/cjs/review/relayrSend.d.ts +16 -1
  21. package/dist/cjs/review/relayrSend.d.ts.map +1 -1
  22. package/dist/cjs/review/relayrSend.js +25 -2
  23. package/dist/cjs/review/relayrSend.js.map +1 -1
  24. package/dist/cjs/review/safeRelayr.d.ts +2 -1
  25. package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
  26. package/dist/cjs/review/safeRelayr.js +23 -29
  27. package/dist/cjs/review/safeRelayr.js.map +1 -1
  28. package/dist/cjs/review/send.d.ts +31 -12
  29. package/dist/cjs/review/send.d.ts.map +1 -1
  30. package/dist/cjs/review/send.js +191 -41
  31. package/dist/cjs/review/send.js.map +1 -1
  32. package/dist/cjs/review/sequence.d.ts +25 -0
  33. package/dist/cjs/review/sequence.d.ts.map +1 -1
  34. package/dist/cjs/review/sequence.js +45 -7
  35. package/dist/cjs/review/sequence.js.map +1 -1
  36. package/dist/cjs/review/tabState.d.ts +4 -0
  37. package/dist/cjs/review/tabState.d.ts.map +1 -1
  38. package/dist/cjs/review/tabState.js +4 -0
  39. package/dist/cjs/review/tabState.js.map +1 -1
  40. package/dist/cjs/review/wallet.d.ts.map +1 -1
  41. package/dist/cjs/review/wallet.js +3 -1
  42. package/dist/cjs/review/wallet.js.map +1 -1
  43. package/dist/cjs/review/writeHold.d.ts +74 -11
  44. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  45. package/dist/cjs/review/writeHold.js +65 -16
  46. package/dist/cjs/review/writeHold.js.map +1 -1
  47. package/dist/cjs/review/writeRecovery.d.ts +31 -6
  48. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  49. package/dist/cjs/review/writeRecovery.js +120 -7
  50. package/dist/cjs/review/writeRecovery.js.map +1 -1
  51. package/dist/cjs/testing.d.ts +13 -0
  52. package/dist/cjs/testing.d.ts.map +1 -1
  53. package/dist/cjs/testing.js +21 -0
  54. package/dist/cjs/testing.js.map +1 -1
  55. package/dist/esm/review/confirming.d.ts +8 -0
  56. package/dist/esm/review/confirming.d.ts.map +1 -0
  57. package/dist/esm/review/confirming.js +8 -0
  58. package/dist/esm/review/confirming.js.map +1 -0
  59. package/dist/esm/review/locks.d.ts +87 -7
  60. package/dist/esm/review/locks.d.ts.map +1 -1
  61. package/dist/esm/review/locks.js +321 -78
  62. package/dist/esm/review/locks.js.map +1 -1
  63. package/dist/esm/review/messages.d.ts +16 -0
  64. package/dist/esm/review/messages.d.ts.map +1 -1
  65. package/dist/esm/review/messages.js +16 -0
  66. package/dist/esm/review/messages.js.map +1 -1
  67. package/dist/esm/review/react/ownership.d.ts +30 -0
  68. package/dist/esm/review/react/ownership.d.ts.map +1 -0
  69. package/dist/esm/review/react/ownership.js +24 -0
  70. package/dist/esm/review/react/ownership.js.map +1 -0
  71. package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
  72. package/dist/esm/review/react/useReviewedRun.js +395 -46
  73. package/dist/esm/review/react/useReviewedRun.js.map +1 -1
  74. package/dist/esm/review/relayrSend.d.ts +16 -1
  75. package/dist/esm/review/relayrSend.d.ts.map +1 -1
  76. package/dist/esm/review/relayrSend.js +28 -5
  77. package/dist/esm/review/relayrSend.js.map +1 -1
  78. package/dist/esm/review/safeRelayr.d.ts +2 -1
  79. package/dist/esm/review/safeRelayr.d.ts.map +1 -1
  80. package/dist/esm/review/safeRelayr.js +24 -30
  81. package/dist/esm/review/safeRelayr.js.map +1 -1
  82. package/dist/esm/review/send.d.ts +31 -12
  83. package/dist/esm/review/send.d.ts.map +1 -1
  84. package/dist/esm/review/send.js +193 -43
  85. package/dist/esm/review/send.js.map +1 -1
  86. package/dist/esm/review/sequence.d.ts +25 -0
  87. package/dist/esm/review/sequence.d.ts.map +1 -1
  88. package/dist/esm/review/sequence.js +43 -7
  89. package/dist/esm/review/sequence.js.map +1 -1
  90. package/dist/esm/review/tabState.d.ts +4 -0
  91. package/dist/esm/review/tabState.d.ts.map +1 -1
  92. package/dist/esm/review/tabState.js +4 -0
  93. package/dist/esm/review/tabState.js.map +1 -1
  94. package/dist/esm/review/wallet.d.ts.map +1 -1
  95. package/dist/esm/review/wallet.js +3 -1
  96. package/dist/esm/review/wallet.js.map +1 -1
  97. package/dist/esm/review/writeHold.d.ts +74 -11
  98. package/dist/esm/review/writeHold.d.ts.map +1 -1
  99. package/dist/esm/review/writeHold.js +65 -16
  100. package/dist/esm/review/writeHold.js.map +1 -1
  101. package/dist/esm/review/writeRecovery.d.ts +31 -6
  102. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  103. package/dist/esm/review/writeRecovery.js +118 -7
  104. package/dist/esm/review/writeRecovery.js.map +1 -1
  105. package/dist/esm/testing.d.ts +13 -0
  106. package/dist/esm/testing.d.ts.map +1 -1
  107. package/dist/esm/testing.js +20 -0
  108. package/dist/esm/testing.js.map +1 -1
  109. 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,
@@ -31,6 +33,28 @@ const sent = ({ chainId, to, data, value, gas }) => [
31
33
  value ?? 0n,
32
34
  gas ?? null,
33
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
+ }
34
58
  exports.REVIEWED_RUN_SCOPE_LINE = "This action has more than one step and no scope to resume it by. Nothing was sent.";
35
59
  exports.REVIEWED_RUN_BUSY_LINE = "This action is still running. Wait for it to finish, then try again.";
36
60
  /**
@@ -46,16 +70,29 @@ function useReviewedRun(deps) {
46
70
  if (cancelling)
47
71
  void (0, receipt_js_1.tell)(cancelling.told);
48
72
  }, [cancelling]);
49
- /** The last shown success's acknowledgement: the next run waits for it. */
73
+ /** Every shown success's acknowledgement: the next run waits for all. */
50
74
  const acknowledged = (0, react_1.useRef)(Promise.resolve());
51
75
  // A success is acknowledged only once its done rendered: a reset after
52
- // that hides nothing, and one before it leaves the success warning.
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.
53
78
  (0, react_1.useEffect)(() => {
54
79
  if (state.acknowledge)
55
- acknowledged.current = state.acknowledge();
80
+ acknowledged.current = Promise.all([
81
+ acknowledged.current,
82
+ state.acknowledge(),
83
+ ]).then(() => undefined);
56
84
  }, [state.acknowledge]);
57
85
  /** The hold followed after a run, and its chain's client: its view is `held`. */
58
- const [watched, setWatched] = (0, react_1.useState)(null);
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
+ };
59
96
  /** The latest run: an earlier one's late answers change nothing. */
60
97
  const latest = (0, react_1.useRef)(0);
61
98
  /** The sent step whose hold the latest run's `ended()` awaits. */
@@ -76,6 +113,101 @@ function useReviewedRun(deps) {
76
113
  * hold keeps the sent step.
77
114
  */
78
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
+ };
79
211
  const replaced = () => {
80
212
  latest.current += 1;
81
213
  life.current.abort();
@@ -94,9 +226,12 @@ function useReviewedRun(deps) {
94
226
  };
95
227
  }, []);
96
228
  /** How a followed step ended, as the run that followed it shows it. */
97
- const showSettled = (run, step, total, settled, receipt, acknowledge) => {
229
+ const showSettled = (run, step, total, settled, receipt, acknowledge, account, reservation) => {
98
230
  if (latest.current !== run)
99
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 });
100
235
  const finished = step === total - 1;
101
236
  setState((now) => settled.result !== "success"
102
237
  ? {
@@ -202,9 +337,24 @@ function useReviewedRun(deps) {
202
337
  const waiting = awaiting.current;
203
338
  if (open.current && !waiting)
204
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
+ };
205
354
  if (waiting?.last) {
206
355
  awaiting.current = null;
207
356
  waiting.end();
357
+ closing();
208
358
  return true;
209
359
  }
210
360
  detach(waiting
@@ -213,9 +363,13 @@ function useReviewedRun(deps) {
213
363
  handle: watched.handle,
214
364
  client: watched.client,
215
365
  call: watched.call,
366
+ account: watched.account,
216
367
  reservation: watched.reservation,
217
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.
218
371
  reset();
372
+ closing();
219
373
  return true;
220
374
  },
221
375
  preload,
@@ -235,6 +389,102 @@ function useReviewedRun(deps) {
235
389
  setWatched(null);
236
390
  },
237
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
+ }
238
488
  replaced();
239
489
  const id = latest.current;
240
490
  const { signal: alive } = life.current;
@@ -248,6 +498,21 @@ function useReviewedRun(deps) {
248
498
  setState((now) => ({ ...now, ...next }));
249
499
  };
250
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));
251
516
  let loaded;
252
517
  /** A step of the action was sent before this run, or by it: never a cancel. */
253
518
  let sentAny = false;
@@ -276,9 +541,11 @@ function useReviewedRun(deps) {
276
541
  let completed = Promise.resolve(null);
277
542
  let shown = false;
278
543
  const action$ = {
279
- account: deps.account,
544
+ account: runAccount,
280
545
  scope: action.scope,
281
546
  onConfirmed: action.onConfirmed,
547
+ locks: action.locks,
548
+ attemptScoped: action.attemptScoped,
282
549
  };
283
550
  const finish = (seen, receipt = null) => {
284
551
  if (shown || alive.aborted)
@@ -292,11 +559,30 @@ function useReviewedRun(deps) {
292
559
  return null;
293
560
  }));
294
561
  void completed.then((ended) => {
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
+ });
295
569
  if (shown || alive.aborted)
296
570
  return;
297
- // Nothing advanced (its receipt unread, or its hold not settled
298
- // for this send yet): it is tried again.
299
- if (unread || (!ended && seen)) {
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
+ }
300
586
  if (mounted.current)
301
587
  setTimeout(() => finish(seen, receipt), index_js_1.RECEIPT_RETRY_MS);
302
588
  return;
@@ -306,8 +592,8 @@ function useReviewedRun(deps) {
306
592
  shown = true;
307
593
  // Shown with the receipt completion checked, a failure's too.
308
594
  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, deps.account, false, target.held.call), ended.settled)
310
- : null);
595
+ ? acknowledging((0, index_js_1.openStepHold)(deps.hold, action$.account, false, target.held.call), ended.settled)
596
+ : null, action$.account, target.held?.reservation);
311
597
  setWatched(null);
312
598
  end?.(ended);
313
599
  });
@@ -319,9 +605,10 @@ function useReviewedRun(deps) {
319
605
  * found one, or null when the wallet could not say (its Safe mode unknown).
320
606
  */
321
607
  const follow = async (finish, released) => {
322
- if (latest.current !== id || !deps?.account)
608
+ if (latest.current !== id || !deps || !runAccount)
323
609
  return false;
324
- const { account, adapter, hold, storage } = deps;
610
+ const { adapter, hold, storage } = deps;
611
+ const account = runAccount;
325
612
  // Its Safe mode is read only once the wallet says it is known.
326
613
  try {
327
614
  await (0, index_js_1.walletReady)(adapter);
@@ -332,36 +619,48 @@ function useReviewedRun(deps) {
332
619
  try {
333
620
  if (latest.current !== id)
334
621
  return false;
335
- const safe = adapter.isSafe();
622
+ const safe = runSafe();
623
+ if (safe === undefined)
624
+ return null;
336
625
  const saved = !action.scope
337
626
  ? null
338
627
  : (0, index_js_1.findReviewedSequence)(action.scope, account, { storage });
339
628
  const total = saved?.calls.length ?? action.calls.length;
340
- // A Safe app's batch is held as its one MultiSend proposal, its last step.
341
- const steps = saved?.sending
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.
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.
357
632
  const found = steps
358
633
  .map(({ call, step }) => ({
359
634
  call,
360
635
  step,
361
636
  handle: (0, index_js_1.openStepHold)(hold, account, safe, call),
362
637
  }))
363
- .find(({ handle, step }) => handle.peek().status !== "clear" ||
364
- (step === total - 1 && handle.unacknowledgedSuccess()));
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
+ });
365
664
  // Its settlement completes it as a send's would: a proposal too.
366
665
  const reservation = found?.handle.peek().reservation ??
367
666
  found?.handle.unacknowledgedSuccess()?.reservation;
@@ -370,6 +669,7 @@ function useReviewedRun(deps) {
370
669
  handle: found.handle,
371
670
  client: deps.clientFor(found.call.chainId),
372
671
  run: id,
672
+ account,
373
673
  call: found.call,
374
674
  reservation,
375
675
  step: found.step,
@@ -383,6 +683,8 @@ function useReviewedRun(deps) {
383
683
  }
384
684
  : null);
385
685
  // Only its hold is followed now: the confirm can close.
686
+ if (reservation)
687
+ mine.current.add(reservation);
386
688
  if (found)
387
689
  update({ following: true });
388
690
  return !!found;
@@ -394,13 +696,17 @@ function useReviewedRun(deps) {
394
696
  };
395
697
  const heldStep = (target) => {
396
698
  const call = target.held?.call ?? action.calls[target.step];
397
- if (!deps?.account || !call)
699
+ if (!deps || !runAccount || !call)
398
700
  return null;
399
701
  try {
702
+ const safe = runSafe();
703
+ if (safe === undefined)
704
+ return null;
400
705
  return {
401
- handle: (0, index_js_1.openStepHold)(deps.hold, deps.account, deps.adapter.isSafe(), call),
706
+ handle: (0, index_js_1.openStepHold)(deps.hold, runAccount, safe, call),
402
707
  client: deps.clientFor(call.chainId),
403
708
  call,
709
+ account: runAccount,
404
710
  reservation: target.held?.reservation,
405
711
  };
406
712
  }
@@ -409,10 +715,10 @@ function useReviewedRun(deps) {
409
715
  }
410
716
  };
411
717
  try {
412
- if (!deps?.account)
718
+ if (!deps || !runAccount)
413
719
  throw new lineError_js_1.ReviewLineError("Connect a wallet first.");
414
720
  if (action.account &&
415
- action.account.toLowerCase() !== deps.account.toLowerCase())
721
+ action.account.toLowerCase() !== runAccount.toLowerCase())
416
722
  throw new lineError_js_1.ReviewLineError(index_js_1.REVIEWED_ACCOUNT_CHANGED_LINE);
417
723
  if (action.calls.length > 1 && action.scope === undefined)
418
724
  throw new lineError_js_1.ReviewLineError(exports.REVIEWED_RUN_SCOPE_LINE);
@@ -433,7 +739,14 @@ function useReviewedRun(deps) {
433
739
  const { resumeReviewed, sendReviewed } = loaded;
434
740
  // A success already shown is acknowledged before this run checks it.
435
741
  await acknowledged.current;
436
- const { account, guard } = deps;
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;
437
750
  const tracked = {
438
751
  ...deps,
439
752
  onProgress: (progress) => {
@@ -490,9 +803,20 @@ function useReviewedRun(deps) {
490
803
  (0, receipt_js_1.tell)(action.follow?.onConfirming, step);
491
804
  },
492
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
+ };
493
817
  const result = saved
494
- ? await resumeReviewed(saved, { ...action, guard, follow: inPlace, onSubmitted }, tracked)
495
- : await sendReviewed({ ...action, account, guard, follow: inPlace, onSubmitted }, tracked);
818
+ ? await resumeReviewed(saved, gated, tracked)
819
+ : await sendReviewed({ ...gated, account }, tracked);
496
820
  // A wallet that reviews on its own page reports no progress: the
497
821
  // result is the run's last word.
498
822
  const calls = saved?.calls ?? action.calls;
@@ -517,6 +841,8 @@ function useReviewedRun(deps) {
517
841
  return result;
518
842
  }
519
843
  catch (error) {
844
+ if (error instanceof OwnSucceeded)
845
+ return close(error.found);
520
846
  if (error instanceof index_js_1.ReviewedReceiptUnverifiedError) {
521
847
  // Still confirming: followed until its hold settles it.
522
848
  update({
@@ -545,7 +871,12 @@ function useReviewedRun(deps) {
545
871
  leave.current = abandon;
546
872
  cancelled?.addEventListener("abort", abandon, { once: true });
547
873
  awaiting.current = {
874
+ run: id,
548
875
  last: error.step === action.calls.length - 1,
876
+ owner: {
877
+ account: runAccount,
878
+ reservation: error.held?.reservation,
879
+ },
549
880
  detached: heldStep(error),
550
881
  end: abandon,
551
882
  };
@@ -553,7 +884,6 @@ function useReviewedRun(deps) {
553
884
  const finish = completing(error, action.calls.length, end);
554
885
  // Followed for a later settlement; one already there is read once it attaches.
555
886
  void follow(finish, () => end(null)).then(() => finish());
556
- const before = error.held?.hashes ?? [];
557
887
  return {
558
888
  status: "receipt",
559
889
  step: error.step,
@@ -564,6 +894,11 @@ function useReviewedRun(deps) {
564
894
  ended: () => outcome.then(async (ran) => {
565
895
  if (!ran)
566
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
+ }
567
902
  const { settled, receipt } = ran;
568
903
  if (settled.result !== "success") {
569
904
  const line = (0, index_js_1.settledLine)(settled);
@@ -573,7 +908,8 @@ function useReviewedRun(deps) {
573
908
  failure: new lineError_js_1.ReviewLineError(line),
574
909
  };
575
910
  }
576
- const signatures = error.held?.signatures;
911
+ // The run's own, as its hold saved them with the step.
912
+ const { signatures } = ran;
577
913
  if (error.step < action.calls.length - 1) {
578
914
  const started = await runLatest.current({
579
915
  ...action,
@@ -591,7 +927,7 @@ function useReviewedRun(deps) {
591
927
  }
592
928
  return {
593
929
  status: "done",
594
- hashes: [...before, settled.hash],
930
+ hashes: ran.hashes,
595
931
  receipts: receipt ? [receipt] : [],
596
932
  ...(signatures ? { signatures } : {}),
597
933
  };
@@ -600,9 +936,9 @@ function useReviewedRun(deps) {
600
936
  }
601
937
  // A resume can complete a step the wallet sent before the reload.
602
938
  try {
603
- const after = action.scope === undefined || !deps?.account
939
+ const after = action.scope === undefined || !runAccount
604
940
  ? null
605
- : (0, index_js_1.findReviewedSequence)(action.scope, deps.account, {
941
+ : (0, index_js_1.findReviewedSequence)(action.scope, runAccount, {
606
942
  storage: deps.storage,
607
943
  });
608
944
  if (after?.completed.length &&
@@ -614,6 +950,19 @@ function useReviewedRun(deps) {
614
950
  catch {
615
951
  /* An unreadable sequence adds nothing to what this run saw. */
616
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" });
617
966
  const cancelled = error instanceof index_js_1.TransactionReviewCancelledError ||
618
967
  (0, index_js_1.isDefiniteWalletRejection)(error);
619
968
  const line = cancelled ? null : (0, index_js_1.errorLine)(error);