@bananapus/nana-sdk-core 2.38.2 → 2.40.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 (89) hide show
  1. package/dist/cjs/review/heldWrite.d.ts +113 -0
  2. package/dist/cjs/review/heldWrite.d.ts.map +1 -0
  3. package/dist/cjs/review/heldWrite.js +569 -0
  4. package/dist/cjs/review/heldWrite.js.map +1 -0
  5. package/dist/cjs/review/index.d.ts +2 -0
  6. package/dist/cjs/review/index.d.ts.map +1 -1
  7. package/dist/cjs/review/index.js +2 -0
  8. package/dist/cjs/review/index.js.map +1 -1
  9. package/dist/cjs/review/messages.d.ts +30 -0
  10. package/dist/cjs/review/messages.d.ts.map +1 -1
  11. package/dist/cjs/review/messages.js +34 -1
  12. package/dist/cjs/review/messages.js.map +1 -1
  13. package/dist/cjs/review/react/HeldWritesNotice.d.ts +11 -0
  14. package/dist/cjs/review/react/HeldWritesNotice.d.ts.map +1 -0
  15. package/dist/cjs/review/react/HeldWritesNotice.js +22 -0
  16. package/dist/cjs/review/react/HeldWritesNotice.js.map +1 -0
  17. package/dist/cjs/review/react/index.d.ts +5 -0
  18. package/dist/cjs/review/react/index.d.ts.map +1 -0
  19. package/dist/cjs/review/react/index.js +21 -0
  20. package/dist/cjs/review/react/index.js.map +1 -0
  21. package/dist/cjs/review/react/useHeldWrite.d.ts +63 -0
  22. package/dist/cjs/review/react/useHeldWrite.d.ts.map +1 -0
  23. package/dist/cjs/review/react/useHeldWrite.js +107 -0
  24. package/dist/cjs/review/react/useHeldWrite.js.map +1 -0
  25. package/dist/cjs/review/react/useHeldWrites.d.ts +40 -0
  26. package/dist/cjs/review/react/useHeldWrites.d.ts.map +1 -0
  27. package/dist/cjs/review/react/useHeldWrites.js +95 -0
  28. package/dist/cjs/review/react/useHeldWrites.js.map +1 -0
  29. package/dist/cjs/review/react/useSavedAttempt.d.ts +54 -0
  30. package/dist/cjs/review/react/useSavedAttempt.d.ts.map +1 -0
  31. package/dist/cjs/review/react/useSavedAttempt.js +105 -0
  32. package/dist/cjs/review/react/useSavedAttempt.js.map +1 -0
  33. package/dist/cjs/review/savedAttempt.d.ts +180 -0
  34. package/dist/cjs/review/savedAttempt.d.ts.map +1 -0
  35. package/dist/cjs/review/savedAttempt.js +527 -0
  36. package/dist/cjs/review/savedAttempt.js.map +1 -0
  37. package/dist/cjs/review/writeHold.d.ts +94 -2
  38. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  39. package/dist/cjs/review/writeHold.js +284 -43
  40. package/dist/cjs/review/writeHold.js.map +1 -1
  41. package/dist/cjs/review/writeRecovery.d.ts +58 -0
  42. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  43. package/dist/cjs/review/writeRecovery.js +161 -2
  44. package/dist/cjs/review/writeRecovery.js.map +1 -1
  45. package/dist/esm/review/heldWrite.d.ts +113 -0
  46. package/dist/esm/review/heldWrite.d.ts.map +1 -0
  47. package/dist/esm/review/heldWrite.js +562 -0
  48. package/dist/esm/review/heldWrite.js.map +1 -0
  49. package/dist/esm/review/index.d.ts +2 -0
  50. package/dist/esm/review/index.d.ts.map +1 -1
  51. package/dist/esm/review/index.js +2 -0
  52. package/dist/esm/review/index.js.map +1 -1
  53. package/dist/esm/review/messages.d.ts +30 -0
  54. package/dist/esm/review/messages.d.ts.map +1 -1
  55. package/dist/esm/review/messages.js +32 -0
  56. package/dist/esm/review/messages.js.map +1 -1
  57. package/dist/esm/review/react/HeldWritesNotice.d.ts +11 -0
  58. package/dist/esm/review/react/HeldWritesNotice.d.ts.map +1 -0
  59. package/dist/esm/review/react/HeldWritesNotice.js +19 -0
  60. package/dist/esm/review/react/HeldWritesNotice.js.map +1 -0
  61. package/dist/esm/review/react/index.d.ts +5 -0
  62. package/dist/esm/review/react/index.d.ts.map +1 -0
  63. package/dist/esm/review/react/index.js +5 -0
  64. package/dist/esm/review/react/index.js.map +1 -0
  65. package/dist/esm/review/react/useHeldWrite.d.ts +63 -0
  66. package/dist/esm/review/react/useHeldWrite.d.ts.map +1 -0
  67. package/dist/esm/review/react/useHeldWrite.js +104 -0
  68. package/dist/esm/review/react/useHeldWrite.js.map +1 -0
  69. package/dist/esm/review/react/useHeldWrites.d.ts +40 -0
  70. package/dist/esm/review/react/useHeldWrites.d.ts.map +1 -0
  71. package/dist/esm/review/react/useHeldWrites.js +91 -0
  72. package/dist/esm/review/react/useHeldWrites.js.map +1 -0
  73. package/dist/esm/review/react/useSavedAttempt.d.ts +54 -0
  74. package/dist/esm/review/react/useSavedAttempt.d.ts.map +1 -0
  75. package/dist/esm/review/react/useSavedAttempt.js +102 -0
  76. package/dist/esm/review/react/useSavedAttempt.js.map +1 -0
  77. package/dist/esm/review/savedAttempt.d.ts +180 -0
  78. package/dist/esm/review/savedAttempt.d.ts.map +1 -0
  79. package/dist/esm/review/savedAttempt.js +524 -0
  80. package/dist/esm/review/savedAttempt.js.map +1 -0
  81. package/dist/esm/review/writeHold.d.ts +94 -2
  82. package/dist/esm/review/writeHold.d.ts.map +1 -1
  83. package/dist/esm/review/writeHold.js +285 -44
  84. package/dist/esm/review/writeHold.js.map +1 -1
  85. package/dist/esm/review/writeRecovery.d.ts +58 -0
  86. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  87. package/dist/esm/review/writeRecovery.js +160 -2
  88. package/dist/esm/review/writeRecovery.js.map +1 -1
  89. package/package.json +16 -2
