@bananapus/nana-sdk-core 2.33.2 → 2.34.1

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.
@@ -57,5 +57,7 @@ export declare const WRITE_HOLD_SAFE_REPLACED_LINE = "Another Safe transaction u
57
57
  export declare const WRITE_HOLD_LOOKS_DONE_LINE = "This looks done. Check your wallet, then continue.";
58
58
  /** A held Safe write whose follower ended without proof. The exit is offered. */
59
59
  export declare const WRITE_HOLD_SAFE_UNPROVEN_LINE = "This Safe transaction couldn't be confirmed here. Check your Safe, then send again.";
60
+ /** A held write whose transaction is mined but whose result can't be read yet. No exit yet. */
61
+ export declare const WRITE_HOLD_MINED_LINE = "Your transaction is onchain, but its result can't be read yet.";
60
62
  export {};
61
63
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/review/messages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,KAAK,aAAa,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,EACE,MAAiB,EACjB,KAAkB,EAClB,UAA0D,GAC3D,GAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC,GACL,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,EACE,MAAiB,EACjB,OAAmB,EACnB,gBAAwB,GACzB,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GACxE,MAAM,CAOR;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,MAAM,SAAgB,GAAG,MAAM,CAE/D;AAED,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,oCAAoC,CAAC;AAElE,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,EACjC,MAAM,SAAgB,GACrB,MAAM,CAIR;AAED,iEAAiE;AACjE,eAAO,MAAM,2BAA2B,0EACiC,CAAC;AAE1E,gDAAgD;AAChD,eAAO,MAAM,2BAA2B,oCAAoC,CAAC;AAE7E,yEAAyE;AACzE,eAAO,MAAM,4BAA4B,2EACiC,CAAC;AAE3E,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,8FAC0D,CAAC;AAE9F,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,kEAC6B,CAAC;AAElE,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,yEACmC,CAAC;AAEzE,gGAAgG;AAChG,eAAO,MAAM,yBAAyB,uFACgD,CAAC;AAEvF,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,6FAC0D,CAAC;AAE7F,+DAA+D;AAC/D,eAAO,MAAM,2BAA2B,iFACwC,CAAC;AAEjF,wFAAwF;AACxF,eAAO,MAAM,6BAA6B,gFACqC,CAAC;AAEhF,6GAA6G;AAC7G,eAAO,MAAM,0BAA0B,uDACe,CAAC;AAEvD,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,wFAC6C,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/review/messages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,KAAK,aAAa,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,EACE,MAAiB,EACjB,KAAkB,EAClB,UAA0D,GAC3D,GAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC,GACL,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,EACE,MAAiB,EACjB,OAAmB,EACnB,gBAAwB,GACzB,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GACxE,MAAM,CAOR;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,MAAM,SAAgB,GAAG,MAAM,CAE/D;AAED,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,oCAAoC,CAAC;AAElE,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,EACjC,MAAM,SAAgB,GACrB,MAAM,CAIR;AAED,iEAAiE;AACjE,eAAO,MAAM,2BAA2B,0EACiC,CAAC;AAE1E,gDAAgD;AAChD,eAAO,MAAM,2BAA2B,oCAAoC,CAAC;AAE7E,yEAAyE;AACzE,eAAO,MAAM,4BAA4B,2EACiC,CAAC;AAE3E,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,8FAC0D,CAAC;AAE9F,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,kEAC6B,CAAC;AAElE,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,yEACmC,CAAC;AAEzE,gGAAgG;AAChG,eAAO,MAAM,yBAAyB,uFACgD,CAAC;AAEvF,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,6FAC0D,CAAC;AAE7F,+DAA+D;AAC/D,eAAO,MAAM,2BAA2B,iFACwC,CAAC;AAEjF,wFAAwF;AACxF,eAAO,MAAM,6BAA6B,gFACqC,CAAC;AAEhF,6GAA6G;AAC7G,eAAO,MAAM,0BAA0B,uDACe,CAAC;AAEvD,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,wFAC6C,CAAC;AAExF,+FAA+F;AAC/F,eAAO,MAAM,qBAAqB,mEACgC,CAAC"}
@@ -5,7 +5,7 @@
5
5
  * `action` is a noun such as "action", "launch" or "payment".
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.WRITE_HOLD_SAFE_UNPROVEN_LINE = exports.WRITE_HOLD_LOOKS_DONE_LINE = exports.WRITE_HOLD_SAFE_REPLACED_LINE = exports.WRITE_HOLD_HASHED_EXIT_LINE = exports.WRITE_HOLD_NONCE_LINE = exports.WRITE_HOLD_UNCHECKED_LINE = exports.WRITE_HOLD_REVERTED_LINE = exports.WRITE_HOLD_PENDING_LINE = exports.WRITE_HOLD_IN_TAB_LINE = exports.RELAYR_PAYMENT_RELEASED_LINE = exports.SAFE_PROPOSAL_EXECUTED_LINE = exports.SAFE_PROPOSAL_REPLACED_LINE = exports.WRITE_EXIT_LABEL = void 0;
8
+ exports.WRITE_HOLD_MINED_LINE = exports.WRITE_HOLD_SAFE_UNPROVEN_LINE = exports.WRITE_HOLD_LOOKS_DONE_LINE = exports.WRITE_HOLD_SAFE_REPLACED_LINE = exports.WRITE_HOLD_HASHED_EXIT_LINE = exports.WRITE_HOLD_NONCE_LINE = exports.WRITE_HOLD_UNCHECKED_LINE = exports.WRITE_HOLD_REVERTED_LINE = exports.WRITE_HOLD_PENDING_LINE = exports.WRITE_HOLD_IN_TAB_LINE = exports.RELAYR_PAYMENT_RELEASED_LINE = exports.SAFE_PROPOSAL_EXECUTED_LINE = exports.SAFE_PROPOSAL_REPLACED_LINE = exports.WRITE_EXIT_LABEL = void 0;
9
9
  exports.relayrHeldMessage = relayrHeldMessage;
