@bananapus/nana-sdk-core 2.33.1 → 2.34.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.
@@ -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,11 @@ 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
+ * - `clear`: nothing is held; `settled` says how the call's last write
28
+ * ended on authenticated evidence (its receipt or canonical execution),
29
+ * saved for every reader until the next write on the call is reserved. A
30
+ * proven expiry says so only to the caller that saw it; the user's exit
31
+ * leaves none;
28
32
  * - `pending-known-hash`: its saved hash (or Safe proposal) can still run. No
29
33
  * exit: the wallet speeds it up or cancels it;
30
34
  * - `held-no-hash`: the wallet never returned a hash. The exit is offered;
@@ -159,7 +163,10 @@ export declare function createWriteHold(options?: {
159
163
  }) => Promise<WriteHoldState>;
160
164
  /** What the saved record alone says, without asking the chain. */
161
165
  peek: () => WriteHoldState;
162
- /** Every state this controller reads or makes for the call, and other tabs' changes. */
166
+ /**
167
+ * Every state this controller reads or makes for the call, and other
168
+ * tabs' changes. The latest one, if any, is replayed at once.
169
+ */
163
170
  subscribe(listener: (state: WriteHoldState) => void): () => void;
164
171
  /**
165
172
  * Send the call under its hold. A held call throws
@@ -195,8 +202,18 @@ export declare function createWriteHold(options?: {
195
202
  */
196
203
  onUnproven(client: WriteHoldClient, proposalHash?: Hex): Promise<WriteHoldState>;
197
204
  /**
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.
205
+ * Attach a hash the chain authenticates as this exact call, sent by the
206
+ * account on the reservation's chain at or after its next nonce, and
207
+ * not mined before it was reserved, to the held write that has none: under
208
+ * one lock, without releasing it. The hold then follows that hash: no
209
+ * exit while it is pending, and its receipt settles it.
210
+ */
211
+ attachHash(client: WriteHoldClient, hash: Hex): Promise<WriteHoldState>;
212
+ /**
213
+ * The app's word that the held Safe proposal ran, with its execution's
214
+ * transaction when known. Nothing is saved as executed on its word:
215
+ * the hash is kept as a candidate, and settles it only once its own
216
+ * receipt proves this Safe's execution of the exact proposal or call.
200
217
  */
201
218
  onExecuted(client: WriteHoldClient, executionHash?: Hex): Promise<WriteHoldState>;
202
219
  };
@@ -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,6 +6,7 @@ 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");
10
11
  /** A send was refused because the exact call is held; `state` says how. */
