@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.
Files changed (149) 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/heldWrite.d.ts +6 -1
  6. package/dist/cjs/review/heldWrite.d.ts.map +1 -1
  7. package/dist/cjs/review/heldWrite.js +13 -6
  8. package/dist/cjs/review/heldWrite.js.map +1 -1
  9. package/dist/cjs/review/locks.d.ts +141 -2
  10. package/dist/cjs/review/locks.d.ts.map +1 -1
  11. package/dist/cjs/review/locks.js +406 -58
  12. package/dist/cjs/review/locks.js.map +1 -1
  13. package/dist/cjs/review/messages.d.ts +16 -0
  14. package/dist/cjs/review/messages.d.ts.map +1 -1
  15. package/dist/cjs/review/messages.js +17 -1
  16. package/dist/cjs/review/messages.js.map +1 -1
  17. package/dist/cjs/review/react/ownership.d.ts +30 -0
  18. package/dist/cjs/review/react/ownership.d.ts.map +1 -0
  19. package/dist/cjs/review/react/ownership.js +27 -0
  20. package/dist/cjs/review/react/ownership.js.map +1 -0
  21. package/dist/cjs/review/react/useReviewedRun.d.ts +17 -3
  22. package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
  23. package/dist/cjs/review/react/useReviewedRun.js +528 -68
  24. package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
  25. package/dist/cjs/review/react/useSavedAttempt.d.ts.map +1 -1
  26. package/dist/cjs/review/react/useSavedAttempt.js +12 -0
  27. package/dist/cjs/review/react/useSavedAttempt.js.map +1 -1
  28. package/dist/cjs/review/relayrSend.d.ts +20 -0
  29. package/dist/cjs/review/relayrSend.d.ts.map +1 -1
  30. package/dist/cjs/review/relayrSend.js +150 -105
  31. package/dist/cjs/review/relayrSend.js.map +1 -1
  32. package/dist/cjs/review/runStatus.d.ts +6 -2
  33. package/dist/cjs/review/runStatus.d.ts.map +1 -1
  34. package/dist/cjs/review/runStatus.js +3 -1
  35. package/dist/cjs/review/runStatus.js.map +1 -1
  36. package/dist/cjs/review/safeRelayr.d.ts +12 -2
  37. package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
  38. package/dist/cjs/review/safeRelayr.js +226 -156
  39. package/dist/cjs/review/safeRelayr.js.map +1 -1
  40. package/dist/cjs/review/savedAttempt.d.ts.map +1 -1
  41. package/dist/cjs/review/savedAttempt.js +7 -2
  42. package/dist/cjs/review/savedAttempt.js.map +1 -1
  43. package/dist/cjs/review/send.d.ts +46 -5
  44. package/dist/cjs/review/send.d.ts.map +1 -1
  45. package/dist/cjs/review/send.js +251 -58
  46. package/dist/cjs/review/send.js.map +1 -1
  47. package/dist/cjs/review/sequence.d.ts +25 -0
  48. package/dist/cjs/review/sequence.d.ts.map +1 -1
  49. package/dist/cjs/review/sequence.js +45 -7
  50. package/dist/cjs/review/sequence.js.map +1 -1
  51. package/dist/cjs/review/sign.d.ts +6 -1
  52. package/dist/cjs/review/sign.d.ts.map +1 -1
  53. package/dist/cjs/review/sign.js +10 -4
  54. package/dist/cjs/review/sign.js.map +1 -1
  55. package/dist/cjs/review/tabState.d.ts +4 -0
  56. package/dist/cjs/review/tabState.d.ts.map +1 -1
  57. package/dist/cjs/review/tabState.js +4 -0
  58. package/dist/cjs/review/tabState.js.map +1 -1
  59. package/dist/cjs/review/wallet.d.ts +16 -4
  60. package/dist/cjs/review/wallet.d.ts.map +1 -1
  61. package/dist/cjs/review/wallet.js +62 -17
  62. package/dist/cjs/review/wallet.js.map +1 -1
  63. package/dist/cjs/review/writeHold.d.ts +92 -14
  64. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  65. package/dist/cjs/review/writeHold.js +102 -26
  66. package/dist/cjs/review/writeHold.js.map +1 -1
  67. package/dist/cjs/review/writeRecovery.d.ts +42 -7
  68. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  69. package/dist/cjs/review/writeRecovery.js +183 -21
  70. package/dist/cjs/review/writeRecovery.js.map +1 -1
  71. package/dist/cjs/testing.d.ts +13 -0
  72. package/dist/cjs/testing.d.ts.map +1 -1
  73. package/dist/cjs/testing.js +21 -0
  74. package/dist/cjs/testing.js.map +1 -1
  75. package/dist/esm/review/confirming.d.ts +8 -0
  76. package/dist/esm/review/confirming.d.ts.map +1 -0
  77. package/dist/esm/review/confirming.js +8 -0
  78. package/dist/esm/review/confirming.js.map +1 -0
  79. package/dist/esm/review/heldWrite.d.ts +6 -1
  80. package/dist/esm/review/heldWrite.d.ts.map +1 -1
  81. package/dist/esm/review/heldWrite.js +13 -6
  82. package/dist/esm/review/heldWrite.js.map +1 -1
  83. package/dist/esm/review/locks.d.ts +141 -2
  84. package/dist/esm/review/locks.d.ts.map +1 -1
  85. package/dist/esm/review/locks.js +396 -58
  86. package/dist/esm/review/locks.js.map +1 -1
  87. package/dist/esm/review/messages.d.ts +16 -0
  88. package/dist/esm/review/messages.d.ts.map +1 -1
  89. package/dist/esm/review/messages.js +16 -0
  90. package/dist/esm/review/messages.js.map +1 -1
  91. package/dist/esm/review/react/ownership.d.ts +30 -0
  92. package/dist/esm/review/react/ownership.d.ts.map +1 -0
  93. package/dist/esm/review/react/ownership.js +24 -0
  94. package/dist/esm/review/react/ownership.js.map +1 -0
  95. package/dist/esm/review/react/useReviewedRun.d.ts +17 -3
  96. package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
  97. package/dist/esm/review/react/useReviewedRun.js +529 -69
  98. package/dist/esm/review/react/useReviewedRun.js.map +1 -1
  99. package/dist/esm/review/react/useSavedAttempt.d.ts.map +1 -1
  100. package/dist/esm/review/react/useSavedAttempt.js +12 -0
  101. package/dist/esm/review/react/useSavedAttempt.js.map +1 -1
  102. package/dist/esm/review/relayrSend.d.ts +20 -0
  103. package/dist/esm/review/relayrSend.d.ts.map +1 -1
  104. package/dist/esm/review/relayrSend.js +154 -109
  105. package/dist/esm/review/relayrSend.js.map +1 -1
  106. package/dist/esm/review/runStatus.d.ts +6 -2
  107. package/dist/esm/review/runStatus.d.ts.map +1 -1
  108. package/dist/esm/review/runStatus.js +3 -1
  109. package/dist/esm/review/runStatus.js.map +1 -1
  110. package/dist/esm/review/safeRelayr.d.ts +12 -2
  111. package/dist/esm/review/safeRelayr.d.ts.map +1 -1
  112. package/dist/esm/review/safeRelayr.js +226 -156
  113. package/dist/esm/review/safeRelayr.js.map +1 -1
  114. package/dist/esm/review/savedAttempt.d.ts.map +1 -1
  115. package/dist/esm/review/savedAttempt.js +7 -2
  116. package/dist/esm/review/savedAttempt.js.map +1 -1
  117. package/dist/esm/review/send.d.ts +46 -5
  118. package/dist/esm/review/send.d.ts.map +1 -1
  119. package/dist/esm/review/send.js +252 -59
  120. package/dist/esm/review/send.js.map +1 -1
  121. package/dist/esm/review/sequence.d.ts +25 -0
  122. package/dist/esm/review/sequence.d.ts.map +1 -1
  123. package/dist/esm/review/sequence.js +43 -7
  124. package/dist/esm/review/sequence.js.map +1 -1
  125. package/dist/esm/review/sign.d.ts +6 -1
  126. package/dist/esm/review/sign.d.ts.map +1 -1
  127. package/dist/esm/review/sign.js +10 -4
  128. package/dist/esm/review/sign.js.map +1 -1
  129. package/dist/esm/review/tabState.d.ts +4 -0
  130. package/dist/esm/review/tabState.d.ts.map +1 -1
  131. package/dist/esm/review/tabState.js +4 -0
  132. package/dist/esm/review/tabState.js.map +1 -1
  133. package/dist/esm/review/wallet.d.ts +16 -4
  134. package/dist/esm/review/wallet.d.ts.map +1 -1
  135. package/dist/esm/review/wallet.js +62 -17
  136. package/dist/esm/review/wallet.js.map +1 -1
  137. package/dist/esm/review/writeHold.d.ts +92 -14
  138. package/dist/esm/review/writeHold.d.ts.map +1 -1
  139. package/dist/esm/review/writeHold.js +102 -26
  140. package/dist/esm/review/writeHold.js.map +1 -1
  141. package/dist/esm/review/writeRecovery.d.ts +42 -7
  142. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  143. package/dist/esm/review/writeRecovery.js +180 -22
  144. package/dist/esm/review/writeRecovery.js.map +1 -1
  145. package/dist/esm/testing.d.ts +13 -0
  146. package/dist/esm/testing.d.ts.map +1 -1
  147. package/dist/esm/testing.js +20 -0
  148. package/dist/esm/testing.js.map +1 -1
  149. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { assertLocksHeld, keepOutcome } from "./locks.js";