10
10
  exports.relayrDiscardLine = relayrDiscardLine;
11
11
  exports.writeHeldMessage = writeHeldMessage;
@@ -70,4 +70,6 @@ exports.WRITE_HOLD_SAFE_REPLACED_LINE = "Another Safe transaction used this nonc
70
70
  exports.WRITE_HOLD_LOOKS_DONE_LINE = "This looks done. Check your wallet, then continue.";
71
71
  /** A held Safe write whose follower ended without proof. The exit is offered. */
72
72
  exports.WRITE_HOLD_SAFE_UNPROVEN_LINE = "This Safe transaction couldn't be confirmed here. Check your Safe, then send again.";
73
+ /** A held write whose transaction is mined but whose result can't be read yet. No exit yet. */
74
+ exports.WRITE_HOLD_MINED_LINE = "Your transaction is onchain, but its result can't be read yet.";
73
75
  //# sourceMappingURL=messages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/review/messages.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAUH,8CAeC;AAOD,8CAcC;AAGD,4CAEC;AAMD,4CAOC;AA3DD;;;;GAIG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,EACE,MAAM,GAAG,QAAQ,EACjB,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EAClB,UAAU,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,cAAc,EAAE,MAKxD,EAAE;IAEN,OAAO,KAAK,GAAG,KAAK,GAAG,KAAK;QAC1B,CAAC,CAAC,QAAQ,MAAM,4CAA4C,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,yBAAyB;QAC9G,CAAC,CAAC,QAAQ,MAAM,iEAAiE,CAAC;AACtF,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAC/B,MAAqB,EACrB,EACE,MAAM,GAAG,QAAQ,EACjB,OAAO,GAAG,SAAS,EACnB,gBAAgB,GAAG,KAAK,MAC6C,EAAE;IAEzE,IAAI,MAAM,KAAK,KAAK;QAClB,OAAO,QAAQ,MAAM,wDAAwD,OAAO,uCAAuC,CAAC;IAC9H,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,OAAO,OAAO,6BAA6B,CAAC;IAC7E,OAAO,gBAAgB;QACrB,CAAC,CAAC,iDAAiD,MAAM,qDAAqD;QAC9G,CAAC,CAAC,QAAQ,MAAM,gDAAgD,CAAC;AACrE,CAAC;AAED,wEAAwE;AACxE,SAAgB,gBAAgB,CAAC,MAAM,GAAG,aAAa;IACrD,OAAO,gBAAgB,MAAM,2CAA2C,CAAC;AAC3E,CAAC;AAED,4EAA4E;AAC/D,QAAA,gBAAgB,GAAG,iCAAiC,CAAC;AAElE,8DAA8D;AAC9D,SAAgB,gBAAgB,CAC9B,IAAiC,EACjC,MAAM,GAAG,aAAa;IAEtB,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE;QACjC,CAAC,CAAC,mBAAmB,MAAM,0CAA0C;QACrE,CAAC,CAAC,mBAAmB,MAAM,6CAA6C,CAAC;AAC7E,CAAC;AAED,iEAAiE;AACpD,QAAA,2BAA2B,GACtC,uEAAuE,CAAC;AAE1E,gDAAgD;AACnC,QAAA,2BAA2B,GAAG,iCAAiC,CAAC;AAE7E,yEAAyE;AAC5D,QAAA,4BAA4B,GACvC,wEAAwE,CAAC;AAE3E,sFAAsF;AACzE,QAAA,sBAAsB,GACjC,2FAA2F,CAAC;AAE9F,mFAAmF;AACtE,QAAA,uBAAuB,GAClC,+DAA+D,CAAC;AAElE,0FAA0F;AAC7E,QAAA,wBAAwB,GACnC,sEAAsE,CAAC;AAEzE,gGAAgG;AACnF,QAAA,yBAAyB,GACpC,oFAAoF,CAAC;AAEvF,qFAAqF;AACxE,QAAA,qBAAqB,GAChC,0FAA0F,CAAC;AAE7F,+DAA+D;AAClD,QAAA,2BAA2B,GACtC,8EAA8E,CAAC;AAEjF,wFAAwF;AAC3E,QAAA,6BAA6B,GACxC,6EAA6E,CAAC;AAEhF,6GAA6G;AAChG,QAAA,0BAA0B,GACrC,oDAAoD,CAAC;AAEvD,iFAAiF;AACpE,QAAA,6BAA6B,GACxC,qFAAqF,CAAC"}
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/review/messages.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAUH,8CAeC;AAOD,8CAcC;AAGD,4CAEC;AAMD,4CAOC;AA3DD;;;;GAIG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,EACE,MAAM,GAAG,QAAQ,EACjB,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EAClB,UAAU,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,cAAc,EAAE,MAKxD,EAAE;IAEN,OAAO,KAAK,GAAG,KAAK,GAAG,KAAK;QAC1B,CAAC,CAAC,QAAQ,MAAM,4CAA4C,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,yBAAyB;QAC9G,CAAC,CAAC,QAAQ,MAAM,iEAAiE,CAAC;AACtF,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAC/B,MAAqB,EACrB,EACE,MAAM,GAAG,QAAQ,EACjB,OAAO,GAAG,SAAS,EACnB,gBAAgB,GAAG,KAAK,MAC6C,EAAE;IAEzE,IAAI,MAAM,KAAK,KAAK;QAClB,OAAO,QAAQ,MAAM,wDAAwD,OAAO,uCAAuC,CAAC;IAC9H,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,OAAO,OAAO,6BAA6B,CAAC;IAC7E,OAAO,gBAAgB;QACrB,CAAC,CAAC,iDAAiD,MAAM,qDAAqD;QAC9G,CAAC,CAAC,QAAQ,MAAM,gDAAgD,CAAC;AACrE,CAAC;AAED,wEAAwE;AACxE,SAAgB,gBAAgB,CAAC,MAAM,GAAG,aAAa;IACrD,OAAO,gBAAgB,MAAM,2CAA2C,CAAC;AAC3E,CAAC;AAED,4EAA4E;AAC/D,QAAA,gBAAgB,GAAG,iCAAiC,CAAC;AAElE,8DAA8D;AAC9D,SAAgB,gBAAgB,CAC9B,IAAiC,EACjC,MAAM,GAAG,aAAa;IAEtB,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE;QACjC,CAAC,CAAC,mBAAmB,MAAM,0CAA0C;QACrE,CAAC,CAAC,mBAAmB,MAAM,6CAA6C,CAAC;AAC7E,CAAC;AAED,iEAAiE;AACpD,QAAA,2BAA2B,GACtC,uEAAuE,CAAC;AAE1E,gDAAgD;AACnC,QAAA,2BAA2B,GAAG,iCAAiC,CAAC;AAE7E,yEAAyE;AAC5D,QAAA,4BAA4B,GACvC,wEAAwE,CAAC;AAE3E,sFAAsF;AACzE,QAAA,sBAAsB,GACjC,2FAA2F,CAAC;AAE9F,mFAAmF;AACtE,QAAA,uBAAuB,GAClC,+DAA+D,CAAC;AAElE,0FAA0F;AAC7E,QAAA,wBAAwB,GACnC,sEAAsE,CAAC;AAEzE,gGAAgG;AACnF,QAAA,yBAAyB,GACpC,oFAAoF,CAAC;AAEvF,qFAAqF;AACxE,QAAA,qBAAqB,GAChC,0FAA0F,CAAC;AAE7F,+DAA+D;AAClD,QAAA,2BAA2B,GACtC,8EAA8E,CAAC;AAEjF,wFAAwF;AAC3E,QAAA,6BAA6B,GACxC,6EAA6E,CAAC;AAEhF,6GAA6G;AAChG,QAAA,0BAA0B,GACrC,oDAAoD,CAAC;AAEvD,iFAAiF;AACpE,QAAA,6BAA6B,GACxC,qFAAqF,CAAC;AAExF,+FAA+F;AAClF,QAAA,qBAAqB,GAChC,gEAAgE,CAAC"}
@@ -24,7 +24,15 @@ type Held<S extends string, Extra> = {
24
24
  /**
25
25
  * What a write hold says about one exact call, from {@link WriteHold.state}:
26
26
  *
27
- * - `clear`: nothing is held; `settled` says how a hold just ended;
27
+ * "The exit is offered" means through the gate: `exit` is true only on a
28
+ * state from state() or exit(). peek(), replays and every other emitted state
29
+ * keep the status and line with `exit: false`.
30
+ *
31
+ * - `clear`: nothing is held; `settled` says how the call's last write
32
+ * ended on authenticated evidence (its receipt or canonical execution),
33
+ * saved for every reader until the next write on the call is reserved. A
34
+ * proven expiry says so only to the caller that saw it; the user's exit
35
+ * leaves none;
28
36
  * - `pending-known-hash`: its saved hash (or Safe proposal) can still run. No
29
37
  * exit: the wallet speeds it up or cancels it;
30
38
  * - `held-no-hash`: the wallet never returned a hash. The exit is offered;
@@ -63,27 +71,27 @@ export type WriteHoldState = {
63
71
  hash: Hex;
64
72
  exit: false;
65
73
  }> | Held<"held-no-hash", {
66
- exit: true;
74
+ exit: boolean;
67
75
  }> | Held<"dropped-proven", {
68
76
  hash: Hex;
69
77
  usedBy: Hex;
70
- exit: true;
78
+ exit: boolean;
71
79
  warning: string;
72
80
  }> | Held<"held-unchecked", {
73
81
  hash: Hex;
74
- exit: true;
82
+ exit: boolean;
75
83
  warning: string;
76
84
  }> | Held<"safe-replaced", {
77
85
  hash: Hex;
78
- exit: true;
86
+ exit: boolean;
79
87
  warning: string;
80
88
  }> | Held<"safe-unproven", {
81
89
  hash: Hex;
82
- exit: true;
90
+ exit: boolean;
83
91
  warning: string;
84
92
  }> | Held<"looks-done", {
85
93
  hash?: Hex;
86
- exit: true;
94
+ exit: boolean;
87
95
  warning?: string;
88
96
  }> | Held<"ran-executed", {
89
97
  executionHash?: Hex;
@@ -159,7 +167,10 @@ export declare function createWriteHold(options?: {
159
167
  }) => Promise<WriteHoldState>;
160
168
  /** What the saved record alone says, without asking the chain. */
161
169
  peek: () => WriteHoldState;
162
- /** Every state this controller reads or makes for the call, and other tabs' changes. */
170
+ /**
171
+ * Every state this controller reads or makes for the call, and other
172
+ * tabs' changes. The latest one, if any, is replayed at once.
173
+ */
163
174
  subscribe(listener: (state: WriteHoldState) => void): () => void;
164
175
  /**
165
176
  * Send the call under its hold. A held call throws
@@ -195,8 +206,18 @@ export declare function createWriteHold(options?: {
195
206
  */
196
207
  onUnproven(client: WriteHoldClient, proposalHash?: Hex): Promise<WriteHoldState>;
197
208
  /**
198
- * The app's own proof that the held Safe proposal ran, with its
199
- * execution's transaction when known. Kept until its result settles it.
209
+ * Attach a hash the chain authenticates as this exact call, sent by the
210
+ * account on the reservation's chain at or after its next nonce, and
211
+ * not mined before it was reserved, to the held write that has none: under
212
+ * one lock, without releasing it. The hold then follows that hash: no
213
+ * exit while it is pending, and its receipt settles it.
214
+ */
215
+ attachHash(client: WriteHoldClient, hash: Hex): Promise<WriteHoldState>;
216
+ /**
217
+ * The app's word that the held Safe proposal ran, with its execution's
218
+ * transaction when known. Nothing is saved as executed on its word:
219
+ * the hash is kept as a candidate, and settles it only once its own
220
+ * receipt proves this Safe's execution of the exact proposal or call.
200
221
  */
201
222
  onExecuted(client: WriteHoldClient, executionHash?: Hex): Promise<WriteHoldState>;
202
223
  };
