@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.
- package/dist/cjs/review/messages.d.ts +2 -0
- package/dist/cjs/review/messages.d.ts.map +1 -1
- package/dist/cjs/review/messages.js +3 -1
- package/dist/cjs/review/messages.js.map +1 -1
- package/dist/cjs/review/writeHold.d.ts +21 -4
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +261 -41
- package/dist/cjs/review/writeHold.js.map +1 -1
- package/dist/cjs/review/writeRecovery.d.ts +51 -0
- package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
- package/dist/cjs/review/writeRecovery.js +163 -9
- package/dist/cjs/review/writeRecovery.js.map +1 -1
- package/dist/esm/review/messages.d.ts +2 -0
- package/dist/esm/review/messages.d.ts.map +1 -1
- package/dist/esm/review/messages.js +2 -0
- package/dist/esm/review/messages.js.map +1 -1
- package/dist/esm/review/writeHold.d.ts +21 -4
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +263 -43
- package/dist/esm/review/writeHold.js.map +1 -1
- package/dist/esm/review/writeRecovery.d.ts +51 -0
- package/dist/esm/review/writeRecovery.d.ts.map +1 -1
- package/dist/esm/review/writeRecovery.js +163 -9
- package/dist/esm/review/writeRecovery.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
199
|
-
*
|
|
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":"AAAA,OAAO,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
614
|
-
*
|
|
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
|
-
|
|
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());
|