2
+ import { CONFIRMING_RENEW_MS } from "./confirming.js";
1
3
  import { ReviewedCallRevertedError, ReviewedReceiptUnverifiedError, ReviewLineError, WalletNotInvokedError, } from "./lineError.js";
2
4
  import { encodeFunctionData, formatEther, } from "viem";
3
5
  import { failedSimulation, promptReview, } from "./confirm.js";
@@ -14,7 +16,7 @@ import { mintPlan, relayrStandInSender, REVIEWED_PLAN_REFUSED_LINE, spendPlan, }
14
16
  import { heldBlock, heldReceipt, tell, waitForTrackedReceipt, } from "./receipt.js";
15
17
  import { relayrPaymentChains } from "./relayr.js";
16
18
  import { relayrFeeCap } from "./relayrSend.js";
17
- import { assertSavableCalls, readReviewedSequence, removeReviewedSequence, REVIEWED_SEQUENCE_MOVED_LINE, REVIEWED_SEQUENCE_UNCERTAIN_LINE, REVIEWED_SEQUENCE_UNFINISHED_LINE, saveReviewedSequence, } from "./sequence.js";
19
+ import { assertSavableCalls, decodeOutcome, encodeOutcome, readReviewedSequence, removeReviewedSequence, REVIEWED_SEQUENCE_MOVED_LINE, REVIEWED_SEQUENCE_UNCERTAIN_LINE, REVIEWED_SEQUENCE_UNFINISHED_LINE, saveReviewedSequence, } from "./sequence.js";
18
20
  import { plannedCallsOf, prepareSignature, signPrepared, } from "./sign.js";