@@ -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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"}
@@ -6,7 +6,18 @@ const viem_1 = require("viem");
6
6
  const safeService_js_1 = require("../safeService.js");
7
7
  const messages_js_1 = require("./messages.js");
8
8
  const writeRecovery_js_1 = require("./writeRecovery.js");
9
+ const untrusted_js_1 = require("../untrusted.js");
9
10
  const relayr_js_1 = require("./relayr.js");
11
+ /**
12
+ * A state read without the chain (peek, a replay, another tab's change, a
13
+ * send's progress): its status and line, never the exit. Only the gate in
14
+ * state() and exit() offers that.
15
+ */
16
+ function ungated(state) {
17
+ return state.status === "clear" || !state.exit
18
+ ? state
19
+ : { ...state, exit: false };
20
+ }
10
21
  /** A send was refused because the exact call is held; `state` says how. */
11
22
  class WriteHeldError extends Error {
12
23
  constructor(state) {
@@ -34,6 +45,11 @@ class WriteHoldBusyError extends Error {
34
45
  }
35
46
  exports.WriteHoldBusyError = WriteHoldBusyError;
36
47
  const SETTLE_POLL_MS = 15_000;
48
+ /** A Safe's execution events, in its 1.3 and 1.4 layouts alike. */
49
+ const SAFE_EXECUTION_TOPICS = [
50
+ (0, viem_1.toEventSelector)("ExecutionSuccess(bytes32,uint256)"),
51
+ (0, viem_1.toEventSelector)("ExecutionFailure(bytes32,uint256)"),
52
+ ];
37
53
  /** A hash the network still hasn't seen this long after the wallet returned it was almost certainly never broadcast. */
38
54
  const UNSEEN_HASH_MS = 30 * 60_000;
39
55
  /** 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. */
@@ -83,11 +99,6 @@ function unchecked(record) {
83
99
  }
84
100
  /** About an hour of polls; a later state() looks again. */
85
101
  const SETTLE_POLL_ATTEMPTS = 240;
86
- function listenerKey(call) {
87
- const scope = call.namespace ??
88
- `${call.call.to}:${BigInt(call.call.value ?? 0)}:${call.call.data}`;
89
- return `${call.chainId}:${call.account}:${scope}`.toLowerCase();
90
- }
91
102
  function clear(settled) {
92
103
  return settled ? { status: "clear", settled } : { status: "clear" };
93
104
  }
@@ -159,7 +170,10 @@ function createWriteHold(options = {}) {
159
170
  const peekers = new Map();
160
171
  const timers = new Map();
161
172
  let onStorage;
173
+ /** The latest state emitted for each call, replayed to a late subscriber. */
174
+ const latest = new Map();
162
175
  function emit(key, state) {
176
+ latest.set(key, state);
163
177
  for (const listener of listeners.get(key) ?? [])
164
178
  listener(state);
165
179
  }
@@ -186,10 +200,16 @@ function createWriteHold(options = {}) {
186
200
  storage,
187
201
  locks: options.locks,
188
202
  });
189
- const key = listenerKey(call);
203
+ // The canonical recovery scope: a namespace and an exact call never share it.
204
+ const key = recovery.scopeKey;
190
205
  /** The effect read the last state() was given: the exit asks it again. */
191
206
  let lastEffect;
192
- const peek = () => classify(recovery.read());
207
+ /** Nothing held: how the call's last write settled, if saved. */
208
+ const cleared = () => clear(recovery.settled());
209
+ const peek = () => {
210
+ const record = recovery.read();
211
+ return record ? ungated(classify(record)) : cleared();
212
+ };
193
213
  const shown = (state) => (emit(key, state), state);
194
214
  async function locked(work, wait) {
195
215
  let entered = false;
@@ -232,11 +252,15 @@ function createWriteHold(options = {}) {
232
252
  }
233
253
  async function settle(record, receipt, client, proof) {
234
254
  const verdict = await (0, writeRecovery_js_1.verifyReviewedWriteReceipt)(client, record, receipt, proof).catch(() => null);
235
- if (verdict && release(record))
236
- return clear({
255
+ if (verdict && release(record)) {
256
+ const settled = {
237
257
  result: verdict,
238
258
  hash: receipt.transactionHash.toLowerCase(),
239
- });
259
+ };
260
+ // Kept for any later reader: another tab, a reload, a follow timer.
261
+ recovery.markSettled(record, settled);
262
+ return clear(settled);
263
+ }
240
264
  if (!record.safe &&
241
265
  receipt.status === "reverted" &&
242
266
  receipt.transactionHash.toLowerCase() === record.hash) {
@@ -294,6 +318,89 @@ function createWriteHold(options = {}) {
294
318
  // A proof that can't be dropped keeps the exit refusing it.
295
319
  return unchecked(recovery.unexecuted(record));
296
320
  }
321
+ /** The first hash the hold has seen that the node knows as a transaction with no receipt yet. */
322
+ /**
323
+ * Positive proof that `receipt` isn't the held Safe proposal's
324
+ * execution: it is in a canonical block, and it reverted or its Safe
325
+ * logged no execution of the proposal's safeTxHash. A read that fails
326
+ * proves nothing.
327
+ */
328
+ async function disproves(record, receipt, client) {
329
+ const block = await client
330
+ .getBlock({ blockNumber: receipt.blockNumber })
331
+ .catch(() => null);
332
+ if (!block?.hash ||
333
+ block.hash.toLowerCase() !== receipt.blockHash?.toLowerCase())
334
+ return false;
335
+ if (receipt.status === "reverted")
336
+ return true;
337
+ return !receipt.logs.some((log) => log.address?.toLowerCase() === record.account &&
338
+ SAFE_EXECUTION_TOPICS.includes(log.topics?.[0]) &&
339
+ (log.topics.length === 2
340
+ ? log.topics[1]
341
+ : log.data?.slice(0, 66))?.toLowerCase() === record.hash);
342
+ }
343
+ /**
344
+ * A held hash the node shows mined, though its receipt can't be read: it
345
+ * may have run, so no exit at once, and no "speed it up" either. Once
346
+ * that has lasted 30 minutes (`minedAt` keeps the clock), the warned exit.
347
+ * Seen pending again, the clock stops.
348
+ */
349
+ async function minedUnread(state, client) {
350
+ const look = await lookAt(state.hash, client);
351
+ // Only positive pending evidence stops the clock; a read that fails
352
+ // keeps the one saved.
353
+ if (look === "pending")
354
+ return { ...state, record: recovery.mined(state.record, false) };
355
+ const record = look === "mined" ? recovery.mined(state.record, true) : state.record;
356
+ if (record.minedAt === undefined)
357
+ return state;
358
+ if (Date.now() - Number(record.minedAt) >= UNSEEN_HASH_MS)
359
+ return unchecked(record);
360
+ return { ...state, record, line: messages_js_1.WRITE_HOLD_MINED_LINE };
361
+ }
362
+ /** What the node shows of `hash`: positive pending or mined metadata, or nothing it can say. */
363
+ async function lookAt(hash, client) {
364
+ const sent = await client.getTransaction({ hash }).catch(() => null);
365
+ if (sent?.hash?.toLowerCase() !== hash)
366
+ return null;
367
+ return sent.blockNumber === null ? "pending" : "mined";
368
+ }
369
+ /**
370
+ * No exit while a hash the hold has seen (its own, unless proven dropped,
371
+ * or a candidate) is pending, or was seen mined with its receipt
372
+ * unreadable less than 30 minutes ago: it may have run. Its clock is
373
+ * kept per hash, survives read failures, and stops only on positive
374
+ * pending evidence.
375
+ */
376
+ async function seenGate(saved, client, ownDropped) {
377
+ let record = saved;
378
+ const seen = [
379
+ ...(record.hash && !ownDropped ? [record.hash] : []),
380
+ ...(record.candidates ?? []),
381
+ ];
382
+ for (const hash of seen) {
383
+ const own = hash === record.hash;
384
+ const look = await lookAt(hash, client);
385
+ if (look !== null)
386
+ record = own
387
+ ? recovery.mined(record, look === "mined")
388
+ : recovery.candidateMined(record, hash, look === "mined");
389
+ const at = own ? record.minedAt : record.candidateMinedAt?.[hash];
390
+ if (look === "pending" ||
391
+ (at !== undefined && Date.now() - Number(at) < UNSEEN_HASH_MS))
392
+ return {
393
+ status: "pending-known-hash",
394
+ record,
395
+ hash,
396
+ exit: false,
397
+ line: look === "pending"
398
+ ? messages_js_1.WRITE_HOLD_PENDING_LINE
399
+ : messages_js_1.WRITE_HOLD_MINED_LINE,
400
+ };
401
+ }
402
+ return null;
403
+ }
297
404
  async function safeState(saved, client) {
298
405
  let record = saved;
299
406
  const hash = record.hash;
@@ -325,7 +432,24 @@ function createWriteHold(options = {}) {
325
432
  }
326
433
  }
327
434
  }
328
- else if (record.executionHash) {
435
+ // An execution hash the app reported settles it only on its own
436
+ // authenticated receipt; until then it is a candidate, never proof.
437
+ if (!record.executionHash)
438
+ for (const candidate of record.candidates ?? []) {
439
+ const receipt = await client
440
+ .getTransactionReceipt({ hash: candidate })
441
+ .catch(() => null);
442
+ if (!receipt)
443
+ continue;
444
+ const settled = await settle(record, receipt, client);
445
+ if (settled.status === "clear" || settled.status === "ran-executed")
446
+ return settled;
447
+ // Only a canonical receipt that names no execution of this
448
+ // proposal disproves it; anything unread keeps it.
449
+ if (await disproves(record, receipt, client))
450
+ record = recovery.disproven(record, candidate);
451
+ }
452
+ if (record.executed && record.executionHash) {
329
453
  const settled = await execution(record, client);
330
454
  if (settled?.status === "clear")
331
455
  return settled;
@@ -358,16 +482,28 @@ function createWriteHold(options = {}) {
358
482
  if (expired && release(record))
359
483
  return clear({ result: "expired" });
360
484
  }
361
- if (end.look === "executed" &&
362
- (!record.executed ||
485
+ if (end.look === "executed") {
486
+ if (!record.executed ||
363
487
  (end.execution &&
364
- end.execution.executionHash !== record.executionHash))) {
365
- record = recovery.executed(record, end.execution?.executionHash);
488
+ end.execution.executionHash !== record.executionHash))
489
+ record = recovery.executed(record, end.execution?.executionHash);
366
490
  if (record.executionHash) {
367
491
  const settled = await execution(record, client);
368
- if (settled)
492
+ // Only a settled receipt ends it here; one that can't be verified
493
+ // leaves the finalized log to decide.
494
+ if (settled?.status === "clear")
369
495
  return settled;
370
496
  }
497
+ // It ran: with its receipt unreadable, the canonical log at a
498
+ // finalized block carries its result. Never a resend exit.
499
+ if (end.execution && release(record)) {
500
+ const settled = {
501
+ result: end.execution.status,
502
+ hash: end.execution.executionHash,
503
+ };
504
+ recovery.markSettled(record, settled);
505
+ return clear(settled);
506
+ }
371
507
  }
372
508
  if (!record.executed &&
373
509
  !record.unproven &&
@@ -378,10 +514,35 @@ function createWriteHold(options = {}) {
378
514
  follow(client, record.id);
379
515
  return classify(record);
380
516
  }
517
+ /**
518
+ * The one gate for every exit, offered or taken: no exit while any hash
519
+ * the hold has seen (its own, or a candidate) is a known pending
520
+ * transaction, whatever the state says, a replacement included.
521
+ */
381
522
  async function inspect(client, effect) {
523
+ const state = await inspectChain(client, effect);
524
+ if (state.status === "pending-known-hash")
525
+ return minedUnread(state, client);
526
+ if (state.status === "clear" || !state.exit)
527
+ return state;
528
+ // A hash proven dropped (another transaction used its sent nonce at a
529
+ // canonical finalized block) can't run, whatever a node still shows.
530
+ return ((await seenGate(state.record, client, state.status === "dropped-proven")) ?? state);
531
+ }
532
+ /**
533
+ * The way out of every method that has a client: a state that could
534
+ * offer the exit, or track a hash, is the gate's answer (inspect), so no
535
+ * method reports an exit state() would refuse. Under the call's lock.
536
+ */
537
+ async function finish(state, client) {
538
+ return state.status === "clear" || state.status === "ran-executed"
539
+ ? state
540
+ : inspect(client);
541
+ }
542
+ async function inspectChain(client, effect) {
382
543
  let record = recovery.read();
383
544
  if (!record)
384
- return clear();
545
+ return cleared();
385
546
  // No evidence from another chain.
386
547
  if ((await client.getChainId().catch(() => null)) !== record.chainId)
387
548
  return classify(record);
@@ -392,7 +553,7 @@ function createWriteHold(options = {}) {
392
553
  .catch(() => null);
393
554
  record = recovery.read();
394
555
  if (!record)
395
- return clear();
556
+ return cleared();
396
557
  if (check?.status === "sent") {
397
558
  // An authenticated hash is seen: any vanished clock stops.
398
559
  if (record.vanishedAt !== undefined)
@@ -405,17 +566,6 @@ function createWriteHold(options = {}) {
405
566
  // A hash saved before its clock existed starts it now.
406
567
  if (record.hash && record.submittedAt === undefined)
407
568
  record = recovery.firstSeen(record);
408
- // The app's effect read only changes the line, and offers the exit.
409
- if (check?.status === "pending" && check.effect)
410
- return {
411
- status: "looks-done",
412
- record,
413
- exit: true,
414
- line: messages_js_1.WRITE_HOLD_LOOKS_DONE_LINE,
415
- ...(record.hash
416
- ? { hash: record.hash, warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE }
417
- : {}),
418
- };
419
569
  if (record.hash &&
420
570
  record.sentNonce !== undefined &&
421
571
  check?.status === "pending" &&
@@ -429,6 +579,18 @@ function createWriteHold(options = {}) {
429
579
  line: (0, messages_js_1.writeHeldMessage)(),
430
580
  warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE,
431
581
  };
582
+ // The app's effect read only changes the line, and offers the exit;
583
+ // a proven drop above takes priority.
584
+ if (check?.status === "pending" && check.effect)
585
+ return {
586
+ status: "looks-done",
587
+ record,
588
+ exit: true,
589
+ line: messages_js_1.WRITE_HOLD_LOOKS_DONE_LINE,
590
+ ...(record.hash
591
+ ? { hash: record.hash, warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE }
592
+ : {}),
593
+ };
432
594
  // A hash seen with its sent nonce that the node has since lost.
433
595
  if (record.hash &&
434
596
  record.sentNonce !== undefined &&
@@ -464,7 +626,10 @@ function createWriteHold(options = {}) {
464
626
  state,
465
627
  /** What the saved record alone says, without asking the chain. */
466
628
  peek,
467
- /** Every state this controller reads or makes for the call, and other tabs' changes. */
629
+ /**
630
+ * Every state this controller reads or makes for the call, and other
631
+ * tabs' changes. The latest one, if any, is replayed at once.
632
+ */
468
633
  subscribe(listener) {
469
634
  let set = listeners.get(key);
470
635
  if (!set)
@@ -472,6 +637,16 @@ function createWriteHold(options = {}) {
472
637
  set.add(listener);
473
638
  peekers.set(key, peek);
474
639
  watchTabs();
640
+ // A subscriber that arrives after the hold spoke hears its latest state.
641
+ // Only while it still names the call's current record; otherwise
642
+ // what the saved record says now.
643
+ const last = latest.get(key);
644
+ if (last) {
645
+ const now = peek();
646
+ // A clear is compared by its settlement's identity, a hold by its record.
647
+ const identity = (state) => JSON.stringify(state.status === "clear" ? (state.settled ?? null) : state.record);
648
+ listener(ungated(identity(now) === identity(last) ? last : now));
649
+ }
475
650
  return () => {
476
651
  set.delete(listener);
477
652
  if (!set.size) {
@@ -506,7 +681,8 @@ function createWriteHold(options = {}) {
506
681
  attempt = recovery.reserve(actualCall, evidence);
507
682
  }
508
683
  catch (error) {
509
- const now = peek();
684
+ const now = await finish(classify(recovery.read()), client);
685
+ shown(now);
510
686
  if (now.status !== "clear")
511
687
  throw new WriteHeldError(now);
512
688
  throw error;
@@ -533,7 +709,7 @@ function createWriteHold(options = {}) {
533
709
  catch {
534
710
  /* Not this proposal: the service is asked later. */
535
711
  }
536
- shown(classify(attempt));
712
+ shown(ungated(classify(attempt)));
537
713
  },
538
714
  onRejected() {
539
715
  recovery.rejected();
@@ -572,7 +748,10 @@ function createWriteHold(options = {}) {
572
748
  recovery.abandon(snapshot.record, { checkedWallet: true });
573
749
  }
574
750
  catch {
575
- return { released: false, state: peek() };
751
+ return {
752
+ released: false,
753
+ state: await finish(classify(recovery.read()), client),
754
+ };
576
755
  }
577
756
  return { released: true, state: clear() };
578
757
  }, true);
@@ -591,8 +770,8 @@ function createWriteHold(options = {}) {
591
770
  if (!record ||
592
771
  (!record.safe &&
593
772
  record.hash !== receipt?.transactionHash?.toLowerCase()))
594
- return classify(record);
595
- return settle(record, receipt, client, proof);
773
+ return finish(classify(record), client);
774
+ return finish(await settle(record, receipt, client, proof), client);
596
775
  }, true);
597
776
  return shown(result?.value ?? peek());
598
777
  },
@@ -615,15 +794,75 @@ function createWriteHold(options = {}) {
615
794
  return shown(result?.value ?? peek());
616
795
  },
617
796
  /**
618
- * The app's own proof that the held Safe proposal ran, with its
619
- * execution's transaction when known. Kept until its result settles it.
797
+ * Attach a hash the chain authenticates as this exact call, sent by the
798
+ * account on the reservation's chain at or after its next nonce, and
799
+ * not mined before it was reserved, to the held write that has none: under
800
+ * one lock, without releasing it. The hold then follows that hash: no
801
+ * exit while it is pending, and its receipt settles it.
802
+ */
803
+ async attachHash(client, hash) {
804
+ const result = await locked(async () => {
805
+ const record = recovery.read();
806
+ if (record &&
807
+ !record.safe &&
808
+ !record.hash &&
809
+ (0, untrusted_js_1.isBytes32)(hash) &&
810
+ (await client.getChainId().catch(() => null)) === record.chainId) {
811
+ const sent = await client
812
+ .getTransaction({ hash })
813
+ .catch(() => null);
814
+ if (sent?.hash?.toLowerCase() === hash.toLowerCase() &&
815
+ sent.from?.toLowerCase() === record.account &&
816
+ (sent.chainId === undefined || sent.chainId === record.chainId) &&
817
+ sent.to?.toLowerCase() === record.call.to &&
818
+ sent.input?.toLowerCase() === record.call.data &&
819
+ BigInt(sent.value) === BigInt(record.call.value) &&
820
+ // This attempt, not an earlier identical call: never below the
821
+ // account's next nonce or mined before it was reserved.
822
+ // With no such evidence, an identical earlier call can't be
823
+ // told apart: nothing is attached, and the exit stays.
824
+ record.nonce !== undefined &&
825
+ record.fromBlock !== undefined &&
826
+ BigInt(sent.nonce) >= BigInt(record.nonce) &&
827
+ (sent.blockNumber === null ||
828
+ sent.blockNumber >= BigInt(record.fromBlock)))
829
+ try {
830
+ recovery.submitted(hash, record);
831
+ }
832
+ catch {
833
+ /* Kept in this tab's memory, it still holds. */
834
+ }
835
+ }
836
+ return inspect(client);
837
+ }, true);
838
+ return shown(result?.value ?? peek());
839
+ },
840
+ /**
841
+ * The app's word that the held Safe proposal ran, with its execution's
842
+ * transaction when known. Nothing is saved as executed on its word:
843
+ * the hash is kept as a candidate, and settles it only once its own
844
+ * receipt proves this Safe's execution of the exact proposal or call.
620
845
  */
621
846
  async onExecuted(client, executionHash) {
622
847
  const result = await locked(async () => {
623
848
  const record = recovery.read();
624
849
  if (!record?.safe || !record.hash)
625
- return classify(record);
626
- recovery.executed(record, executionHash);
850
+ return finish(classify(record), client);
851
+ if (executionHash) {
852
+ // Authenticated on arrival: its own receipt settles it, or proves
853
+ // it isn't this write's; only an unresolved one is kept.
854
+ const receipt = await client
855
+ .getTransactionReceipt({ hash: executionHash })
856
+ .catch(() => null);
857
+ if (receipt) {
858
+ const settled = await settle(record, receipt, client);
859
+ if (settled.status === "clear" ||
860
+ settled.status === "ran-executed")
861
+ return settled;
862
+ }
863
+ if (!receipt || !(await disproves(record, receipt, client)))
864
+ recovery.candidate(record, executionHash);
865
+ }
627
866
  return inspect(client);
628
867
  }, true);
629
868
  return shown(result?.value ?? peek());