@bananapus/nana-sdk-core 2.26.0 → 2.27.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 (49) hide show
  1. package/dist/cjs/review/contractWrite.d.ts +12 -1
  2. package/dist/cjs/review/contractWrite.d.ts.map +1 -1
  3. package/dist/cjs/review/contractWrite.js +28 -2
  4. package/dist/cjs/review/contractWrite.js.map +1 -1
  5. package/dist/cjs/review/index.d.ts +1 -0
  6. package/dist/cjs/review/index.d.ts.map +1 -1
  7. package/dist/cjs/review/index.js +1 -0
  8. package/dist/cjs/review/index.js.map +1 -1
  9. package/dist/cjs/review/writeRecovery.d.ts +106 -0
  10. package/dist/cjs/review/writeRecovery.d.ts.map +1 -0
  11. package/dist/cjs/review/writeRecovery.js +385 -0
  12. package/dist/cjs/review/writeRecovery.js.map +1 -0
  13. package/dist/cjs/safeService.d.ts +16 -9
  14. package/dist/cjs/safeService.d.ts.map +1 -1
  15. package/dist/cjs/safeService.js +33 -26
  16. package/dist/cjs/safeService.js.map +1 -1
  17. package/dist/cjs/v6/sticky.d.ts +2 -0
  18. package/dist/cjs/v6/sticky.d.ts.map +1 -1
  19. package/dist/cjs/v6/sticky.js +43 -8
  20. package/dist/cjs/v6/sticky.js.map +1 -1
  21. package/dist/cjs/v6/suckers.d.ts +32 -0
  22. package/dist/cjs/v6/suckers.d.ts.map +1 -1
  23. package/dist/cjs/v6/suckers.js +70 -0
  24. package/dist/cjs/v6/suckers.js.map +1 -1
  25. package/dist/esm/review/contractWrite.d.ts +12 -1
  26. package/dist/esm/review/contractWrite.d.ts.map +1 -1
  27. package/dist/esm/review/contractWrite.js +26 -2
  28. package/dist/esm/review/contractWrite.js.map +1 -1
  29. package/dist/esm/review/index.d.ts +1 -0
  30. package/dist/esm/review/index.d.ts.map +1 -1
  31. package/dist/esm/review/index.js +1 -0
  32. package/dist/esm/review/index.js.map +1 -1
  33. package/dist/esm/review/writeRecovery.d.ts +106 -0
  34. package/dist/esm/review/writeRecovery.d.ts.map +1 -0
  35. package/dist/esm/review/writeRecovery.js +378 -0
  36. package/dist/esm/review/writeRecovery.js.map +1 -0
  37. package/dist/esm/safeService.d.ts +16 -9
  38. package/dist/esm/safeService.d.ts.map +1 -1
  39. package/dist/esm/safeService.js +33 -26
  40. package/dist/esm/safeService.js.map +1 -1
  41. package/dist/esm/v6/sticky.d.ts +2 -0
  42. package/dist/esm/v6/sticky.d.ts.map +1 -1
  43. package/dist/esm/v6/sticky.js +44 -9
  44. package/dist/esm/v6/sticky.js.map +1 -1
  45. package/dist/esm/v6/suckers.d.ts +32 -0
  46. package/dist/esm/v6/suckers.d.ts.map +1 -1
  47. package/dist/esm/v6/suckers.js +71 -2
  48. package/dist/esm/v6/suckers.js.map +1 -1
  49. package/package.json +1 -1
@@ -1,5 +1,12 @@
1
1
  import type { Address } from "viem";
2
2
  export type ReviewedWritePhase = "review" | "simulating" | "signing";
3
+ /** The wallet returned a hash, but the application's submission handoff failed. */
4
+ export declare class SubmittedContractWriteError<THash = unknown> extends Error {
5
+ readonly hash: THash;
6
+ readonly name = "SubmittedContractWriteError";
7
+ readonly cause: unknown;
8
+ constructor(hash: THash, cause: unknown);
9
+ }
3
10
  /**
4
11
  * Whether a wallet error proves that nothing was broadcast. Only an explicit
5
12
  * rejection does: EIP-1193 code 4001, or viem's `UserRejectedRequestError`,
@@ -26,6 +33,10 @@ export type ReviewedContractWriteOptions<TRequest extends {
26
33
  onBeforeWriteAborted?: () => unknown | Promise<unknown>;
27
34
  /** Clear that intent only when the wallet explicitly rejects the write. */
28
35
  onWriteRejected?: () => unknown | Promise<unknown>;
36
+ /** The invoked wallet may have broadcast without returning its identity. */
37
+ onWriteUncertain?: (error: unknown) => unknown | Promise<unknown>;
38
+ /** Persist the returned identity before following its receipt. */
39
+ onWriteSubmitted?: (hash: THash) => unknown | Promise<unknown>;
29
40
  write: (simulated: TSimulated) => Promise<THash>;