19
21
  import { isRevert, REVERT_UNKNOWN_LINE, revertDetail, revertReason, simulateCallSequence, simulateStateChangingTransaction, simulationFailure, } from "./simulation.js";
20
22
  import { unsupportedWalletRequest, walletReady, } from "./wallet.js";
@@ -382,16 +384,38 @@ export async function resumeReviewed(record, action, deps) {
382
384
  const { settled } = state;
383
385
  // Read before anything advances: a failed read leaves the step to resume again.
384
386
  const receipt = await confirmedReceipt(settled, deps.clientFor(call.chainId));
385
- // This sequence's own step: it is recorded, told, and only then
386
- // acknowledged, so it is never offered again.
387
- const kept = await recordSending(saved, settled, storage, expected);
388
- await tellConfirmed(action, handle, call, step, settled, receipt);
387
+ // This sequence's own step: told, recorded, and only then acknowledged,
388
+ // so it is never offered again. Its notice not delivered yet, it stays
389
+ // held at its receipt, acknowledged by none, for a completion to tell.
390
+ let kept = expected;
391
+ const outcome = outcomeOf(handle, reservation, {
392
+ account: saved.account,
393
+ step,
394
+ call,
395
+ prior: [...saved.completed],
396
+ ...(saved.signatures ? { signatures: saved.signatures } : {}),
397
+ });
398
+ const ended = await endStep(action, handle, outcome, settled, receipt, async () => {
399
+ kept = await recordSending(saved, settled, storage, expected);
400
+ });
401
+ if (!ended)
402
+ throw new ReviewedReceiptUnverifiedError(step, settled.hash, undefined, {
403
+ call: outcome.call,
404
+ reservation,
405
+ hashes: [...outcome.prior],
406
+ ...signedBy(outcome),
407
+ });
389
408
  if (settled?.result === "success")
390
409
  await handle.acknowledge(settled);
391
410
  if (kept === null) {
392
411
  const total = saved.calls.length;
393
412
  tell(deps.onProgress, { step: total - 1, total, phase: "done" });
394
- return { status: "done", hashes: saved.completed, receipts: [] };
413
+ return {
414
+ status: "done",
415
+ hashes: hashesThrough(outcome, settled),
416
+ receipts: [],
417
+ ...signedBy(outcome),
418
+ };
395
419
  }
396
420
  expected = kept;
397
421
  }