11
12
  class WriteHeldError extends Error {
@@ -34,6 +35,11 @@ class WriteHoldBusyError extends Error {
34
35
  }
35
36
  exports.WriteHoldBusyError = WriteHoldBusyError;
36
37
  const SETTLE_POLL_MS = 15_000;
38
+ /** A Safe's execution events, in its 1.3 and 1.4 layouts alike. */
39
+ const SAFE_EXECUTION_TOPICS = [
40
+ (0, viem_1.toEventSelector)("ExecutionSuccess(bytes32,uint256)"),
41
+ (0, viem_1.toEventSelector)("ExecutionFailure(bytes32,uint256)"),
42
+ ];
37
43
  /** A hash the network still hasn't seen this long after the wallet returned it was almost certainly never broadcast. */
38
44
  const UNSEEN_HASH_MS = 30 * 60_000;
39
45
  /** 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 +89,6 @@ function unchecked(record) {
83
89
  }
84
90
  /** About an hour of polls; a later state() looks again. */
85
91
  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
92
  function clear(settled) {
92
93
  return settled ? { status: "clear", settled } : { status: "clear" };
93
94
  }
@@ -159,7 +160,10 @@ function createWriteHold(options = {}) {
159
160
  const peekers = new Map();
160
161
  const timers = new Map();
161
162
  let onStorage;
163
+ /** The latest state emitted for each call, replayed to a late subscriber. */
164
+ const latest = new Map();
162
165
  function emit(key, state) {
166
+ latest.set(key, state);
163
167
  for (const listener of listeners.get(key) ?? [])
164
168
  listener(state);
165
169
  }
@@ -186,10 +190,16 @@ function createWriteHold(options = {}) {
186
190
  storage,
187
191
  locks: options.locks,
188
192
  });
189
- const key = listenerKey(call);
193
+ // The canonical recovery scope: a namespace and an exact call never share it.
194
+ const key = recovery.scopeKey;
190
195
  /** The effect read the last state() was given: the exit asks it again. */
191
196
  let lastEffect;
192
- const peek = () => classify(recovery.read());
197
+ /** Nothing held: how the call's last write settled, if saved. */
198
+ const cleared = () => clear(recovery.settled());
199
+ const peek = () => {
200
+ const record = recovery.read();
201
+ return record ? classify(record) : cleared();
202
+ };
193
203
  const shown = (state) => (emit(key, state), state);
194
204
  async function locked(work, wait) {
195
205
  let entered = false;
@@ -232,11 +242,15 @@ function createWriteHold(options = {}) {
232
242
  }
233
243
  async function settle(record, receipt, client, proof) {
234
244
  const verdict = await (0, writeRecovery_js_1.verifyReviewedWriteReceipt)(client, record, receipt, proof).catch(() => null);
235
- if (verdict && release(record))
236
- return clear({
245
+ if (verdict && release(record)) {
246
+ const settled = {
237
247
  result: verdict,
238
248
  hash: receipt.transactionHash.toLowerCase(),
239
- });
249
+ };
250
+ // Kept for any later reader: another tab, a reload, a follow timer.
251
+ recovery.markSettled(record, settled);
252
+ return clear(settled);
253
+ }
240
254
  if (!record.safe &&
241
255
  receipt.status === "reverted" &&
242
256
  receipt.transactionHash.toLowerCase() === record.hash) {
@@ -294,6 +308,89 @@ function createWriteHold(options = {}) {
294
308
  // A proof that can't be dropped keeps the exit refusing it.
295
309
  return unchecked(recovery.unexecuted(record));
296
310
  }
311
+ /** The first hash the hold has seen that the node knows as a transaction with no receipt yet. */
312
+ /**
313
+ * Positive proof that `receipt` isn't the held Safe proposal's
314
+ * execution: it is in a canonical block, and it reverted or its Safe
315
+ * logged no execution of the proposal's safeTxHash. A read that fails
316
+ * proves nothing.
317
+ */
318
+ async function disproves(record, receipt, client) {
319
+ const block = await client
320
+ .getBlock({ blockNumber: receipt.blockNumber })
321
+ .catch(() => null);
322
+ if (!block?.hash ||
323
+ block.hash.toLowerCase() !== receipt.blockHash?.toLowerCase())
324
+ return false;
325
+ if (receipt.status === "reverted")
326
+ return true;
327
+ return !receipt.logs.some((log) => log.address?.toLowerCase() === record.account &&
328
+ SAFE_EXECUTION_TOPICS.includes(log.topics?.[0]) &&
329
+ (log.topics.length === 2
330
+ ? log.topics[1]
331
+ : log.data?.slice(0, 66))?.toLowerCase() === record.hash);
332
+ }
333
+ /**
334
+ * A held hash the node shows mined, though its receipt can't be read: it
335
+ * may have run, so no exit at once, and no "speed it up" either. Once
336
+ * that has lasted 30 minutes (`minedAt` keeps the clock), the warned exit.
337
+ * Seen pending again, the clock stops.
338
+ */
339
+ async function minedUnread(state, client) {
340
+ const look = await lookAt(state.hash, client);
341
+ // Only positive pending evidence stops the clock; a read that fails
342
+ // keeps the one saved.
343
+ if (look === "pending")
344
+ return { ...state, record: recovery.mined(state.record, false) };
345
+ const record = look === "mined" ? recovery.mined(state.record, true) : state.record;
346
+ if (record.minedAt === undefined)
347
+ return state;
348
+ if (Date.now() - Number(record.minedAt) >= UNSEEN_HASH_MS)
349
+ return unchecked(record);
350
+ return { ...state, record, line: messages_js_1.WRITE_HOLD_MINED_LINE };
351
+ }
352
+ /** What the node shows of `hash`: positive pending or mined metadata, or nothing it can say. */
353
+ async function lookAt(hash, client) {
354
+ const sent = await client.getTransaction({ hash }).catch(() => null);
355
+ if (sent?.hash?.toLowerCase() !== hash)
356
+ return null;
357
+ return sent.blockNumber === null ? "pending" : "mined";
358
+ }
359
+ /**
360
+ * No exit while a hash the hold has seen (its own, unless proven dropped,
361
+ * or a candidate) is pending, or was seen mined with its receipt
362
+ * unreadable less than 30 minutes ago: it may have run. Its clock is
363
+ * kept per hash, survives read failures, and stops only on positive
364
+ * pending evidence.
365
+ */
366
+ async function seenGate(saved, client, ownDropped) {
367
+ let record = saved;
368
+ const seen = [
369
+ ...(record.hash && !ownDropped ? [record.hash] : []),
370
+ ...(record.candidates ?? []),
371
+ ];
372
+ for (const hash of seen) {
373
+ const own = hash === record.hash;
374
+ const look = await lookAt(hash, client);
375
+ if (look !== null)
376
+ record = own
377
+ ? recovery.mined(record, look === "mined")
378
+ : recovery.candidateMined(record, hash, look === "mined");
379
+ const at = own ? record.minedAt : record.candidateMinedAt?.[hash];
380
+ if (look === "pending" ||
381
+ (at !== undefined && Date.now() - Number(at) < UNSEEN_HASH_MS))
382
+ return {
383
+ status: "pending-known-hash",
384
+ record,
385
+ hash,
386
+ exit: false,
387
+ line: look === "pending"
388
+ ? messages_js_1.WRITE_HOLD_PENDING_LINE
389
+ : messages_js_1.WRITE_HOLD_MINED_LINE,
390
+ };
391
+ }
392
+ return null;
393
+ }
297
394
  async function safeState(saved, client) {
298
395
  let record = saved;
299
396
  const hash = record.hash;
@@ -310,17 +407,39 @@ function createWriteHold(options = {}) {
310
407
  const settled = await settle(record, atOnce, client);
311
408
  if (settled.status === "clear")
312
409
  return settled;
313
- // It ran; only its verified result (a failure at finality) ends it.
314
- record = recovery.executed(record, hash);
315
- // Who sent it at which nonce proves a canonical replacement later.
316
- const sent = await client.getTransaction({ hash }).catch(() => null);
317
- if (sent?.hash?.toLowerCase() === hash && (0, viem_1.isAddress)(sent.from))
318
- record = recovery.sentBy(record, sent.from, BigInt(sent.nonce));
319
- follow(client, record.id);
320
- return ranExecuted(record);
410
+ // Saved as executed only once authenticated as this Safe's
411
+ // execution of the exact call (settle does that); a receipt alone
412
+ // proves nothing, and the record stays unproven.
413
+ if (settled.status === "ran-executed") {
414
+ record = settled.record;
415
+ // Who sent it at which nonce proves a canonical replacement later.
416
+ const sent = await client
417
+ .getTransaction({ hash })
418
+ .catch(() => null);
419
+ if (sent?.hash?.toLowerCase() === hash && (0, viem_1.isAddress)(sent.from))
420
+ record = recovery.sentBy(record, sent.from, BigInt(sent.nonce));
421
+ return ranExecuted(record, hash, settled.result);
422
+ }
321
423
  }
322
424
  }
323
- else if (record.executionHash) {
425
+ // An execution hash the app reported settles it only on its own
426
+ // authenticated receipt; until then it is a candidate, never proof.
427
+ if (!record.executionHash)
428
+ for (const candidate of record.candidates ?? []) {
429
+ const receipt = await client
430
+ .getTransactionReceipt({ hash: candidate })
431
+ .catch(() => null);
432
+ if (!receipt)
433
+ continue;
434
+ const settled = await settle(record, receipt, client);
435
+ if (settled.status === "clear" || settled.status === "ran-executed")
436
+ return settled;
437
+ // Only a canonical receipt that names no execution of this
438
+ // proposal disproves it; anything unread keeps it.
439
+ if (await disproves(record, receipt, client))
440
+ record = recovery.disproven(record, candidate);
441
+ }
442
+ if (record.executed && record.executionHash) {
324
443
  const settled = await execution(record, client);
325
444
  if (settled?.status === "clear")
326
445
  return settled;
@@ -353,16 +472,28 @@ function createWriteHold(options = {}) {
353
472
  if (expired && release(record))
354
473
  return clear({ result: "expired" });
355
474
  }
356
- if (end.look === "executed" &&
357
- (!record.executed ||
475
+ if (end.look === "executed") {
476
+ if (!record.executed ||
358
477
  (end.execution &&
359
- end.execution.executionHash !== record.executionHash))) {
360
- record = recovery.executed(record, end.execution?.executionHash);
478
+ end.execution.executionHash !== record.executionHash))
479
+ record = recovery.executed(record, end.execution?.executionHash);
361
480
  if (record.executionHash) {
362
481
  const settled = await execution(record, client);
363
- if (settled)
482
+ // Only a settled receipt ends it here; one that can't be verified
483
+ // leaves the finalized log to decide.
484
+ if (settled?.status === "clear")
364
485
  return settled;
365
486
  }
487
+ // It ran: with its receipt unreadable, the canonical log at a
488
+ // finalized block carries its result. Never a resend exit.
489
+ if (end.execution && release(record)) {
490
+ const settled = {
491
+ result: end.execution.status,
492
+ hash: end.execution.executionHash,
493
+ };
494
+ recovery.markSettled(record, settled);
495
+ return clear(settled);
496
+ }
366
497
  }
367
498
  if (!record.executed &&
368
499
  !record.unproven &&
@@ -373,10 +504,25 @@ function createWriteHold(options = {}) {
373
504
  follow(client, record.id);
374
505
  return classify(record);
375
506
  }
507
+ /**
508
+ * The one gate for every exit, offered or taken: no exit while any hash
509
+ * the hold has seen (its own, or a candidate) is a known pending
510
+ * transaction, whatever the state says, a replacement included.
511
+ */
376
512
  async function inspect(client, effect) {
513
+ const state = await inspectChain(client, effect);
514
+ if (state.status === "pending-known-hash")
515
+ return minedUnread(state, client);
516
+ if (state.status === "clear" || !state.exit)
517
+ return state;
518
+ // A hash proven dropped (another transaction used its sent nonce at a
519
+ // canonical finalized block) can't run, whatever a node still shows.
520
+ return ((await seenGate(state.record, client, state.status === "dropped-proven")) ?? state);
521
+ }
522
+ async function inspectChain(client, effect) {
377
523
  let record = recovery.read();
378
524
  if (!record)
379
- return clear();
525
+ return cleared();
380
526
  // No evidence from another chain.
381
527
  if ((await client.getChainId().catch(() => null)) !== record.chainId)
382
528
  return classify(record);
@@ -387,7 +533,7 @@ function createWriteHold(options = {}) {
387
533
  .catch(() => null);
388
534
  record = recovery.read();
389
535
  if (!record)
390
- return clear();
536
+ return cleared();
391
537
  if (check?.status === "sent") {
392
538
  // An authenticated hash is seen: any vanished clock stops.
393
539
  if (record.vanishedAt !== undefined)
@@ -400,17 +546,6 @@ function createWriteHold(options = {}) {
400
546
  // A hash saved before its clock existed starts it now.
401
547
  if (record.hash && record.submittedAt === undefined)
402
548
  record = recovery.firstSeen(record);
403
- // The app's effect read only changes the line, and offers the exit.
404
- if (check?.status === "pending" && check.effect)
405
- return {
406
- status: "looks-done",
407
- record,
408
- exit: true,
409
- line: messages_js_1.WRITE_HOLD_LOOKS_DONE_LINE,
410
- ...(record.hash
411
- ? { hash: record.hash, warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE }
412
- : {}),
413
- };
414
549
  if (record.hash &&
415
550
  record.sentNonce !== undefined &&
416
551
  check?.status === "pending" &&
@@ -424,6 +559,18 @@ function createWriteHold(options = {}) {
424
559
  line: (0, messages_js_1.writeHeldMessage)(),
425
560
  warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE,
426
561
  };
562
+ // The app's effect read only changes the line, and offers the exit;
563
+ // a proven drop above takes priority.
564
+ if (check?.status === "pending" && check.effect)
565
+ return {
566
+ status: "looks-done",
567
+ record,
568
+ exit: true,
569
+ line: messages_js_1.WRITE_HOLD_LOOKS_DONE_LINE,
570
+ ...(record.hash
571
+ ? { hash: record.hash, warning: messages_js_1.WRITE_HOLD_HASHED_EXIT_LINE }
572
+ : {}),
573
+ };
427
574
  // A hash seen with its sent nonce that the node has since lost.
428
575
  if (record.hash &&
429
576
  record.sentNonce !== undefined &&
@@ -459,7 +606,10 @@ function createWriteHold(options = {}) {
459
606
  state,
460
607
  /** What the saved record alone says, without asking the chain. */
461
608
  peek,
462
- /** Every state this controller reads or makes for the call, and other tabs' changes. */
609
+ /**
610
+ * Every state this controller reads or makes for the call, and other
611
+ * tabs' changes. The latest one, if any, is replayed at once.
612
+ */
463
613
  subscribe(listener) {
464
614
  let set = listeners.get(key);
465
615
  if (!set)
@@ -467,6 +617,16 @@ function createWriteHold(options = {}) {
467
617
  set.add(listener);
468
618
  peekers.set(key, peek);
469
619
  watchTabs();
620
+ // A subscriber that arrives after the hold spoke hears its latest state.
621
+ // Only while it still names the call's current record; otherwise
622
+ // what the saved record says now.
623
+ const last = latest.get(key);
624
+ if (last) {
625
+ const now = peek();
626
+ // A clear is compared by its settlement's identity, a hold by its record.
627
+ const identity = (state) => JSON.stringify(state.status === "clear" ? (state.settled ?? null) : state.record);
628
+ listener(identity(now) === identity(last) ? last : now);
629
+ }
470
630
  return () => {
471
631
  set.delete(listener);
472
632
  if (!set.size) {
@@ -610,15 +770,75 @@ function createWriteHold(options = {}) {
610
770
  return shown(result?.value ?? peek());
611
771
  },
612
772
  /**
613
- * The app's own proof that the held Safe proposal ran, with its
614
- * execution's transaction when known. Kept until its result settles it.
773
+ * Attach a hash the chain authenticates as this exact call, sent by the
774
+ * account on the reservation's chain at or after its next nonce, and
775
+ * not mined before it was reserved, to the held write that has none: under
776
+ * one lock, without releasing it. The hold then follows that hash: no
777
+ * exit while it is pending, and its receipt settles it.
778
+ */
779
+ async attachHash(client, hash) {
780
+ const result = await locked(async () => {
781
+ const record = recovery.read();
782
+ if (record &&
783
+ !record.safe &&
784
+ !record.hash &&
785
+ (0, untrusted_js_1.isBytes32)(hash) &&
786
+ (await client.getChainId().catch(() => null)) === record.chainId) {
787
+ const sent = await client
788
+ .getTransaction({ hash })
789
+ .catch(() => null);
790
+ if (sent?.hash?.toLowerCase() === hash.toLowerCase() &&
791
+ sent.from?.toLowerCase() === record.account &&
792
+ (sent.chainId === undefined || sent.chainId === record.chainId) &&
793
+ sent.to?.toLowerCase() === record.call.to &&
794
+ sent.input?.toLowerCase() === record.call.data &&
795
+ BigInt(sent.value) === BigInt(record.call.value) &&
796
+ // This attempt, not an earlier identical call: never below the
797
+ // account's next nonce or mined before it was reserved.
798
+ // With no such evidence, an identical earlier call can't be
799
+ // told apart: nothing is attached, and the exit stays.
800
+ record.nonce !== undefined &&
801
+ record.fromBlock !== undefined &&
802
+ BigInt(sent.nonce) >= BigInt(record.nonce) &&
803
+ (sent.blockNumber === null ||
804
+ sent.blockNumber >= BigInt(record.fromBlock)))
805
+ try {
806
+ recovery.submitted(hash, record);
807
+ }
808
+ catch {
809
+ /* Kept in this tab's memory, it still holds. */
810
+ }
811
+ }
812
+ return inspect(client);
813
+ }, true);
814
+ return shown(result?.value ?? peek());
815
+ },
816
+ /**
817
+ * The app's word that the held Safe proposal ran, with its execution's
818
+ * transaction when known. Nothing is saved as executed on its word:
819
+ * the hash is kept as a candidate, and settles it only once its own
820
+ * receipt proves this Safe's execution of the exact proposal or call.
615
821
  */
616
822
  async onExecuted(client, executionHash) {
617
823
  const result = await locked(async () => {
618
824
  const record = recovery.read();
619
825
  if (!record?.safe || !record.hash)
620
826
  return classify(record);
621
- recovery.executed(record, executionHash);
827
+ if (executionHash) {
828
+ // Authenticated on arrival: its own receipt settles it, or proves
829
+ // it isn't this write's; only an unresolved one is kept.
830
+ const receipt = await client
831
+ .getTransactionReceipt({ hash: executionHash })
832
+ .catch(() => null);
833
+ if (receipt) {
834
+ const settled = await settle(record, receipt, client);
835
+ if (settled.status === "clear" ||
836
+ settled.status === "ran-executed")
837
+ return settled;
838
+ }
839
+ if (!receipt || !(await disproves(record, receipt, client)))
840
+ recovery.candidate(record, executionHash);
841
+ }
622
842
  return inspect(client);
623
843
  }, true);
624
844
  return shown(result?.value ?? peek());