@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
package/dist/esm/review/send.js
CHANGED
|
@@ -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:
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
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 {
|
|
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
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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, {
|
|
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
|
-
|
|
503
|
-
state.
|
|
504
|
-
|
|
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 (
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
542
|
-
|
|
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
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1344
|
+
hashes: last.hashes,
|
|
1152
1345
|
receipts,
|
|
1153
|
-
...
|
|
1346
|
+
...signedBy(last.outcome),
|
|
1154
1347
|
};
|
|
1155
1348
|
}
|
|
1156
1349
|
//# sourceMappingURL=send.js.map
|