@@ -430,14 +454,87 @@ async function confirmedReceipt(settled, client, given) {
430
454
  /**
431
455
  * Tells `onConfirmed` of a step's verified success, once per settlement:
432
456
  * only the completion (a run, `completeReviewedStep`, `resumeReviewed`, in
433
- * any tab) that claims its notice on the hold tells it.
457
+ * any tab) that claims its notice on the hold tells it. The claim is made
458
+ * for good once told, and given back if the site never gets it, so a later
459
+ * completion tells it.
434
460
  */
435
461
  async function tellConfirmed(action, handle, call, step, settled, receipt) {
436
- if (settled?.result === "success" &&
437
- receipt &&
438
- (await handle.claimConfirmed(settled)))
439
- await tell(action.onConfirmed, call, step, receipt);
462
+ if (settled?.result !== "success" || !receipt)
463
+ return "delivered";
464
+ const claimant = globalThis.crypto.randomUUID();
465
+ if (!(await handle.claimConfirmed(settled, claimant)))
466
+ // Another completion's: delivered, or not yet.
467
+ return (await handle.confirmation(settled)) === "delivered"
468
+ ? "delivered"
469
+ : "pending";
470
+ // Renewed while this delivery runs (kept for a lock too), so only a claim
471
+ // whose tab closed or crashed lapses; ended only by this claimant.
472
+ const renewal = setInterval(() => void handle.claimConfirmed(settled, claimant).catch(() => undefined), CONFIRMING_RENEW_MS);
473
+ let delivery = "pending";
474
+ const end = async (delivered) => {
475
+ clearInterval(renewal);
476
+ await handle.settleConfirmed(settled, delivered, claimant);
477
+ };
478
+ try {
479
+ const told = await tellKept(action, () => action.onConfirmed?.(call, step, receipt), async (applied) => {
480
+ delivery = applied ? "delivered" : "undelivered";
481
+ await end(applied);
482
+ });
483
+ if (!told)
484
+ return delivery;
485
+ await end(true);
486
+ return "delivered";
487
+ }
488
+ catch (error) {
489
+ clearInterval(renewal);
490
+ throw error;
491
+ }
440
492
  }
493
+ /**
494
+ * Ends a settled step, the one way every completion does (a run, a resume,
495
+ * `completeReviewedStep`): its `onConfirmed` told with its outcome's call
496
+ * and step, and only once delivered is it recorded done (`record`, its
497
+ * reservation leaving the sequence's journal). Until then the step stays
498
+ * held there, so a later completion or resume tells it before any step
499
+ * after it is sent. False while undelivered.
500
+ */
501
+ async function endStep(action, handle, outcome, settled, receipt, record) {
502
+ if ((await tellConfirmed(action, handle, outcome.call, outcome.step, settled, receipt)) !== "delivered")
503
+ return false;
504
+ await record();
505
+ return true;
506
+ }
507
+ /**
508
+ * A sent step's outcome as its run saved it on the hold with `reservation`
509
+ * ({@link ReviewedOutcome}), else `handed` (what its caller has: a hold
510
+ * that saved none, or an older version's).
511
+ */
512
+ const outcomeOf = (handle, reservation, handed) => (reservation === undefined
513
+ ? undefined
514
+ : decodeOutcome(handle.outcomeOf(reservation))) ?? handed;
515
+ /** The action's hashes through `outcome`'s step, as `settled` ended it. */
516
+ const hashesThrough = (outcome, settled) => settled?.result === "success" && "hash" in settled
517
+ ? [...outcome.prior, settled.hash]
518
+ : [...outcome.prior];
519
+ /** The outcome's signatures, as a result spreads them. */
520
+ const signedBy = ({ signatures }) => signatures ? { signatures: [...signatures] } : {};
521
+ /**
522
+ * Tells a site what the wallet answered, under its `locks`: refused for a
523
+ * lost one, kept for a fresh lock ({@link keepOutcome}, `settled` told
524
+ * whether it was applied in the end), or dropped when it can't be kept
525
+ * (`settled` told false). Any other failure is logged and never changes
526
+ * what was sent. False when the outcome was only kept, not told.
527
+ */
528
+ const tellKept = async ({ locks, attemptScoped }, run, settled) => {
529
+ let kept = false;
530
+ await tell(() => keepOutcome(locks, run, attemptScoped ? run : undefined, settled).then((applied) => void (kept = !applied), async (error) => {
531
+ if (error?.code !== "LOCK_LOST")
532
+ throw error;
533
+ kept = true;
534
+ await settled?.(false);
535
+ }));
536
+ return !kept;
537
+ };
441
538
  /**
442
539
  * A sent step's settlement by its own reservation, and the receipt its hold
443
540
  * verified ({@link heldReceipt}), read again until it reads. Null on `follow`'s
@@ -456,7 +553,10 @@ async function followSent(handle, client, reserved, follow) {
456
553
  const read = (work) => Promise.race([work, ended]);
457
554
  try {
458
555
  while (!signal.aborted) {
459
- const state = await read(followHeldWrite(handle, client, { signal }));
556
+ const state = await read(followHeldWrite(handle, client, {
557
+ signal,
558
+ ...(reserved ? { reservation: reserved } : {}),
559
+ }));
460
560
  if (signal.aborted)
461
561
  return null;
462
562
  if (state?.status !== "clear" ||
@@ -489,33 +589,72 @@ async function followSent(handle, client, reserved, follow) {
489
589
  * one acknowledgement of a followed step. Returns the settlement and that
490
590
  * checked receipt (a failure's too); null until then. A read that fails or disagrees with the hold throws with
491
591
  * nothing advanced: call it again. Call it once per settlement:
492
- * `onConfirmed` is told each time.
592
+ * `onConfirmed` is told each time. Its reservation's own saved settlement
593
+ * completes it after a later reservation of the call replaced it. Once
594
+ * `signal` aborts (the run that showed it is gone) it returns null, leaving
595
+ * the journal and `onConfirmed` to the run that resumes it. With
596
+ * `acknowledge` false it acknowledges nothing: the caller acknowledges the
597
+ * success once it has shown it (`handle.acknowledge`), so a success it
598
+ * never showed still warns the next run. `undelivered` says `onConfirmed`
599
+ * was kept for a lost lock and dropped: nothing is acknowledged, and a later
600
+ * completion (under the same or a fresh lock) tells it once.
493
601
  */
494
- export async function completeReviewedStep(unverified, action, deps, receipt) {
602
+ export async function completeReviewedStep(unverified, action, deps, receipt, signal, acknowledge = true) {
495
603
  const { step, held } = unverified;
496
- if (!held)
604
+ if (!held || signal?.aborted)
497
605
  return null;
498
606
  const { call, reservation } = held;
499
607
  const client = deps.clientFor(call.chainId);
500
608
  const handle = openStepHold(deps.hold, action.account, false, call);
501
609
  const state = await handle.state(client);
502
- if (state.status !== "clear" ||
503
- state.reservation !== reservation ||
504
- !state.settled)
610
+ const settled = (state.status === "clear" && state.reservation === reservation
611
+ ? state.settled
612
+ : undefined) ??
613
+ (reservation === undefined ? undefined : handle.settlementOf(reservation));
614
+ if (!settled)
505
615
  return null;
506
- const { settled } = state;
507
616
  const confirmed = await confirmedReceipt(settled, client, receipt);
508
- if (action.scope !== undefined) {
617
+ if (signal?.aborted)
618
+ return null;
619
+ const outcome = outcomeOf(handle, reservation, {
620
+ account: action.account,
621
+ step,
622
+ call,
623
+ prior: [...(held.hashes ?? [])],
624
+ ...(held.signatures ? { signatures: held.signatures } : {}),
625
+ });
626
+ const ended = await endStep(action, handle, outcome, settled, confirmed, async () => {
627
+ if (action.scope === undefined)
628
+ return;
509
629
  const storage = deps.storage ?? createHeldWriteStorage();
510
- const { raw, record } = readReviewedSequence(action.scope, action.account, storage);
511
- if (record?.sending?.step === step &&
512
- record.sending.reservation === reservation)
513
- await recordSending(record, settled, storage, raw);
514
- }
515
- await tellConfirmed(action, handle, call, step, settled, confirmed);
516
- if (settled.result === "success")
630
+ const scope = action.scope;
631
+ // Its journal entry by the reservation it saved (by the step its
632
+ // outcome saved, without one), never by the index of the action that
633
+ // completes it: a refreshed action's calls may be fewer.
634
+ const sending = () => {
635
+ const read = readReviewedSequence(scope, action.account, storage);
636
+ const saved = read.record?.sending;
637
+ return saved &&
638
+ saved.reservation === reservation &&
639
+ (reservation !== undefined || saved.step === outcome.step)
640
+ ? read
641
+ : null;
642
+ };
643
+ const read = sending();
644
+ if (!read)
645
+ return;
646
+ // Another completion recording it first leaves nothing to record.
647
+ await recordSending(read.record, settled, storage, read.raw).catch((error) => {
648
+ if (sending())
649
+ throw error;
650
+ });
651
+ });
652
+ const done = { settled, receipt: confirmed, ...signedBy(outcome) };
653
+ if (!ended)
654
+ return { ...done, undelivered: true, hashes: [] };
655
+ if (acknowledge && settled.result === "success")
517
656
  await handle.acknowledge(settled);
518
- return { settled, receipt: confirmed };
657
+ return { ...done, hashes: hashesThrough(outcome, settled) };
519
658
  }
520
659
  /**
521
660
  * The hash a wallet returned, even when the site's record of it failed
@@ -538,8 +677,21 @@ async function send(action, deps, resuming) {
538
677
  const sent = snapshots.map(({ sent }) => sent);
539
678
  /** Each step's simulation block, read once; a resumed run's prerequisites are long confirmed. */
540
679
  const blocks = calls.map((call) => resuming ? undefined : call.simulationBlockNumber);
541
- /** A site's release callback never stops core's own release. */
542
- const tellUnsent = (call, step) => tell(action.onUnsent, call, step);
680
+ /**
681
+ * Tell the site its steps from `from` were not sent, each only while every
682
+ * one of its locks is still this tab's, read fresh right before that
683
+ * step's callback (an earlier callback's await may have outlived the
684
+ * lease): once one was lost, the lock's new owner owns the site's journal,
685
+ * whatever ended the attempt. A site's release callback never stops core's
686
+ * own release.
687
+ */
688
+ const tellUnsent = async (steps, from) => {
689
+ for (const [index, call] of steps.entries())
690
+ await tellKept(action, async () => {
691
+ await assertLocksHeld(action.locks);
692
+ await action.onUnsent?.(call, from + index);
693
+ });
694
+ };
543
695
  const connected = adapter.account();
544
696
  if (!connected)
545
697
  throw new ReviewLineError("Connect a wallet first.");
@@ -574,32 +726,34 @@ async function send(action, deps, resuming) {
574
726
  try {
575
727
  for (const [step, call] of calls.entries()) {
576
728
  began += 1;
729
+ // The site's own record before the wallet: never under a lost lock.
730
+ await assertLocksHeld(action.locks);
577
731
  await action.beforeWallet?.(call, step);
578
732
  }
733
+ await assertLocksHeld(action.locks);
579
734
  // The site's hooks may have awaited: the gates once more, right before the wallet.
580
735
  action.guard?.();
581
736
  action.assertWriteState?.();
582
737
  if (adapter.account()?.toLowerCase() !== account.toLowerCase())
583
738
  throw new ReviewLineError(ACCOUNT_CHANGED);
584
739
  submitting = true;
585
- hash = await adapter.submitToReviewer({
586
- account,
587
- calls: sent.map((call) => ({ ...call })),
588
- });
740
+ hash = await adapter.submitToReviewer({ account, calls: sent.map((call) => ({ ...call })) }, { fence: () => assertLocksHeld(action.locks) });
589
741
  }
590
742
  catch (error) {
591
- // Only the site's own refusal or an explicit rejection proves nothing was sent.
592
- if (!submitting || isDefiniteWalletRejection(error))
593
- for (const [step, call] of calls.slice(0, began).entries())
594
- await tellUnsent(call, step);
743
+ // Only the site's own refusal, the adapter's or an explicit rejection
744
+ // proves nothing was sent.
745
+ if (!submitting ||
746
+ error instanceof WalletNotInvokedError ||
747
+ isDefiniteWalletRejection(error))
748
+ await tellUnsent(calls.slice(0, began), 0);
595
749
  throw error;
596
750
  }
597
751
  // Every step is told; a site's failure to record it never changes what was sent.
598
752
  for (const [step, call] of calls.entries())
599
- await tell(action.onSubmitted, call, step, hash, {
753
+ await tellKept(action, () => action.onSubmitted?.(call, step, hash, {
600
754
  safe: false,
601
755
  adopted: false,
602
- });
756
+ }));
603
757
  return { status: "done", hashes: [hash], receipts: [] };
604
758
  }
605
759
  const { scope } = action;
@@ -700,7 +854,7 @@ async function send(action, deps, resuming) {
700
854
  });
701
855
  };