30
41
  onPhase?: (phase: ReviewedWritePhase) => void;
31
42
  /**
@@ -50,5 +61,5 @@ export type ReviewedContractWriteOptions<TRequest extends {
50
61
  */
51
62
  export declare function submitReviewedContractWrite<TRequest extends {
52
63
  chainId: number;
53
- }, TSimulated, THash>({ request, expectedAccount, review, switchChain, currentAccount, simulate, reverify, beforeWrite, beforeSend, onBeforeWriteAborted, onWriteRejected, write, onPhase, accountChangedError, guard, }: ReviewedContractWriteOptions<TRequest, TSimulated, THash>): Promise<THash>;
64
+ }, TSimulated, THash>({ request, expectedAccount, review, switchChain, currentAccount, simulate, reverify, beforeWrite, beforeSend, onBeforeWriteAborted, onWriteRejected, onWriteUncertain, onWriteSubmitted, write, onPhase, accountChangedError, guard, }: ReviewedContractWriteOptions<TRequest, TSimulated, THash>): Promise<THash>;
54
65
  //# sourceMappingURL=contractWrite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contractWrite.d.ts","sourceRoot":"","sources":["../../../src/review/contractWrite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcjE;AAED,MAAM,MAAM,4BAA4B,CACtC,QAAQ,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACpC,UAAU,EACV,KAAK,IACH;IACF,OAAO,EAAE,QAAQ,CAAC;IAClB,eAAe,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,cAAc,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACpC,UAAU,EACV,KAAK,EACL,EACA,OAAO,EACP,eAAe,EACf,MAAM,EACN,WAAW,EACX,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,KAAK,EACL,OAAO,EACP,mBAAoE,EACpE,KAAK,GACN,EAAE,4BAA4B,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CA0C5E"}
