@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.
- 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 +31 -10
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +278 -39
- 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 +31 -10
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +280 -41
- 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,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
|
-
*
|
|
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:
|
|
74
|
+
exit: boolean;
|
|
67
75
|
}> | Held<"dropped-proven", {
|
|
68
76
|
hash: Hex;
|
|
69
77
|
usedBy: Hex;
|
|
70
|
-
exit:
|
|
78
|
+
exit: boolean;
|
|
71
79
|
warning: string;
|
|
72
80
|
}> | Held<"held-unchecked", {
|
|
73
81
|
hash: Hex;
|
|
74
|
-
exit:
|
|
82
|
+
exit: boolean;
|
|
75
83
|
warning: string;
|
|
76
84
|
}> | Held<"safe-replaced", {
|
|
77
85
|
hash: Hex;
|
|
78
|
-
exit:
|
|
86
|
+
exit: boolean;
|
|
79
87
|
warning: string;
|
|
80
88
|
}> | Held<"safe-unproven", {
|
|
81
89
|
hash: Hex;
|
|
82
|
-
exit:
|
|
90
|
+
exit: boolean;
|
|
83
91
|
warning: string;
|
|
84
92
|
}> | Held<"looks-done", {
|
|
85
93
|
hash?: Hex;
|
|
86
|
-
exit:
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
199
|
-
*
|
|
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":"AAAA,OAAO,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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 =
|
|
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 {
|
|
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
|
-
*
|
|
619
|
-
*
|
|
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
|
-
|
|
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());
|