702
856
  signed = {
703
- signatures: await signPrepared(prepared, account, adapter, stillReviewed),
857
+ signatures: await signPrepared(prepared, account, adapter, stillReviewed, () => assertLocksHeld(action.locks), () => assertLocksHeld(action.locks)),
704
858
  };
705
859
  }
706
860
  if (batched) {
@@ -731,9 +885,10 @@ async function send(action, deps, resuming) {
731
885
  }
732
886
  const released = async () => {
733
887
  gate.onRejected();
734
- for (const [step, call] of calls.slice(0, began).entries())
735
- await tellUnsent(call, step);
888
+ await tellUnsent(calls.slice(0, began), 0);
736
889
  };
890
+ /** Every lock is still this tab's: the hold's and the site's. */
891
+ const fence = () => assertLocksHeld([gate, ...(action.locks ?? [])]);
737
892
  return submitReviewedContractWrite({
738
893
  request: batched,
739
894
  expectedAccount: account,
@@ -761,12 +916,23 @@ async function send(action, deps, resuming) {
761
916
  return sending;
762
917
  },
763
918
  beforeWrite: async () => {
764
- await gate.beforeSend();
919
+ await gate.beforeSend(undefined, encodeOutcome({
920
+ account,
921
+ step: calls.length - 1,
922
+ call: batched,
923
+ prior: [],
924
+ ...signed,
925
+ }));
765
926
  try {
766
927
  for (const [step, call] of calls.entries()) {
767
928
  began = step + 1;
929
+ // The site's own record before the wallet: never under a lost lock.
930
+ await assertLocksHeld(action.locks);
768
931
  await action.beforeWallet?.(call, step);
769
932
  }
933
+ // Core's own check before the adapter, which fences again as
934
+ // the last await before the wallet.
935
+ await fence();
770
936
  }
771
937
  catch (error) {
772
938
  await released();
@@ -789,13 +955,13 @@ async function send(action, deps, resuming) {
789
955
  gate.afterSubmit(submitted);
790
956
  // Every step went: each is told, whatever an earlier callback does.
791
957
  for (const [step, call] of calls.entries())
792
- await tell(action.onSubmitted, call, step, submitted, {
958
+ await tellKept(action, () => action.onSubmitted?.(call, step, submitted, {
793
959
  safe: true,
794
960
  adopted: false,
795
- });
961
+ }));
796
962
  },
797
963
  write: async () => {
798
- const id = await adapter.sendCalls(batched.chainId, sent.map(({ to, data, value }) => ({ to, data, value })));
964
+ const id = await adapter.sendCalls(batched.chainId, sent.map(({ to, data, value }) => ({ to, data, value })), { fence });
799
965
  // A Safe app answers with the proposal's safeTxHash; anything else can't be followed.
800
966
  if (typeof id !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(id))
801
967
  throw new ReviewLineError(SAFE_BATCH_UNFOLLOWABLE_LINE);
@@ -828,6 +994,7 @@ async function send(action, deps, resuming) {
828
994
  ...snapshots[step].sent,
829
995
  })),
830
996
  completed: [],
997
+ ...signed,
831
998
  };
832
999
  // Refused when another window changed it meanwhile.
833
1000
  expected = await saveReviewedSequence(journal, storage, expected);
@@ -848,6 +1015,11 @@ async function send(action, deps, resuming) {
848
1015
  }
849
1016
  };
850
1017
  const hashes = [];
1018
+ // A resumed run shows the signatures its first run signed.
1019
+ if (journal?.signatures)
1020
+ signed = { signatures: journal.signatures };
1021
+ /** The last step's outcome, as its hold keeps it, and its hashes: the done result's source. */
1022
+ let last;
851
1023
  const receipts = [];
852
1024
  const total = offset + calls.length;
853
1025
  for (const [step, call] of calls.entries()) {
@@ -909,6 +1081,14 @@ async function send(action, deps, resuming) {
909
1081
  const runEnd = hashes.length ? action.follow?.signal : undefined;
910
1082
  /** The site's failed record of this step's hash: the step was sent, and is held unverified. */
911
1083
  let unrecorded;
1084
+ /** What this step's run shows, saved with its reservation. */
1085
+ const outcome = {
1086
+ account,
1087
+ step: at,
1088
+ call,
1089
+ prior: journal ? [...journal.completed] : [...hashes],
1090
+ ...signed,
1091
+ };
912
1092
  const hash = await handles[step]
913
1093
  .send(client, (gate) => {
914
1094
  // Under the hold's own check of the call, before anything reserves.
@@ -922,8 +1102,10 @@ async function send(action, deps, resuming) {
922
1102
  delete journal.sending;
923
1103
  await keep();
924
1104
  }
925
- await tellUnsent(call, at);
1105
+ await tellUnsent([call], at);
926
1106
  };
1107
+ /** Every lock is still this tab's: the hold's and the site's. */
1108
+ const fence = () => assertLocksHeld([gate, ...(action.locks ?? [])]);
927
1109
  return submitReviewedContractWrite({
928
1110
  request: reviewed,
929
1111
  expectedAccount: account,
@@ -976,7 +1158,7 @@ async function send(action, deps, resuming) {
976
1158
  },
977
1159
  beforeWrite: async () => {
978
1160
  // An adopted proposal is held as the call it runs.
979
- await gate.beforeSend(queued?.call);
1161
+ await gate.beforeSend(queued?.call, encodeOutcome(outcome));
980
1162
  reserved = handles[step].peek().reservation;
981
1163
  if (journal)
982
1164
  try {
@@ -993,7 +1175,12 @@ async function send(action, deps, resuming) {
993
1175
  throw error;
994
1176
  }
995
1177
  try {
1178
+ // The site's own record before the wallet: never under a lost lock.
1179
+ await assertLocksHeld(action.locks);
996
1180
  await action.beforeWallet?.(call, at);
1181
+ // Core's own check before the adapter, which fences again as
1182
+ // the last await before the wallet.
1183
+ await fence();
997
1184
  }
998
1185
  catch (error) {
999
1186
  await released();
@@ -1023,10 +1210,10 @@ async function send(action, deps, resuming) {
1023
1210
  // An adopted proposal's nonce lets the hold prove its execution onchain.
1024
1211
  onWriteSubmitted: async (submitted) => {
1025
1212
  gate.afterSubmit(submitted, queued?.tx);
1026
- await tell(action.onSubmitted, call, at, submitted, {
1213
+ await tellKept(action, () => action.onSubmitted?.(call, at, submitted, {
1027
1214
  safe,
1028
1215
  adopted: !!queued,
1029
- });
1216
+ }));
1030
1217
  },
1031
1218
  write: async ({ chainId, to, data, value, gas }) => queued?.proposalHash ??
1032
1219
  adapter.sendTransaction({
@@ -1038,6 +1225,7 @@ async function send(action, deps, resuming) {
1038
1225
  // After a step was sent, the run's end refuses this one, unsent,
1039
1226
  // as late as the wallet's own last check.
1040
1227
  ...(runEnd ? { signal: runEnd } : {}),
1228
+ fence,
1041
1229
  }),
1042
1230
  onPhase: (phase) => progress(phase),
1043
1231
  guard: action.guard,
@@ -1072,14 +1260,14 @@ async function send(action, deps, resuming) {
1072
1260
  }
1073
1261
  // From here the step was sent: it ends done, failed as its hold
1074
1262
  // verified, or unverified with what resumes it, never any other way.
1263
+ /** `completed`: this step's hash already recorded as completed. */
1075
1264
  const unverified = (cause) => new ReviewedReceiptUnverifiedError(at, hash, isSafeExecTransaction(reviewed.data)
1076
1265
  ? WRITE_HOLD_SAFE_UNCONFIRMED_LINE
1077
1266
  : undefined, {
1078
1267
  call,
1079
1268
  reservation: reserved,
1080
- // This run's sends, its own last: only the steps before it completed.
1081
- hashes: journal ? [...journal.completed] : hashes.slice(0, -1),
1082
- ...signed,
1269
+ hashes: [...outcome.prior],
1270
+ ...signedBy(outcome),
1083
1271
  }, cause === undefined ? {} : { cause });
1084
1272
  // The site could not record the hash: held as sent, for it to resume.
1085
1273
  if (unrecorded !== undefined)
@@ -1138,19 +1326,24 @@ async function send(action, deps, resuming) {
1138
1326
  throw new ReviewedCallRevertedError(at, mined, receipt);
1139
1327
  }
1140
1328
  receipts.push(receipt);
1141
- if (journal) {
1329
+ // Its notice not delivered yet: held at its receipt for its completion.
1330
+ const ended = await endStep(action, handles[step], outcome, settled, receipt, async () => {
1331
+ if (!journal)
1332
+ return;
1142
1333
  delete journal.sending;
1143
1334
  journal.completed.push(mined);
1144
1335
  await keep();
1145
- }
1146
- await tellConfirmed(action, handles[step], call, at, settled, receipt);
1336
+ });
1337
+ if (!ended)
1338
+ throw unverified();
1339
+ last = { outcome, hashes: hashesThrough(outcome, settled) };
1147
1340
  }
1148
1341
  onProgress({ step: total - 1, total, phase: "done" });
1149
1342
  return {
1150
1343
  status: "done",
1151
- hashes: journal ? [...journal.completed] : hashes,
1344
+ hashes: last.hashes,
1152
1345
  receipts,
1153
- ...signed,
1346
+ ...signedBy(last.outcome),
1154
1347
  };
1155
1348
  }
1156
1349
  //# sourceMappingURL=send.js.map