@bananapus/nana-sdk-core 2.38.2 → 2.39.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/heldWrite.d.ts +113 -0
- package/dist/cjs/review/heldWrite.d.ts.map +1 -0
- package/dist/cjs/review/heldWrite.js +551 -0
- package/dist/cjs/review/heldWrite.js.map +1 -0
- package/dist/cjs/review/index.d.ts +2 -0
- package/dist/cjs/review/index.d.ts.map +1 -1
- package/dist/cjs/review/index.js +2 -0
- package/dist/cjs/review/index.js.map +1 -1
- package/dist/cjs/review/messages.d.ts +30 -0
- package/dist/cjs/review/messages.d.ts.map +1 -1
- package/dist/cjs/review/messages.js +34 -1
- package/dist/cjs/review/messages.js.map +1 -1
- package/dist/cjs/review/savedAttempt.d.ts +180 -0
- package/dist/cjs/review/savedAttempt.d.ts.map +1 -0
- package/dist/cjs/review/savedAttempt.js +527 -0
- package/dist/cjs/review/savedAttempt.js.map +1 -0
- package/dist/cjs/review/writeHold.d.ts +94 -2
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +284 -43
- package/dist/cjs/review/writeHold.js.map +1 -1
- package/dist/cjs/review/writeRecovery.d.ts +58 -0
- package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
- package/dist/cjs/review/writeRecovery.js +159 -2
- package/dist/cjs/review/writeRecovery.js.map +1 -1
- package/dist/esm/review/heldWrite.d.ts +113 -0
- package/dist/esm/review/heldWrite.d.ts.map +1 -0
- package/dist/esm/review/heldWrite.js +544 -0
- package/dist/esm/review/heldWrite.js.map +1 -0
- package/dist/esm/review/index.d.ts +2 -0
- package/dist/esm/review/index.d.ts.map +1 -1
- package/dist/esm/review/index.js +2 -0
- package/dist/esm/review/index.js.map +1 -1
- package/dist/esm/review/messages.d.ts +30 -0
- package/dist/esm/review/messages.d.ts.map +1 -1
- package/dist/esm/review/messages.js +32 -0
- package/dist/esm/review/messages.js.map +1 -1
- package/dist/esm/review/savedAttempt.d.ts +180 -0
- package/dist/esm/review/savedAttempt.d.ts.map +1 -0
- package/dist/esm/review/savedAttempt.js +524 -0
- package/dist/esm/review/savedAttempt.js.map +1 -0
- package/dist/esm/review/writeHold.d.ts +94 -2
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +285 -44
- package/dist/esm/review/writeHold.js.map +1 -1
- package/dist/esm/review/writeRecovery.d.ts +58 -0
- package/dist/esm/review/writeRecovery.d.ts.map +1 -1
- package/dist/esm/review/writeRecovery.js +158 -2
- package/dist/esm/review/writeRecovery.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { type Address, type Hex, type PublicClient, type TransactionReceipt } from "viem";
|
|
2
2
|
import { type SafeQueuedTransaction, type SafeServiceOptions } from "../safeService.js";
|
|
3
3
|
import { type HeldWriteStorage, type ReviewedWriteRecoveryInput, type ReviewedWriteRecoveryRecord } from "./writeRecovery.js";
|
|
4
|
+
/** A saved success the user hasn't acknowledged: it blocks sending the action again. */
|
|
5
|
+
export type UnacknowledgedSuccess = {
|
|
6
|
+
result: "success";
|
|
7
|
+
hash: Hex;
|
|
8
|
+
/** The reservation it settled. */
|
|
9
|
+
reservation?: string;
|
|
10
|
+
};
|
|
4
11
|
/** The reads a write hold makes on the call's chain; a viem PublicClient satisfies it. */
|
|
5
12
|
export type WriteHoldClient = Pick<PublicClient, "getBlock" | "getBlockNumber" | "getChainId" | "getTransaction" | "getTransactionCount" | "getTransactionReceipt" | "request">;
|
|
6
13
|
/** How a held write ended, when the hold saw it end. */
|
|
7
14
|
export type WriteHoldSettled =
|
|
8
|
-
/** Its verified receipt (a revert only once final). */
|
|
15
|
+
/** Its verified receipt (a revert only once final); `acknowledged` once the user saw it. */
|
|
9
16
|
{
|
|
10
17
|
result: "success" | "failed";
|
|
11
18
|
hash: Hex;
|
|
19
|
+
acknowledged?: true;
|
|
12
20
|
}
|
|
13
21
|
/** A Safe proposal's expiry before it ran is proven. */
|
|
14
22
|
| {
|
|
@@ -230,13 +238,97 @@ export declare function createWriteHold(options?: {
|
|
|
230
238
|
safe?: {
|
|
231
239
|
safeTxHash: Hex;
|
|
232
240
|
};
|
|
233
|
-
} | undefined, client?: WriteHoldClient | undefined) => Promise<WriteHoldState
|
|
241
|
+
} | undefined, client?: WriteHoldClient | undefined) => Promise<WriteHoldState | (WriteHoldState & {
|
|
242
|
+
declined?: "refused" | "unreadable";
|
|
243
|
+
})>;
|
|
234
244
|
/**
|
|
235
245
|
* Every state this controller reads or makes for the call, and other
|
|
236
246
|
* tabs' changes. The latest one, if any, is replayed at once.
|
|
237
247
|
*/
|
|
238
248
|
subscribe(listener: (state: WriteHoldState) => void): () => void;
|
|
249
|
+
/**
|
|
250
|
+
* The send gate's evidence for this key: its saved success, while no
|
|
251
|
+
* record is held and the user hasn't acknowledged it. Pure, from storage.
|
|
252
|
+
*/
|
|
253
|
+
unacknowledgedSuccess(skip?: (hash: Hex) => boolean): UnacknowledgedSuccess | undefined;
|
|
254
|
+
/** How `reservation` settled, if it is this key's latest and settled. */
|
|
255
|
+
settlementOf(reservation: string): WriteHoldSettled | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* The user acknowledged a success the hold settled while they were
|
|
258
|
+
* away (or a view saw it settle): its saved settlement is marked
|
|
259
|
+
* acknowledged, keeping its result for every reader, so it blocks no
|
|
260
|
+
* send. Under the call's lock; nothing is sent.
|
|
261
|
+
*/
|
|
262
|
+
acknowledge(settled: {
|
|
263
|
+
result: "success" | "failed";
|
|
264
|
+
hash: Hex;
|
|
265
|
+
}): Promise<WriteHoldState>;
|
|
239
266
|
};
|
|
267
|
+
list: ({ account, chainId }: {
|
|
268
|
+
account: Address;
|
|
269
|
+
chainId: number;
|
|
270
|
+
}) => {
|
|
271
|
+
key: string;
|
|
272
|
+
record: ReviewedWriteRecoveryRecord;
|
|
273
|
+
handle: {
|
|
274
|
+
peek: () => WriteHoldState;
|
|
275
|
+
state(client: WriteHoldClient, checks?: {
|
|
276
|
+
effect?: (chainId: number) => Promise<boolean>;
|
|
277
|
+
}): Promise<WriteHoldState>;
|
|
278
|
+
send<T>(client: WriteHoldClient, run: (gate: WriteHoldGate) => Promise<T>): Promise<T>;
|
|
279
|
+
exit(snapshot: WriteHoldState, acknowledgement: {
|
|
280
|
+
checkedWallet: true;
|
|
281
|
+
}, client: WriteHoldClient): Promise<{
|
|
282
|
+
released: boolean;
|
|
283
|
+
state: WriteHoldState;
|
|
284
|
+
}>;
|
|
285
|
+
onReceipt: (receipt: TransactionReceipt, client: WriteHoldClient, proof?: {
|
|
286
|
+
calls: readonly ReviewedWriteRecoveryInput["call"][];
|
|
287
|
+
batch?: boolean;
|
|
288
|
+
} | undefined) => Promise<WriteHoldState>;
|
|
289
|
+
onUnproven: (client: WriteHoldClient, proposalHash?: `0x${string}` | undefined) => Promise<WriteHoldState>;
|
|
290
|
+
onExecuted: (client: WriteHoldClient, executionHash?: `0x${string}` | undefined) => Promise<WriteHoldState>;
|
|
291
|
+
attachHash: (client: WriteHoldClient, hash: `0x${string}`) => Promise<WriteHoldState>;
|
|
292
|
+
attachSafeNonce: (client: WriteHoldClient, args_1: {
|
|
293
|
+
safeTxHash: Hex;
|
|
294
|
+
nonce: number;
|
|
295
|
+
}) => Promise<WriteHoldState>;
|
|
296
|
+
adopt: (snapshot: WriteHoldState, evidence?: {
|
|
297
|
+
nonce?: number | bigint;
|
|
298
|
+
sentNonce?: number | bigint;
|
|
299
|
+
fromBlock?: bigint;
|
|
300
|
+
submittedAt?: number;
|
|
301
|
+
hash?: Hex;
|
|
302
|
+
safe?: {
|
|
303
|
+
safeTxHash: Hex;
|
|
304
|
+
};
|
|
305
|
+
} | undefined, client?: WriteHoldClient | undefined) => Promise<WriteHoldState | (WriteHoldState & {
|
|
306
|
+
declined?: "refused" | "unreadable";
|
|
307
|
+
})>;
|
|
308
|
+
/**
|
|
309
|
+
* Every state this controller reads or makes for the call, and other
|
|
310
|
+
* tabs' changes. The latest one, if any, is replayed at once.
|
|
311
|
+
*/
|
|
312
|
+
subscribe(listener: (state: WriteHoldState) => void): () => void;
|
|
313
|
+
/**
|
|
314
|
+
* The send gate's evidence for this key: its saved success, while no
|
|
315
|
+
* record is held and the user hasn't acknowledged it. Pure, from storage.
|
|
316
|
+
*/
|
|
317
|
+
unacknowledgedSuccess(skip?: (hash: Hex) => boolean): UnacknowledgedSuccess | undefined;
|
|
318
|
+
/** How `reservation` settled, if it is this key's latest and settled. */
|
|
319
|
+
settlementOf(reservation: string): WriteHoldSettled | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* The user acknowledged a success the hold settled while they were
|
|
322
|
+
* away (or a view saw it settle): its saved settlement is marked
|
|
323
|
+
* acknowledged, keeping its result for every reader, so it blocks no
|
|
324
|
+
* send. Under the call's lock; nothing is sent.
|
|
325
|
+
*/
|
|
326
|
+
acknowledge(settled: {
|
|
327
|
+
result: "success" | "failed";
|
|
328
|
+
hash: Hex;
|
|
329
|
+
}): Promise<WriteHoldState>;
|
|
330
|
+
};
|
|
331
|
+
}[];
|
|
240
332
|
/** Whether holds outlive this tab right now. */
|
|
241
333
|
durable: () => boolean;
|
|
242
334
|
/** The review's line while holds last only in this tab, or null. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeHold.d.ts","sourceRoot":"","sources":["../../../src/review/writeHold.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,MAAM,CAAC;AACd,OAAO,EAOL,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAgB3B,OAAO,
|
|
1
|
+
{"version":3,"file":"writeHold.d.ts","sourceRoot":"","sources":["../../../src/review/writeHold.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isAddress, toEventSelector, } from "viem";
|
|
2
2
|
import { canonicalSafeTxHash, lookAtSafeProposalEnd, provenSafeProposalEnd, readSafeTransaction, SAFE_PROPOSAL_AWAITING, safeTransactionMessage, } from "../safeService.js";
|
|
3
3
|
import { SAFE_PROPOSAL_EXECUTED_LINE, WRITE_HOLD_HASHED_EXIT_LINE, WRITE_HOLD_IN_TAB_LINE, WRITE_HOLD_LOOKS_DONE_LINE, WRITE_HOLD_MINED_LINE, WRITE_HOLD_NONCE_LINE, WRITE_HOLD_PENDING_LINE, WRITE_HOLD_UNCHECKED_LINE, WRITE_HOLD_REVERTED_LINE, WRITE_HOLD_SAFE_REPLACED_LINE, WRITE_HOLD_SAFE_UNPROVEN_LINE, WRITE_HOLD_STALE_REVIEW_LINE, writeHeldMessage, } from "./messages.js";
|
|
4
|
-
import { createBrowserWriteRecovery, createHeldWriteStorage, findReviewedWrites, heldSafeProposal, findTransactionAtNonce, readReviewedWriteNonce, safeProposalIsHeldCall, SubmittedWritePersistenceError, verifyReviewedWriteExpiry, verifyReviewedWriteReceipt, } from "./writeRecovery.js";
|
|
4
|
+
import { createBrowserWriteRecovery, createHeldWriteStorage, findReviewedWrites, heldSafeProposal, findTransactionAtNonce, listReviewedWrites, readReviewedWriteNonce, safeProposalIsHeldCall, SubmittedWritePersistenceError, verifyReviewedWriteExpiry, verifyReviewedWriteReceipt, } from "./writeRecovery.js";
|
|
5
5
|
import { isBytes32 } from "../untrusted.js";
|
|
6
6
|
import { atCanonicalFinalizedBlock } from "./relayr.js";
|
|
7
7
|
import { TAB, tabShared } from "./tabState.js";
|
|
@@ -74,8 +74,10 @@ const UNSEEN_HASH_MS = 30 * 60_000;
|
|
|
74
74
|
/** The longest one request to Safe's service may take inside the hold. */
|
|
75
75
|
const SAFE_SERVICE_READ_MS = 8_000;
|
|
76
76
|
/** The 30 minutes have passed since the wallet returned the hash, or since it was first read. A clock that couldn't be saved (NaN) never runs out. */
|
|
77
|
-
function unseenTooLong(record
|
|
78
|
-
|
|
77
|
+
function unseenTooLong(record,
|
|
78
|
+
/** A later authenticated sighting, when there is one: the clock runs from it. */
|
|
79
|
+
seen) {
|
|
80
|
+
return (Date.now() - Number(seen ?? record.submittedAt ?? record.firstSeenAt) >=
|
|
79
81
|
UNSEEN_HASH_MS);
|
|
80
82
|
}
|
|
81
83
|
/** Whether a canonical finalized block shows the account's nonce still at or below the sent nonce; false when it can't say. */
|
|
@@ -380,10 +382,14 @@ export function createWriteHold(options = {}) {
|
|
|
380
382
|
* exactly this call. One that can't be read is unknown, not refused.
|
|
381
383
|
*/
|
|
382
384
|
async function adoptable(hash, client) {
|
|
383
|
-
if (!isBytes32(hash)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return
|
|
385
|
+
if (!isBytes32(hash))
|
|
386
|
+
return "refused";
|
|
387
|
+
if (!client)
|
|
388
|
+
return "unreadable";
|
|
389
|
+
const chainId = await client.getChainId().catch(() => null);
|
|
390
|
+
// A client on another chain can't check this chain's transaction.
|
|
391
|
+
if (chainId !== call.chainId)
|
|
392
|
+
return "unreadable";
|
|
387
393
|
const reviewed = {
|
|
388
394
|
account: call.account.toLowerCase(),
|
|
389
395
|
chainId: call.chainId,
|
|
@@ -393,22 +399,35 @@ export function createWriteHold(options = {}) {
|
|
|
393
399
|
value: String(call.call.value ?? 0),
|
|
394
400
|
},
|
|
395
401
|
};
|
|
402
|
+
// One the chain or the service can't show is this call's, held.
|
|
396
403
|
if (call.safe) {
|
|
397
404
|
const proposal = await readSafeTransaction(call.chainId, call.account, hash, service).catch(() => null);
|
|
398
|
-
return !proposal || safeProposalIsHeldCall(proposal, call)
|
|
405
|
+
return !proposal || safeProposalIsHeldCall(proposal, call)
|
|
406
|
+
? "yes"
|
|
407
|
+
: "refused";
|
|
399
408
|
}
|
|
400
409
|
const sent = await client.getTransaction({ hash }).catch(() => null);
|
|
401
|
-
return !sent || sentIsCall(sent, hash, reviewed);
|
|
410
|
+
return !sent || sentIsCall(sent, hash, reviewed) ? "yes" : "refused";
|
|
402
411
|
}
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Safe's authenticated record of the held proposal, or null while the
|
|
414
|
+
* service can't return it (down, or it has none).
|
|
415
|
+
*/
|
|
416
|
+
async function proposalOf(record) {
|
|
417
|
+
return readSafeTransaction(record.chainId, record.account, record.hash, service).catch(() => null);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* A Safe write unseen too long: not proven run, not ended unproven (that
|
|
421
|
+
* has its own exit), and not listed unexecuted by the service for 30
|
|
422
|
+
* minutes since it was sent, adopted, or last listed.
|
|
423
|
+
*/
|
|
424
|
+
function unboundedSafe(record) {
|
|
425
|
+
return (record.safe &&
|
|
426
|
+
!!record.hash &&
|
|
427
|
+
!record.executed &&
|
|
428
|
+
!record.unproven &&
|
|
429
|
+
!record.replaced &&
|
|
430
|
+
unseenTooLong(record, record.pendingSeenAt));
|
|
412
431
|
}
|
|
413
432
|
/** Follow a saved execution's receipt; null while the node has none. */
|
|
414
433
|
async function execution(record, client) {
|
|
@@ -607,13 +626,18 @@ export function createWriteHold(options = {}) {
|
|
|
607
626
|
return settled ?? ranExecuted(record);
|
|
608
627
|
}
|
|
609
628
|
}
|
|
610
|
-
// The chain's proof, from the proposal's nonce
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
629
|
+
// The chain's proof, from the proposal's nonce, saved once. Only the
|
|
630
|
+
// service returning this proposal unexecuted shows it truly pending:
|
|
631
|
+
// that read restarts its unseen clock. A Safe nonce still waiting shows
|
|
632
|
+
// only that nothing ran there.
|
|
633
|
+
const proposal = await proposalOf(record);
|
|
634
|
+
const listed = !!proposal && proposal.isExecuted !== true;
|
|
635
|
+
if (proposal && record.safeNonce === undefined)
|
|
636
|
+
record = recovery.proposalNonce(record, safeTransactionMessage(proposal).nonce);
|
|
637
|
+
// Listed: truly pending, an unproven end included. The fresh record
|
|
638
|
+
// carries the new stamp, so the checked answer below removes any exit.
|
|
639
|
+
if (listed)
|
|
640
|
+
record = recovery.listedAgain(record);
|
|
617
641
|
const end = (record.safeNonce !== undefined &&
|
|
618
642
|
(await provenSafeProposalEnd(client, record.account, hash, BigInt(record.safeNonce)))) ||
|
|
619
643
|
(await lookAtSafeProposalEnd(client, record.chainId, record.account, hash, service));
|
|
@@ -649,10 +673,9 @@ export function createWriteHold(options = {}) {
|
|
|
649
673
|
return clear(settled, record.id);
|
|
650
674
|
}
|
|
651
675
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
unseenTooLong(record))
|
|
676
|
+
// Its wait is bounded like any unchecked write, the clock restarted by
|
|
677
|
+
// each listing (an unproven end has its own exit).
|
|
678
|
+
if (unboundedSafe(record))
|
|
656
679
|
return unchecked(record);
|
|
657
680
|
if (record.executed)
|
|
658
681
|
follow(client, record.id);
|
|
@@ -690,9 +713,13 @@ export function createWriteHold(options = {}) {
|
|
|
690
713
|
// A mirror carries no evidence of its own: its source attempt does.
|
|
691
714
|
if (record.mirrorOf)
|
|
692
715
|
return classify(record);
|
|
693
|
-
// No evidence from another chain.
|
|
694
|
-
|
|
695
|
-
|
|
716
|
+
// No evidence from another chain. Unreadable chain identity still
|
|
717
|
+
// bounds a Safe write's unchecked wait.
|
|
718
|
+
const chainId = await client.getChainId().catch(() => null);
|
|
719
|
+
if (chainId !== record.chainId)
|
|
720
|
+
return chainId === null && unboundedSafe(record)
|
|
721
|
+
? unchecked(record)
|
|
722
|
+
: classify(record);
|
|
696
723
|
if (record.safe)
|
|
697
724
|
return safeState(record, client);
|
|
698
725
|
const check = await recovery
|
|
@@ -1037,10 +1064,13 @@ export function createWriteHold(options = {}) {
|
|
|
1037
1064
|
if (hooks.across) {
|
|
1038
1065
|
// Under an intent: the same atomic reserve as a send, under every
|
|
1039
1066
|
// legacy key's lock, mirrored at each so older versions see it.
|
|
1040
|
-
if (!free()
|
|
1041
|
-
(hash !== undefined && !(await adoptable(hash, client))))
|
|
1067
|
+
if (!free())
|
|
1042
1068
|
return shown(peek());
|
|
1069
|
+
const verdict = hash === undefined ? "yes" : await adoptable(hash, client);
|
|
1070
|
+
if (verdict !== "yes")
|
|
1071
|
+
return { ...shown(peek()), declined: verdict };
|
|
1043
1072
|
let adopted = false;
|
|
1073
|
+
let busy = false;
|
|
1044
1074
|
try {
|
|
1045
1075
|
const { released } = await hooks.across((work) => recovery.withLock(work), () => {
|
|
1046
1076
|
if (!free())
|
|
@@ -1050,21 +1080,34 @@ export function createWriteHold(options = {}) {
|
|
|
1050
1080
|
}, recovery.rejected, Promise.resolve(), hooks.generations());
|
|
1051
1081
|
await released;
|
|
1052
1082
|
}
|
|
1053
|
-
catch {
|
|
1054
|
-
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
// Held elsewhere or no longer free: what the hold shows. Busy: unread.
|
|
1085
|
+
busy = error instanceof WriteHoldBusyError;
|
|
1055
1086
|
}
|
|
1056
1087
|
// A mined hash settles on its verified receipt, through the gate.
|
|
1057
|
-
|
|
1088
|
+
if (adopted && hash && client)
|
|
1089
|
+
return state(client);
|
|
1090
|
+
const now = shown(peek());
|
|
1091
|
+
return busy && now.status === "clear"
|
|
1092
|
+
? { ...now, declined: "unreadable" }
|
|
1093
|
+
: now;
|
|
1058
1094
|
}
|
|
1059
1095
|
const result = await locked(async () => {
|
|
1060
1096
|
if (!free())
|
|
1061
1097
|
return peek();
|
|
1062
|
-
|
|
1063
|
-
|
|
1098
|
+
const verdict = hash === undefined ? "yes" : await adoptable(hash, client);
|
|
1099
|
+
if (verdict !== "yes")
|
|
1100
|
+
return { ...peek(), declined: verdict };
|
|
1064
1101
|
reserve();
|
|
1065
1102
|
return hash && client ? inspect(client) : peek();
|
|
1066
1103
|
}, true);
|
|
1067
|
-
|
|
1104
|
+
if (!result)
|
|
1105
|
+
return { ...shown(peek()), declined: "unreadable" };
|
|
1106
|
+
const { declined, ...answer } = result.value;
|
|
1107
|
+
return {
|
|
1108
|
+
...shown(answer),
|
|
1109
|
+
...(declined ? { declined } : {}),
|
|
1110
|
+
};
|
|
1068
1111
|
},
|
|
1069
1112
|
/**
|
|
1070
1113
|
* Save the Safe nonce of a held Safe proposal adopted before it was
|
|
@@ -1091,6 +1134,46 @@ export function createWriteHold(options = {}) {
|
|
|
1091
1134
|
}, true);
|
|
1092
1135
|
return shown(result?.value ?? peek());
|
|
1093
1136
|
},
|
|
1137
|
+
/**
|
|
1138
|
+
* The send gate's evidence for this key: its saved success, while no
|
|
1139
|
+
* record is held and the user hasn't acknowledged it. Pure, from storage.
|
|
1140
|
+
*/
|
|
1141
|
+
unacknowledgedSuccess(
|
|
1142
|
+
/** A success to look past (one a view already told, its acknowledgement under way). */
|
|
1143
|
+
skip) {
|
|
1144
|
+
if (recovery.read())
|
|
1145
|
+
return undefined;
|
|
1146
|
+
const settled = recovery.settled();
|
|
1147
|
+
return settled?.result === "success" &&
|
|
1148
|
+
!settled.acknowledged &&
|
|
1149
|
+
!skip?.(settled.hash)
|
|
1150
|
+
? {
|
|
1151
|
+
result: "success",
|
|
1152
|
+
hash: settled.hash,
|
|
1153
|
+
reservation: recovery.latestReservation(),
|
|
1154
|
+
}
|
|
1155
|
+
: undefined;
|
|
1156
|
+
},
|
|
1157
|
+
/** How `reservation` settled, if it is this key's latest and settled. */
|
|
1158
|
+
settlementOf(reservation) {
|
|
1159
|
+
const now = peek();
|
|
1160
|
+
return now.status === "clear" && now.reservation === reservation
|
|
1161
|
+
? now.settled
|
|
1162
|
+
: undefined;
|
|
1163
|
+
},
|
|
1164
|
+
/**
|
|
1165
|
+
* The user acknowledged a success the hold settled while they were
|
|
1166
|
+
* away (or a view saw it settle): its saved settlement is marked
|
|
1167
|
+
* acknowledged, keeping its result for every reader, so it blocks no
|
|
1168
|
+
* send. Under the call's lock; nothing is sent.
|
|
1169
|
+
*/
|
|
1170
|
+
async acknowledge(settled) {
|
|
1171
|
+
const result = await locked(async () => {
|
|
1172
|
+
recovery.acknowledgeSettled(settled);
|
|
1173
|
+
return peek();
|
|
1174
|
+
}, true);
|
|
1175
|
+
return shown(result?.value ?? peek());
|
|
1176
|
+
},
|
|
1094
1177
|
async attachHash(client, hash) {
|
|
1095
1178
|
const result = await locked(async () => {
|
|
1096
1179
|
const record = recovery.read();
|
|
@@ -1253,10 +1336,20 @@ export function createWriteHold(options = {}) {
|
|
|
1253
1336
|
? [{ input: { ...exact, call: source.call } }]
|
|
1254
1337
|
: []));
|
|
1255
1338
|
rescan();
|
|
1339
|
+
/** The legacy hold this intent last stood for, with its reservation. */
|
|
1340
|
+
let routed;
|
|
1256
1341
|
/** A legacy key holding anything but this intent's own mirror. */
|
|
1257
1342
|
const foreign = (index) => holds(journals[index]) && delegates[index].peek().status !== "clear";
|
|
1258
1343
|
/** The legacy hold the intent stands for right now, from storage alone. */
|
|
1259
|
-
const active = () =>
|
|
1344
|
+
const active = () => {
|
|
1345
|
+
const index = delegates.findIndex((_, at) => foreign(at));
|
|
1346
|
+
if (index < 0)
|
|
1347
|
+
return undefined;
|
|
1348
|
+
const now = delegates[index].peek();
|
|
1349
|
+
if (now.status !== "clear")
|
|
1350
|
+
routed = { handle: delegates[index], reservation: now.record.id };
|
|
1351
|
+
return delegates[index];
|
|
1352
|
+
};
|
|
1260
1353
|
/** The first legacy key holding an unresolved write, through its own gate. */
|
|
1261
1354
|
async function delegated(client) {
|
|
1262
1355
|
rescan();
|
|
@@ -1264,11 +1357,83 @@ export function createWriteHold(options = {}) {
|
|
|
1264
1357
|
if (!foreign(index))
|
|
1265
1358
|
continue;
|
|
1266
1359
|
const state = await handle.state(client);
|
|
1267
|
-
if (state.status !== "clear")
|
|
1360
|
+
if (state.status !== "clear") {
|
|
1361
|
+
routed = { handle, reservation: state.record.id };
|
|
1268
1362
|
return state;
|
|
1363
|
+
}
|
|
1269
1364
|
}
|
|
1270
1365
|
return null;
|
|
1271
1366
|
}
|
|
1367
|
+
/**
|
|
1368
|
+
* How a legacy hold this intent stood for ended, while the intent itself
|
|
1369
|
+
* never reserved: its settlement (acknowledged or not) is the intent's,
|
|
1370
|
+
* so a view that saw it held shows its result, and an unacknowledged
|
|
1371
|
+
* success still blocks a fresh one.
|
|
1372
|
+
*/
|
|
1373
|
+
/**
|
|
1374
|
+
* Every legacy settlement that may be the intent's: the reservation it
|
|
1375
|
+
* was seen standing for (first), and any reserved after (or not provably
|
|
1376
|
+
* before) its own latest reservation. Times that can't be compared count
|
|
1377
|
+
* as newer: a false block is the safe side.
|
|
1378
|
+
*/
|
|
1379
|
+
function eligibleLegacy() {
|
|
1380
|
+
const eligible = [];
|
|
1381
|
+
if (routed) {
|
|
1382
|
+
const now = routed.handle.peek();
|
|
1383
|
+
if (now.status === "clear" &&
|
|
1384
|
+
now.settled &&
|
|
1385
|
+
now.reservation === routed.reservation)
|
|
1386
|
+
eligible.push({ handle: routed.handle, state: now });
|
|
1387
|
+
}
|
|
1388
|
+
const ownReserved = ownJournal.latestReservation() !== undefined;
|
|
1389
|
+
const ownAt = ownJournal.latestReservedAt();
|
|
1390
|
+
for (const [index, handle] of delegates.entries()) {
|
|
1391
|
+
if (eligible.some((entry) => entry.handle === handle))
|
|
1392
|
+
continue;
|
|
1393
|
+
const now = handle.peek();
|
|
1394
|
+
if (now.status !== "clear" || !now.settled)
|
|
1395
|
+
continue;
|
|
1396
|
+
const legacyAt = journals[index].latestReservedAt() ?? journals[index].settledAt();
|
|
1397
|
+
if (!ownReserved ||
|
|
1398
|
+
ownAt === undefined ||
|
|
1399
|
+
legacyAt === undefined ||
|
|
1400
|
+
// The same moment is no proof of order: counted as newer.
|
|
1401
|
+
legacyAt >= ownAt)
|
|
1402
|
+
eligible.push({ handle, state: now });
|
|
1403
|
+
}
|
|
1404
|
+
return eligible;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* What the intent shows of its legacy holds: the settlement of the one
|
|
1408
|
+
* it was seen standing for (so it is delivered), else an unacknowledged
|
|
1409
|
+
* success, else any eligible one. Blocking is the send gate's
|
|
1410
|
+
* (unacknowledgedSuccess), never this choice.
|
|
1411
|
+
*/
|
|
1412
|
+
function legacySettled() {
|
|
1413
|
+
const eligible = eligibleLegacy();
|
|
1414
|
+
return (eligible.find(({ handle, state }) => handle === routed?.handle &&
|
|
1415
|
+
state.reservation === routed.reservation) ??
|
|
1416
|
+
eligible.find(({ state }) => state.settled.result === "success" &&
|
|
1417
|
+
!("acknowledged" in state.settled && state.settled.acknowledged)) ??
|
|
1418
|
+
eligible[0]);
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* `state`, or, when it is a clear, the legacy settlement that owns the
|
|
1422
|
+
* intent's result instead: the one it was seen standing for, or one
|
|
1423
|
+
* reserved after its own latest reservation, whatever the intent's own
|
|
1424
|
+
* (older) settlement says. One choice for reads, events and the guard.
|
|
1425
|
+
*/
|
|
1426
|
+
function withLegacy(state) {
|
|
1427
|
+
if (state.status !== "clear")
|
|
1428
|
+
return state;
|
|
1429
|
+
const legacy = legacySettled();
|
|
1430
|
+
return legacy
|
|
1431
|
+
? {
|
|
1432
|
+
...legacy.state,
|
|
1433
|
+
...(state.checked === undefined ? {} : { checked: state.checked }),
|
|
1434
|
+
}
|
|
1435
|
+
: state;
|
|
1436
|
+
}
|
|
1272
1437
|
/**
|
|
1273
1438
|
* The final check and reservation, under every legacy key's lock taken
|
|
1274
1439
|
* in one sorted order (the intent's own is held by its send), each
|
|
@@ -1395,9 +1560,64 @@ export function createWriteHold(options = {}) {
|
|
|
1395
1560
|
};
|
|
1396
1561
|
return {
|
|
1397
1562
|
...own,
|
|
1398
|
-
peek: () => active()?.peek() ?? own.peek(),
|
|
1563
|
+
peek: () => active()?.peek() ?? withLegacy(own.peek()),
|
|
1564
|
+
/**
|
|
1565
|
+
* The intent's events, through the same owner as its state(): while a
|
|
1566
|
+
* legacy hold stands for it, its own empty clear is that hold's state
|
|
1567
|
+
* (or its settlement), never a release.
|
|
1568
|
+
*/
|
|
1569
|
+
subscribe: (listener) => own.subscribe((state) => listener(state.status === "clear"
|
|
1570
|
+
? (active()?.peek() ?? withLegacy(state))
|
|
1571
|
+
: state)),
|
|
1399
1572
|
async state(client, checks) {
|
|
1400
|
-
return ((await delegated(client)) ??
|
|
1573
|
+
return ((await delegated(client)) ??
|
|
1574
|
+
withLegacy(await after(await own.state(client, checks))));
|
|
1575
|
+
},
|
|
1576
|
+
/**
|
|
1577
|
+
* The send gate: any unacknowledged success, the intent's own or an
|
|
1578
|
+
* eligible legacy hold's. Pure, from storage, whatever a view shows.
|
|
1579
|
+
*/
|
|
1580
|
+
unacknowledgedSuccess(skip) {
|
|
1581
|
+
// Every eligible success is considered: one looked past never hides
|
|
1582
|
+
// another.
|
|
1583
|
+
const mine = own.unacknowledgedSuccess(skip);
|
|
1584
|
+
if (mine)
|
|
1585
|
+
return mine;
|
|
1586
|
+
for (const { state } of eligibleLegacy()) {
|
|
1587
|
+
const settled = state.settled;
|
|
1588
|
+
if (settled.result === "success" &&
|
|
1589
|
+
"hash" in settled &&
|
|
1590
|
+
!settled.acknowledged &&
|
|
1591
|
+
!skip?.(settled.hash))
|
|
1592
|
+
return {
|
|
1593
|
+
result: "success",
|
|
1594
|
+
hash: settled.hash,
|
|
1595
|
+
reservation: state.reservation,
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
return undefined;
|
|
1599
|
+
},
|
|
1600
|
+
/**
|
|
1601
|
+
* How a reservation this intent stood for settled, on its own key or a
|
|
1602
|
+
* legacy one, whichever hold is shown now.
|
|
1603
|
+
*/
|
|
1604
|
+
settlementOf(reservation) {
|
|
1605
|
+
for (const handle of [own, ...delegates]) {
|
|
1606
|
+
const settled = handle.settlementOf(reservation);
|
|
1607
|
+
if (settled)
|
|
1608
|
+
return settled;
|
|
1609
|
+
}
|
|
1610
|
+
return undefined;
|
|
1611
|
+
},
|
|
1612
|
+
/** A settlement is acknowledged where it was saved, found by its hash. */
|
|
1613
|
+
async acknowledge(settled) {
|
|
1614
|
+
const hash = settled.hash.toLowerCase();
|
|
1615
|
+
const legacy = eligibleLegacy().find(({ state }) => state.settled !== undefined &&
|
|
1616
|
+
"hash" in state.settled &&
|
|
1617
|
+
state.settled.hash === hash);
|
|
1618
|
+
return legacy
|
|
1619
|
+
? withLegacy(await legacy.handle.acknowledge(settled))
|
|
1620
|
+
: own.acknowledge(settled);
|
|
1401
1621
|
},
|
|
1402
1622
|
async send(client, run) {
|
|
1403
1623
|
const held = await delegated(client);
|
|
@@ -1513,8 +1733,29 @@ export function createWriteHold(options = {}) {
|
|
|
1513
1733
|
adopt: async (...args) => route()?.peek() ?? own.adopt(...args),
|
|
1514
1734
|
};
|
|
1515
1735
|
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Every write held for `account` on `chainId`, with a handle to each at
|
|
1738
|
+
* the key it was saved under: for an app-wide notice of everything held,
|
|
1739
|
+
* whatever action made it. Read from storage alone; each handle's state()
|
|
1740
|
+
* is the gated answer.
|
|
1741
|
+
*/
|
|
1742
|
+
function list({ account, chainId }) {
|
|
1743
|
+
return listReviewedWrites({ account, chainId }, { storage }).map(({ key, record }) => {
|
|
1744
|
+
const input = {
|
|
1745
|
+
chainId: record.chainId,
|
|
1746
|
+
account: record.account,
|
|
1747
|
+
safe: record.safe,
|
|
1748
|
+
call: record.call,
|
|
1749
|
+
};
|
|
1750
|
+
const handle = record.namespace === undefined
|
|
1751
|
+
? openExact(input, key)
|
|
1752
|
+
: open({ ...input, namespace: record.namespace });
|
|
1753
|
+
return { key, record, handle };
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1516
1756
|
return {
|
|
1517
1757
|
open,
|
|
1758
|
+
list,
|
|
1518
1759
|
/** Whether holds outlive this tab right now. */
|
|
1519
1760
|
durable: () => storage.durable(),
|
|
1520
1761
|
/** The review's line while holds last only in this tab, or null. */
|