1
+ {"version":3,"file":"contractWrite.d.ts","sourceRoot":"","sources":["../../../src/review/contractWrite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAC;AAErE,mFAAmF;AACnF,qBAAa,2BAA2B,CAAC,KAAK,GAAG,OAAO,CAAE,SAAQ,KAAK;IAKnE,QAAQ,CAAC,IAAI,EAAE,KAAK;IAJtB,QAAQ,CAAC,IAAI,iCAAiC;IAC9C,SAAiB,KAAK,EAAE,OAAO,CAAC;gBAGrB,IAAI,EAAE,KAAK,EACpB,KAAK,EAAE,OAAO;CAOjB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBjE;AAED,MAAM,MAAM,4BAA4B,CACtC,QAAQ,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACpC,UAAU,EACV,KAAK,IACH;IACF,OAAO,EAAE,QAAQ,CAAC;IAClB,eAAe,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,cAAc,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACpC,UAAU,EACV,KAAK,EACL,EACA,OAAO,EACP,eAAe,EACf,MAAM,EACN,WAAW,EACX,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,EACL,OAAO,EACP,mBAAoE,EACpE,KAAK,GACN,EAAE,4BAA4B,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAoD5E"}
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SubmittedContractWriteError = void 0;
3
4
  exports.isDefiniteWalletRejection = isDefiniteWalletRejection;
4
5
  exports.submitReviewedContractWrite = submitReviewedContractWrite;
6
+ /** The wallet returned a hash, but the application's submission handoff failed. */
7
+ class SubmittedContractWriteError extends Error {
8
+ constructor(hash, cause) {
9
+ super("The wallet returned a transaction identity, but recording it failed. Keep this transaction pending.");
10
+ this.hash = hash;
11
+ this.name = "SubmittedContractWriteError";
12
+ Object.defineProperty(this, "cause", { value: cause, configurable: true });
13
+ }
14
+ }
15
+ exports.SubmittedContractWriteError = SubmittedContractWriteError;
5
16
  /**
6
17
  * Whether a wallet error proves that nothing was broadcast. Only an explicit
7
18
  * rejection does: EIP-1193 code 4001, or viem's `UserRejectedRequestError`,
@@ -13,6 +24,9 @@ function isDefiniteWalletRejection(error) {
13
24
  let current = error;
14
25
  for (let depth = 0; depth < 8 && current && typeof current === "object"; depth += 1) {
15
26
  const item = current;
27
+ if (item.name === "SubmittedContractWriteError" ||
28
+ item.name === "SubmittedWritePersistenceError")
29
+ return false;
16
30
  if (item.code === 4001 || item.name === "UserRejectedRequestError") {
17
31
  return true;
18
32
  }
@@ -29,7 +43,7 @@ function isDefiniteWalletRejection(error) {
29
43
  * before the review opens, after a chain switch and again immediately before
30
44
  * signing.
31
45
  */
32
- async function submitReviewedContractWrite({ request, expectedAccount, review, switchChain, currentAccount, simulate, reverify, beforeWrite, beforeSend, onBeforeWriteAborted, onWriteRejected, write, onPhase, accountChangedError = "The connected account changed. Review again.", guard, }) {
46
+ async function submitReviewedContractWrite({ request, expectedAccount, review, switchChain, currentAccount, simulate, reverify, beforeWrite, beforeSend, onBeforeWriteAborted, onWriteRejected, onWriteUncertain, onWriteSubmitted, write, onPhase, accountChangedError = "The connected account changed. Review again.", guard, }) {
33
47
  guard?.();
34
48
  if (!expectedAccount)
35
49
  throw new Error("Connect a wallet first.");
@@ -58,16 +72,28 @@ async function submitReviewedContractWrite({ request, expectedAccount, review, s
58
72
  throw error;
59
73
  }
60
74
  // No await or caller callback may separate the final synchronous gates from write.
75
+ let hash;
61
76
  try {
62
- return await write(simulated);
77
+ hash = await write(simulated);
63
78
  }
64
79
  catch (error) {
65
80
  // The same-looking error from review/simulation never reaches this catch.
66
81
  // An RPC timeout or ambiguous submission must preserve the recovery lock.
67
82
  if (isDefiniteWalletRejection(error))
68
83
  await onWriteRejected?.();
84
+ else
85
+ await onWriteUncertain?.(error);
69
86
  throw error;
70
87
  }
88
+ // A persistence failure after the wallet returned a hash is never a wallet
89
+ // rejection, even if a callback happens to throw a rejection-shaped error.
90
+ try {
91
+ await onWriteSubmitted?.(hash);
92
+ }
93
+ catch (cause) {
94
+ throw new SubmittedContractWriteError(hash, cause);
95
+ }
96
+ return hash;
71
97
  }
72
98
  function assertExpectedAccount(current, expected, message) {
73
99
  if (!current || current.toLowerCase() !== expected.toLowerCase()) {
@@ -1 +1 @@
1
- {"version":3,"file":"contractWrite.js","sourceRoot":"","sources":["../../../src/review/contractWrite.ts"],"names":[],"mappings":";;AAWA,8DAcC;AA6CD,kEA8DC;AAhID;;;;;;GAMG;AACH,SAAgB,yBAAyB,CAAC,KAAc;IACtD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KACE,IAAI,KAAK,GAAG,CAAC,EACb,KAAK,GAAG,CAAC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EACnD,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,IAAI,GAAG,OAA8D,CAAC;QAC5E,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B,EAAE,CAAC;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAoCD;;;;;;;;GAQG;AACI,KAAK,UAAU,2BAA2B,CAI/C,EACA,OAAO,EACP,eAAe,EACf,MAAM,EACN,WAAW,EACX,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,KAAK,EACL,OAAO,EACP,mBAAmB,GAAG,8CAA8C,EACpE,KAAK,GACqD;IAC1D,KAAK,EAAE,EAAE,CAAC;IACV,IAAI,CAAC,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACjE,qBAAqB,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAE9E,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC;IACpB,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IAEtB,OAAO,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,MAAM,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,qBAAqB,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAC9E,MAAM,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC;IAC1B,qBAAqB,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1C,qBAAqB,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAE9E,IAAI,WAAW;QAAE,MAAM,WAAW,EAAE,CAAC;IAErC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;QACrB,qBAAqB,CACnB,cAAc,EAAE,EAChB,eAAe,EACf,mBAAmB,CACpB,CAAC;QACF,UAAU,EAAE,EAAE,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,WAAW;YAAE,MAAM,oBAAoB,EAAE,EAAE,CAAC;QAChD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,mFAAmF;IACnF,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,yBAAyB,CAAC,KAAK,CAAC;YAAE,MAAM,eAAe,EAAE,EAAE,CAAC;QAChE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,OAA4B,EAC5B,QAAiB,EACjB,OAAe;IAEf,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"contractWrite.js","sourceRoot":"","sources":["../../../src/review/contractWrite.ts"],"names":[],"mappings":"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"}
@@ -4,4 +4,5 @@ export * from "./receipt.js";
4
4
  export * from "./simulation.js";
5
5
  export * from "./transactionReview.js";
6
6
  export * from "./transaction-message.js";
7
+ export * from "./writeRecovery.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/review/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/review/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
@@ -20,4 +20,5 @@ __exportStar(require("./receipt.js"), exports);
20
20
  __exportStar(require("./simulation.js"), exports);
21
21
  __exportStar(require("./transactionReview.js"), exports);
22
22
  __exportStar(require("./transaction-message.js"), exports);
23
+ __exportStar(require("./writeRecovery.js"), exports);
23
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/review/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,2CAAyB;AACzB,+CAA6B;AAC7B,kDAAgC;AAChC,yDAAuC;AACvC,2DAAyC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/review/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,2CAAyB;AACzB,+CAA6B;AAC7B,kDAAgC;AAChC,yDAAuC;AACvC,2DAAyC;AACzC,qDAAmC"}
@@ -0,0 +1,106 @@
1
+ import { type Address, type Hex, type PublicClient, type TransactionReceipt } from "viem";
2
+ import { type SafeServiceOptions } from "../safeService.js";
3
+ export type ReviewedWriteRecoveryInput = {
4
+ chainId: number;
5
+ account: Address;
6
+ safe: boolean;
7
+ call: {
8
+ to: Address;
9
+ data: Hex;
10
+ value?: bigint | string;
11
+ };
12
+ };
13
+ export type ReviewedWriteRecoveryRecord = Omit<ReviewedWriteRecoveryInput, "call"> & {
14
+ version: 1;
15
+ id: string;
16
+ call: {
17
+ to: Address;
18
+ data: Hex;
19
+ value: string;
20
+ };
21
+ /** The identity returned by the wallet, never an inferred historical match. */
22
+ hash?: Hex;
23
+ };
24
+ type RecoveryStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
25
+ type RecoveryLocks = Pick<LockManager, "request">;
26
+ /** The wallet returned this identity, but storing it failed. Keep it in memory and retry submitted(hash, record). */
27
+ export declare class SubmittedWritePersistenceError extends Error {
28
+ readonly name = "SubmittedWritePersistenceError";
29
+ readonly record: ReviewedWriteRecoveryRecord;
30
+ readonly cause: unknown;
31
+ constructor(record: ReviewedWriteRecoveryRecord, cause: unknown);
32
+ }
33
+ declare function recordOf(value: unknown): ReviewedWriteRecoveryRecord;
34
+ /** Validate an existing domain journal's recovered record with the shared storage rules. */
35
+ export { recordOf as parseReviewedWriteRecoveryRecord };
36
+ /** Exact reviewed evidence, independent of the reservation and submission identities. */
37
+ export declare function sameReviewedWrite(record: ReviewedWriteRecoveryRecord, input: ReviewedWriteRecoveryInput): boolean;
38
+ /**
39
+ * One ordinary-write reservation per account/chain/target/selector. Use the
40
+ * existing domain journal instead when an action already has one. There is no
41
+ * expiry or nonce-based release: an unknown broadcast remains unknown.
42
+ *
43
+ * Every mutation must run inside withLock, including receipt-based clearing.
44
+ * Storage and native cross-tab exclusion are mandatory before opening a wallet.
45
+ */
46
+ export declare function createBrowserWriteRecovery(input: ReviewedWriteRecoveryInput, options?: {
47
+ storage?: RecoveryStorage;
48
+ locks?: RecoveryLocks;
49
+ }): {
50
+ read: () => ReviewedWriteRecoveryRecord | null;
51
+ reserve(actualCall?: ReviewedWriteRecoveryInput["call"]): ReviewedWriteRecoveryRecord;
52
+ submitted(hash: Hex, expected?: ReviewedWriteRecoveryRecord | undefined): ReviewedWriteRecoveryRecord;
53
+ rejected: () => void;
54
+ clear: (expected: ReviewedWriteRecoveryRecord) => void;
55
+ withLock<T>(work: () => Promise<T>, wait?: boolean): Promise<T>;
56
+ };
57
+ /** Minimal RPC reads; PublicClient instances for any chain satisfy this shape. */
58
+ export type ReviewedWriteReceiptClient = {
59
+ getChainId(): Promise<number>;
60
+ getTransaction(args: {
61
+ hash: Hex;
62
+ }): Promise<{
63
+ hash: Hex;
64
+ chainId?: number;
65
+ from: Address;
66
+ to: Address | null;
67
+ input: Hex;
68
+ blockHash: Hex | null;
69
+ blockNumber: bigint | null;
70
+ transactionIndex: number | null;
71
+ }>;
72
+ getTransactionReceipt(args: {
73
+ hash: Hex;
74
+ }): Promise<TransactionReceipt>;
75
+ getBlock(args: {
76
+ blockNumber: bigint;
77
+ }): Promise<{
78
+ hash: Hex | null;
79
+ number: bigint | null;
80
+ }>;
81
+ getBlock(args: {
82
+ blockTag: "finalized";
83
+ }): Promise<{
84
+ number: bigint | null;
85
+ hash: Hex | null;
86
+ timestamp: bigint;
87
+ }>;
88
+ };
89
+ /**
90
+ * Authenticate the saved wallet identity, canonical inclusion and Safe effect.
91
+ * Ordinary wallet wrappers are allowed: the wallet's returned hash binds their
92
+ * execution. A wallet-returned Safe proposal hash authenticates its execution
93
+ * event even through a wrapper; an immediate execution hash additionally needs
94
+ * exact reviewed calldata. A failure releases only after finality.
95
+ * Throws on missing or inconsistent evidence; the caller retains its record.
96
+ */
97
+ export declare function verifyReviewedWriteReceipt(client: ReviewedWriteReceiptClient, saved: ReviewedWriteRecoveryRecord, receipt: TransactionReceipt, proof?: {
98
+ calls: readonly ReviewedWriteRecoveryInput["call"][];
99
+ batch?: boolean;
100
+ }): Promise<"success" | "failed">;
101
+ /**
102
+ * Prove a stamped Safe call expired before execution. An advanced nonce, stale
103
+ * service result, UI phase or local clock never releases a saved write.
104
+ */
105
+ export declare function verifyReviewedWriteExpiry(client: Pick<PublicClient, "getChainId" | "getBlock" | "request">, saved: ReviewedWriteRecoveryRecord, service?: SafeServiceOptions): Promise<true>;
106
+ //# sourceMappingURL=writeRecovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeRecovery.d.ts","sourceRoot":"","sources":["../../../src/review/writeRecovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SubmittedWritePersistenceError = void 0;
4
+ exports.parseReviewedWriteRecoveryRecord = recordOf;
5
+ exports.sameReviewedWrite = sameReviewedWrite;
6
+ exports.createBrowserWriteRecovery = createBrowserWriteRecovery;
7
+ exports.verifyReviewedWriteReceipt = verifyReviewedWriteReceipt;
8
+ exports.verifyReviewedWriteExpiry = verifyReviewedWriteExpiry;
9
+ const viem_1 = require("viem");
10
+ const safeService_js_1 = require("../safeService.js");
11
+ const untrusted_js_1 = require("../untrusted.js");
12
+ const relayr_js_1 = require("./relayr.js");
13
+ // A returned hash must survive a component remount even when its durable update
14
+ // failed. It is visible only while the exact durable reservation still exists.
15
+ const unpersistedSubmissions = new WeakMap();
16
+ /** The wallet returned this identity, but storing it failed. Keep it in memory and retry submitted(hash, record). */
17
+ class SubmittedWritePersistenceError extends Error {
18
+ constructor(record, cause) {
19
+ super("The wallet returned a transaction identity, but recovery storage is unavailable. Keep this transaction pending.");
20
+ this.name = "SubmittedWritePersistenceError";
21
+ this.record = recordOf(record);
22
+ Object.defineProperty(this, "cause", { value: cause, configurable: true });
23
+ }
24
+ }
25
+ exports.SubmittedWritePersistenceError = SubmittedWritePersistenceError;
26
+ function normalized(input) {
27
+ const value = (0, untrusted_js_1.uint256)(input?.call?.value ?? 0n);
28
+ if (!Number.isSafeInteger(input?.chainId) ||
29
+ input.chainId <= 0 ||
30
+ !(0, viem_1.isAddress)(input?.account, { strict: false }) ||
31
+ typeof input?.safe !== "boolean" ||
32
+ !(0, viem_1.isAddress)(input?.call?.to, { strict: false }) ||
33
+ !(0, untrusted_js_1.isHexBytes)(input?.call?.data) ||
34
+ value === null)
35
+ throw new Error("Invalid reviewed write recovery evidence.");
36
+ return {
37
+ chainId: input.chainId,
38
+ account: input.account.toLowerCase(),
39
+ safe: input.safe,
40
+ call: {
41
+ to: input.call.to.toLowerCase(),
42
+ data: input.call.data.toLowerCase(),
43
+ value: value.toString(),
44
+ },
45
+ };
46
+ }
47
+ function recordOf(value) {
48
+ const record = value;
49
+ if (record?.version !== 1 ||
50
+ typeof record.id !== "string" ||
51
+ !record.id ||
52
+ typeof record.call?.value !== "string" ||
53
+ !/^(0|[1-9]\d*)$/.test(record.call.value) ||
54
+ (record.hash !== undefined && !(0, untrusted_js_1.isBytes32)(record.hash)))
55
+ throw new Error("Invalid saved write recovery record. Keep this action pending.");
56
+ return {
57
+ version: 1,
58
+ id: record.id,
59
+ ...normalized(record),
60
+ ...(record.hash === undefined
61
+ ? {}
62
+ : { hash: record.hash.toLowerCase() }),
63
+ };
64
+ }
65
+ /** Amounts and quote changes must not evade an unresolved action. */
66
+ function scope(input) {
67
+ return `nana-sdk:reviewed-write:v1:${input.chainId}:${input.account}:${input.call.to}:${input.call.data.slice(0, 10)}`;
68
+ }
69
+ /** Exact reviewed evidence, independent of the reservation and submission identities. */
70
+ function sameReviewedWrite(record, input) {
71
+ return (JSON.stringify(normalized(record)) === JSON.stringify(normalized(input)));
72
+ }
73
+ /**
74
+ * One ordinary-write reservation per account/chain/target/selector. Use the
75
+ * existing domain journal instead when an action already has one. There is no
76
+ * expiry or nonce-based release: an unknown broadcast remains unknown.
77
+ *
78
+ * Every mutation must run inside withLock, including receipt-based clearing.
79
+ * Storage and native cross-tab exclusion are mandatory before opening a wallet.
80
+ */
81
+ function createBrowserWriteRecovery(input, options = {}) {
82
+ const reviewed = normalized(input);
83
+ const key = scope(reviewed);
84
+ let owned;
85
+ let locked = false;
86
+ let lastStorage;
87
+ function storage() {
88
+ const store = options.storage ?? globalThis.localStorage;
89
+ if (!store)
90
+ throw new Error("Write recovery storage is unavailable.");
91
+ lastStorage = store;
92
+ return store;
93
+ }
94
+ function rememberSubmission(record, verifiedReservation = false) {
95
+ if (!lastStorage)
96
+ return;
97
+ let pending = unpersistedSubmissions.get(lastStorage);
98
+ const previous = pending?.get(key);
99
+ // An unreadable stale callback cannot replace another attempt's only hash.
100
+ if (previous &&
101
+ !verifiedReservation &&
102
+ JSON.stringify(previous) !== JSON.stringify(record))
103
+ return;
104
+ if (!pending)
105
+ unpersistedSubmissions.set(lastStorage, (pending = new Map()));
106
+ pending.set(key, recordOf(record));
107
+ }
108
+ function requireLock() {
109
+ if (!locked)
110
+ throw new Error("Write recovery changes require the browser lock.");
111
+ }
112
+ function readStored(raw) {
113
+ if (raw === null)
114
+ return null;
115
+ const record = recordOf(JSON.parse(raw));
116
+ if (scope(record) !== key)
117
+ throw new Error("Saved write recovery scope does not match.");
118
+ return record;
119
+ }
120
+ function read() {
121
+ const store = storage();
122
+ const record = readStored(store.getItem(key));
123
+ const memory = unpersistedSubmissions.get(store)?.get(key);
124
+ if (!record ||
125
+ !memory ||
126
+ JSON.stringify({ ...record, hash: undefined }) !==
127
+ JSON.stringify({ ...memory, hash: undefined }) ||
128
+ (record.hash && record.hash !== memory.hash)) {
129
+ // A readable durable snapshot also proves whether cached evidence is stale.
130
+ unpersistedSubmissions.get(store)?.delete(key);
131
+ return record;
132
+ }
133
+ if (record.hash)
134
+ unpersistedSubmissions.get(store)?.delete(key);
135
+ return recordOf(memory);
136
+ }
137
+ function persist(record) {
138
+ const raw = JSON.stringify(record);
139
+ const store = storage();
140
+ store.setItem(key, raw);
141
+ if (store.getItem(key) !== raw)
142
+ throw new Error("Write recovery storage did not retain the action.");
143
+ }
144
+ function clear(expected) {
145
+ requireLock();
146
+ // A volatile returned hash is useful for tracking, but is not a successful
147
+ // durable commit. submitted(hash, record) must retry that commit first.
148
+ const current = readStored(storage().getItem(key));
149
+ if (!current)
150
+ return;
151
+ if (JSON.stringify(current) !== JSON.stringify(recordOf(expected))) {
152
+ throw new Error("Write recovery changed. Keep the current action pending.");
153
+ }
154
+ const store = storage();
155
+ store.removeItem(key);
156
+ if (store.getItem(key) !== null)
157
+ throw new Error("Write recovery storage did not clear the action.");
158
+ unpersistedSubmissions.get(store)?.delete(key);
159
+ }
160
+ function rejected() {
161
+ requireLock();
162
+ // A rejection callback from another attempt must never release this one.
163
+ if (!owned || owned.hash)
164
+ return;
165
+ const current = read();
166
+ if (current?.id === owned.id && current.hash)
167
+ return;
168
+ clear(owned);
169
+ owned = undefined;
170
+ }
171
+ return {
172
+ read,
173
+ reserve(actualCall) {
174
+ requireLock();
175
+ // A reused Safe proposal may have a different, already verified deadline.
176
+ // Retain its actual call, without widening the unresolved-action scope.
177
+ const actual = actualCall
178
+ ? normalized({ ...reviewed, call: actualCall })
179
+ : reviewed;
180
+ if (scope(actual) !== key)
181
+ throw new Error("The adopted write has a different recovery scope.");
182
+ if (read())
183
+ throw new Error("An earlier wallet write is still unresolved. Check it before trying again.");
184
+ const record = {
185
+ version: 1,
186
+ id: globalThis.crypto.randomUUID(),
187
+ ...actual,
188
+ };
189
+ owned = recordOf(record);
190
+ try {
191
+ persist(record);
192
+ }
193
+ catch (error) {
194
+ // The wallet has not been invoked. A failed readback may still have
195
+ // stored this marker; clean only our exact reservation if readable.
196
+ try {
197
+ rejected();
198
+ }
199
+ catch {
200
+ /* Retain ownership for a later cleanup. */
201
+ }
202
+ throw error;
203
+ }
204
+ return recordOf(record);
205
+ },
206
+ submitted(hash, expected = owned) {
207
+ requireLock();
208
+ if (!(0, untrusted_js_1.isBytes32)(hash))
209
+ throw new Error("The wallet returned an invalid transaction identity.");
210
+ const previous = expected && recordOf(expected);
211
+ const submittedHash = hash.toLowerCase();
212
+ if (!previous ||
213
+ scope(previous) !== key ||
214
+ (previous.hash && previous.hash !== submittedHash)) {
215
+ throw new Error("Write recovery changed before submission was recorded.");
216
+ }
217
+ const submitted = { ...previous, hash: submittedHash };
218
+ let raw;
219
+ try {
220
+ raw = storage().getItem(key);
221
+ }
222
+ catch (cause) {
223
+ owned = submitted;
224
+ rememberSubmission(submitted);
225
+ throw new SubmittedWritePersistenceError(submitted, cause);
226
+ }
227
+ const current = readStored(raw);
228
+ if (!current ||
229
+ JSON.stringify({ ...current, hash: undefined }) !==
230
+ JSON.stringify({ ...previous, hash: undefined }) ||
231
+ (current.hash && current.hash !== submittedHash)) {
232
+ throw new Error("Write recovery changed before submission was recorded.");
233
+ }
234
+ owned = recordOf(submitted);
235
+ if (!current.hash) {
236
+ try {
237
+ persist(submitted);
238
+ }
239
+ catch (cause) {
240
+ rememberSubmission(submitted, true);
241
+ throw new SubmittedWritePersistenceError(submitted, cause);
242
+ }
243
+ }
244
+ if (lastStorage)
245
+ unpersistedSubmissions.get(lastStorage)?.delete(key);
246
+ return submitted;
247
+ },
248
+ rejected,
249
+ clear,
250
+ async withLock(work, wait = false) {
251
+ const locks = options.locks ?? globalThis.navigator?.locks;
252
+ if (!locks)
253
+ throw new Error("This browser cannot safely coordinate wallet writes.");
254
+ return locks.request(key, { mode: "exclusive", ...(!wait && { ifAvailable: true }) }, async (lock) => {
255
+ if (!lock)
256
+ throw new Error("This wallet action is already open in another tab.");
257
+ locked = true;
258
+ try {
259
+ return await work();
260
+ }
261
+ finally {
262
+ locked = false;
263
+ }
264
+ });
265
+ },
266
+ };
267
+ }
268
+ /**
269
+ * Authenticate the saved wallet identity, canonical inclusion and Safe effect.
270
+ * Ordinary wallet wrappers are allowed: the wallet's returned hash binds their
271
+ * execution. A wallet-returned Safe proposal hash authenticates its execution
272
+ * event even through a wrapper; an immediate execution hash additionally needs
273
+ * exact reviewed calldata. A failure releases only after finality.
274
+ * Throws on missing or inconsistent evidence; the caller retains its record.
275
+ */
276
+ async function verifyReviewedWriteReceipt(client, saved, receipt, proof) {
277
+ const record = recordOf(saved);
278
+ const pending = () => new Error("The saved wallet write is not yet proven. Keep this action pending.");
279
+ let calls = [record.call];
280
+ if (proof) {
281
+ if (!record.safe ||
282
+ !Array.isArray(proof.calls) ||
283
+ !proof.calls.length ||
284
+ (proof.batch !== undefined && typeof proof.batch !== "boolean"))
285
+ throw pending();
286
+ calls = proof.calls.map((call) => normalized({ ...record, call }).call);
287
+ if (JSON.stringify(calls[0]) !== JSON.stringify(record.call))
288
+ throw pending();
289
+ }
290
+ if (!record.hash ||
291
+ !(0, untrusted_js_1.isBytes32)(receipt?.transactionHash) ||
292
+ (!record.safe && record.hash !== receipt.transactionHash.toLowerCase()))
293
+ throw pending();
294
+ const hash = receipt.transactionHash;
295
+ const [chainId, transaction, canonical] = await Promise.all([
296
+ client.getChainId(),
297
+ client.getTransaction({ hash }),
298
+ client.getTransactionReceipt({ hash }),
299
+ ]);
300
+ if (chainId !== record.chainId ||
301
+ (transaction.chainId !== undefined &&
302
+ transaction.chainId !== record.chainId) ||
303
+ transaction.hash?.toLowerCase() !== hash.toLowerCase() ||
304
+ canonical.transactionHash?.toLowerCase() !== hash.toLowerCase() ||
305
+ !(0, untrusted_js_1.isBytes32)(canonical.blockHash) ||
306
+ canonical.blockHash.toLowerCase() !== receipt.blockHash?.toLowerCase() ||
307
+ canonical.blockHash.toLowerCase() !==
308
+ transaction.blockHash?.toLowerCase() ||
309
+ typeof canonical.blockNumber !== "bigint" ||
310
+ canonical.blockNumber < 0n ||
311
+ canonical.blockNumber !== receipt.blockNumber ||
312
+ canonical.blockNumber !== transaction.blockNumber ||
313
+ !(0, viem_1.isAddress)(transaction.from, { strict: false }) ||
314
+ transaction.from.toLowerCase() !== canonical.from?.toLowerCase() ||
315
+ transaction.to?.toLowerCase() !== canonical.to?.toLowerCase() ||
316
+ !Number.isSafeInteger(transaction.transactionIndex) ||
317
+ transaction.transactionIndex < 0 ||
318
+ transaction.transactionIndex !== canonical.transactionIndex ||
319
+ canonical.status !== receipt.status ||
320
+ (canonical.status !== "success" && canonical.status !== "reverted"))
321
+ throw pending();
322
+ let status = canonical.status === "success" ? "success" : "failed";
323
+ if (record.safe) {
324
+ if (!Array.isArray(canonical.logs) ||
325
+ canonical.logs.some((log) => log.removed !== false ||
326
+ log.transactionHash?.toLowerCase() !== hash.toLowerCase() ||
327
+ log.blockHash?.toLowerCase() !== canonical.blockHash.toLowerCase() ||
328
+ log.blockNumber !== canonical.blockNumber ||
329
+ log.transactionIndex !== canonical.transactionIndex))
330
+ throw pending();
331
+ const result = await (0, safeService_js_1.readSafeAppExecution)({
332
+ client: { getTransaction: async () => transaction },
333
+ receipt: canonical,
334
+ safe: record.account,
335
+ proposalHash: record.hash,
336
+ calls,
337
+ batch: proof?.batch,
338
+ });
339
+ // A reverted outer executor has not consumed the Safe proposal's nonce.
340
+ if (result.status !== "success" && result.status !== "failed")
341
+ throw pending();
342
+ status = result.status;
343
+ }
344
+ const isCanonical = async () => {
345
+ const block = await client.getBlock({ blockNumber: canonical.blockNumber });
346
+ return (block.number === canonical.blockNumber &&
347
+ block.hash?.toLowerCase() === canonical.blockHash.toLowerCase());
348
+ };
349
+ if (status === "failed") {
350
+ const finalized = await (0, relayr_js_1.atCanonicalFinalizedBlock)(client, async (number) => number >= canonical.blockNumber && (await isCanonical()));
351
+ if (!finalized?.value)
352
+ throw pending();
353
+ }
354
+ else if (!(await isCanonical()))
355
+ throw pending();
356
+ return status;
357
+ }
358
+ /**
359
+ * Prove a stamped Safe call expired before execution. An advanced nonce, stale
360
+ * service result, UI phase or local clock never releases a saved write.
361
+ */
362
+ async function verifyReviewedWriteExpiry(client, saved, service) {
363
+ const record = recordOf(saved);
364
+ const pending = () => new Error("The saved Safe proposal is not proven expired. Keep this action pending.");
365
+ if (!record.safe ||
366
+ !record.hash ||
367
+ (await client.getChainId()) !== record.chainId)
368
+ throw pending();
369
+ const proposal = await (0, safeService_js_1.readSafeTransaction)(record.chainId, record.account, record.hash, service);
370
+ if (!(0, safeService_js_1.safeTransactionMatchesCall)(proposal, {
371
+ ...record.call,
372
+ value: BigInt(record.call.value),
373
+ }))
374
+ throw pending();
375
+ const proof = await (0, relayr_js_1.atCanonicalFinalizedBlock)(client, async (blockNumber) => {
376
+ const block = await client.getBlock({ blockNumber });
377
+ if (block.number !== blockNumber)
378
+ return false;
379
+ return ((await (0, safeService_js_1.lookAtSafeProposal)({ request: client.request, getBlock: async () => block }, record.chainId, record.account, record.hash, service)) === "expired");
380
+ });
381
+ if (!proof?.value)
382
+ throw pending();
383
+ return true;
384
+ }
385
+ //# sourceMappingURL=writeRecovery.js.map