@@ -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":"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"}
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
- return (Date.now() - Number(record.submittedAt ?? record.firstSeenAt) >=
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
- !client ||
385
- (await client.getChainId().catch(() => null)) !== call.chainId)
386
- return false;
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
- /** The authenticated nonce of a Safe proposal, from Safe's service. */
404
- async function proposalNonceOf(record) {
405
- try {
406
- const proposal = await readSafeTransaction(record.chainId, record.account, record.hash, service);
407
- return safeTransactionMessage(proposal).nonce;
408
- }
409
- catch {
410
- return null;
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: Safe's service is asked
611
- // only for the nonce, once, and for an expiry.
612
- if (record.safeNonce === undefined) {
613
- const nonce = await proposalNonceOf(record);
614
- if (nonce !== null)
615
- record = recovery.proposalNonce(record, nonce);
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
- if (!record.executed &&
653
- !record.unproven &&
654
- record.safeNonce === undefined &&
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
- if ((await client.getChainId().catch(() => null)) !== record.chainId)
695
- return classify(record);
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
- /* Held elsewhere, busy, or no longer free: nothing adopted. */
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
- return adopted && hash && client ? state(client) : shown(peek());
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
- if (hash !== undefined && !(await adoptable(hash, client)))
1063
- return peek();
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
- return shown(result?.value ?? peek());
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 = () => delegates.find((_, index) => foreign(index));
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)) ?? after(await own.state(client, checks)));
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. */