@bananapus/nana-sdk-core 2.17.0 → 2.19.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/constants.d.ts +1 -13
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +4 -22
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/generated/abi/jbBuybackHookAbi.d.ts +1187 -0
- package/dist/cjs/generated/abi/jbBuybackHookAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbBuybackHookAbi.js +841 -0
- package/dist/cjs/generated/abi/jbBuybackHookAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.d.ts +791 -0
- package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.js +632 -0
- package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbControllerAbi.d.ts +2569 -0
- package/dist/cjs/generated/abi/jbControllerAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbControllerAbi.js +1906 -0
- package/dist/cjs/generated/abi/jbControllerAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbDirectoryAbi.d.ts +410 -0
- package/dist/cjs/generated/abi/jbDirectoryAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbDirectoryAbi.js +365 -0
- package/dist/cjs/generated/abi/jbDirectoryAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbMultiTerminalAbi.d.ts +1863 -0
- package/dist/cjs/generated/abi/jbMultiTerminalAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbMultiTerminalAbi.js +1459 -0
- package/dist/cjs/generated/abi/jbMultiTerminalAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbPermissionsAbi.d.ts +240 -0
- package/dist/cjs/generated/abi/jbPermissionsAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbPermissionsAbi.js +164 -0
- package/dist/cjs/generated/abi/jbPermissionsAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbProjectsAbi.d.ts +626 -0
- package/dist/cjs/generated/abi/jbProjectsAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbProjectsAbi.js +470 -0
- package/dist/cjs/generated/abi/jbProjectsAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.d.ts +1503 -0
- package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.js +1037 -0
- package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.d.ts +1119 -0
- package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.js +925 -0
- package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbSplitsAbi.d.ts +244 -0
- package/dist/cjs/generated/abi/jbSplitsAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbSplitsAbi.js +198 -0
- package/dist/cjs/generated/abi/jbSplitsAbi.js.map +1 -0
- package/dist/cjs/generated/abi/jbTokensAbi.d.ts +602 -0
- package/dist/cjs/generated/abi/jbTokensAbi.d.ts.map +1 -0
- package/dist/cjs/generated/abi/jbTokensAbi.js +501 -0
- package/dist/cjs/generated/abi/jbTokensAbi.js.map +1 -0
- package/dist/cjs/generated/juicebox.d.ts +25405 -36537
- package/dist/cjs/generated/juicebox.d.ts.map +1 -1
- package/dist/cjs/generated/juicebox.js +14362 -22761
- package/dist/cjs/generated/juicebox.js.map +1 -1
- package/dist/cjs/pureConstants.d.ts +15 -0
- package/dist/cjs/pureConstants.d.ts.map +1 -0
- package/dist/cjs/pureConstants.js +28 -0
- package/dist/cjs/pureConstants.js.map +1 -0
- package/dist/cjs/review/decode.d.ts.map +1 -1
- package/dist/cjs/review/decode.js +35 -51
- package/dist/cjs/review/decode.js.map +1 -1
- package/dist/cjs/review/relayr.d.ts.map +1 -1
- package/dist/cjs/review/relayr.js +11 -31
- package/dist/cjs/review/relayr.js.map +1 -1
- package/dist/cjs/safe.d.ts +376 -8
- package/dist/cjs/safe.d.ts.map +1 -1
- package/dist/cjs/safe.js +1088 -29
- package/dist/cjs/safe.js.map +1 -1
- package/dist/cjs/safeService.d.ts +361 -1
- package/dist/cjs/safeService.d.ts.map +1 -1
- package/dist/cjs/safeService.js +844 -6
- package/dist/cjs/safeService.js.map +1 -1
- package/dist/cjs/untrusted.d.ts +8 -0
- package/dist/cjs/untrusted.d.ts.map +1 -0
- package/dist/cjs/untrusted.js +33 -0
- package/dist/cjs/untrusted.js.map +1 -0
- package/dist/cjs/v6/deploymentAbi.d.ts +4 -0
- package/dist/cjs/v6/deploymentAbi.d.ts.map +1 -0
- package/dist/cjs/v6/deploymentAbi.js +15 -0
- package/dist/cjs/v6/deploymentAbi.js.map +1 -0
- package/dist/cjs/v6/deploymentDiagnostics.d.ts +45 -0
- package/dist/cjs/v6/deploymentDiagnostics.d.ts.map +1 -0
- package/dist/cjs/v6/deploymentDiagnostics.js +525 -0
- package/dist/cjs/v6/deploymentDiagnostics.js.map +1 -0
- package/dist/cjs/v6/distributions.d.ts +90 -0
- package/dist/cjs/v6/distributions.d.ts.map +1 -0
- package/dist/cjs/v6/distributions.js +287 -0
- package/dist/cjs/v6/distributions.js.map +1 -0
- package/dist/cjs/v6/index.d.ts +4 -0
- package/dist/cjs/v6/index.d.ts.map +1 -1
- package/dist/cjs/v6/index.js +4 -0
- package/dist/cjs/v6/index.js.map +1 -1
- package/dist/cjs/v6/nft.d.ts +15 -0
- package/dist/cjs/v6/nft.d.ts.map +1 -1
- package/dist/cjs/v6/nft.js +49 -29
- package/dist/cjs/v6/nft.js.map +1 -1
- package/dist/cjs/v6/omnichain.d.ts +3 -2
- package/dist/cjs/v6/omnichain.d.ts.map +1 -1
- package/dist/cjs/v6/omnichain.js +9 -4
- package/dist/cjs/v6/omnichain.js.map +1 -1
- package/dist/cjs/v6/permissionIds.d.ts +62 -0
- package/dist/cjs/v6/permissionIds.d.ts.map +1 -0
- package/dist/cjs/v6/permissionIds.js +70 -0
- package/dist/cjs/v6/permissionIds.js.map +1 -0
- package/dist/cjs/v6/permissions.d.ts +3 -62
- package/dist/cjs/v6/permissions.d.ts.map +1 -1
- package/dist/cjs/v6/permissions.js +6 -67
- package/dist/cjs/v6/permissions.js.map +1 -1
- package/dist/cjs/v6/revnet721.d.ts +42 -0
- package/dist/cjs/v6/revnet721.d.ts.map +1 -0
- package/dist/cjs/v6/revnet721.js +74 -0
- package/dist/cjs/v6/revnet721.js.map +1 -0
- package/dist/cjs/v6/revnets.d.ts +11 -3
- package/dist/cjs/v6/revnets.d.ts.map +1 -1
- package/dist/cjs/v6/revnets.js +36 -7
- package/dist/cjs/v6/revnets.js.map +1 -1
- package/dist/cjs/v6/splits.d.ts +1 -1
- package/dist/cjs/v6/splits.d.ts.map +1 -1
- package/dist/cjs/v6/splits.js +3 -3
- package/dist/cjs/v6/splits.js.map +1 -1
- package/dist/cjs/v6/sticky.d.ts +2 -2
- package/dist/cjs/v6/sticky.d.ts.map +1 -1
- package/dist/cjs/v6/types.d.ts +1 -1
- package/dist/cjs/v6/types.d.ts.map +1 -1
- package/dist/esm/constants.d.ts +1 -13
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +1 -21
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/generated/abi/jbBuybackHookAbi.d.ts +1187 -0
- package/dist/esm/generated/abi/jbBuybackHookAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbBuybackHookAbi.js +838 -0
- package/dist/esm/generated/abi/jbBuybackHookAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.d.ts +791 -0
- package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.js +629 -0
- package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbControllerAbi.d.ts +2569 -0
- package/dist/esm/generated/abi/jbControllerAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbControllerAbi.js +1903 -0
- package/dist/esm/generated/abi/jbControllerAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbDirectoryAbi.d.ts +410 -0
- package/dist/esm/generated/abi/jbDirectoryAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbDirectoryAbi.js +362 -0
- package/dist/esm/generated/abi/jbDirectoryAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbMultiTerminalAbi.d.ts +1863 -0
- package/dist/esm/generated/abi/jbMultiTerminalAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbMultiTerminalAbi.js +1456 -0
- package/dist/esm/generated/abi/jbMultiTerminalAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbPermissionsAbi.d.ts +240 -0
- package/dist/esm/generated/abi/jbPermissionsAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbPermissionsAbi.js +161 -0
- package/dist/esm/generated/abi/jbPermissionsAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbProjectsAbi.d.ts +626 -0
- package/dist/esm/generated/abi/jbProjectsAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbProjectsAbi.js +467 -0
- package/dist/esm/generated/abi/jbProjectsAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.d.ts +1503 -0
- package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.js +1034 -0
- package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.d.ts +1119 -0
- package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.js +922 -0
- package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbSplitsAbi.d.ts +244 -0
- package/dist/esm/generated/abi/jbSplitsAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbSplitsAbi.js +195 -0
- package/dist/esm/generated/abi/jbSplitsAbi.js.map +1 -0
- package/dist/esm/generated/abi/jbTokensAbi.d.ts +602 -0
- package/dist/esm/generated/abi/jbTokensAbi.d.ts.map +1 -0
- package/dist/esm/generated/abi/jbTokensAbi.js +498 -0
- package/dist/esm/generated/abi/jbTokensAbi.js.map +1 -0
- package/dist/esm/generated/juicebox.d.ts +25405 -36537
- package/dist/esm/generated/juicebox.d.ts.map +1 -1
- package/dist/esm/generated/juicebox.js +14634 -23044
- package/dist/esm/generated/juicebox.js.map +1 -1
- package/dist/esm/pureConstants.d.ts +15 -0
- package/dist/esm/pureConstants.d.ts.map +1 -0
- package/dist/esm/pureConstants.js +25 -0
- package/dist/esm/pureConstants.js.map +1 -0
- package/dist/esm/review/decode.d.ts.map +1 -1
- package/dist/esm/review/decode.js +21 -37
- package/dist/esm/review/decode.js.map +1 -1
- package/dist/esm/review/relayr.d.ts.map +1 -1
- package/dist/esm/review/relayr.js +5 -25
- package/dist/esm/review/relayr.js.map +1 -1
- package/dist/esm/safe.d.ts +376 -8
- package/dist/esm/safe.d.ts.map +1 -1
- package/dist/esm/safe.js +1070 -29
- package/dist/esm/safe.js.map +1 -1
- package/dist/esm/safeService.d.ts +361 -1
- package/dist/esm/safeService.d.ts.map +1 -1
- package/dist/esm/safeService.js +818 -5
- package/dist/esm/safeService.js.map +1 -1
- package/dist/esm/untrusted.d.ts +8 -0
- package/dist/esm/untrusted.d.ts.map +1 -0
- package/dist/esm/untrusted.js +28 -0
- package/dist/esm/untrusted.js.map +1 -0
- package/dist/esm/v6/deploymentAbi.d.ts +4 -0
- package/dist/esm/v6/deploymentAbi.d.ts.map +1 -0
- package/dist/esm/v6/deploymentAbi.js +12 -0
- package/dist/esm/v6/deploymentAbi.js.map +1 -0
- package/dist/esm/v6/deploymentDiagnostics.d.ts +45 -0
- package/dist/esm/v6/deploymentDiagnostics.d.ts.map +1 -0
- package/dist/esm/v6/deploymentDiagnostics.js +521 -0
- package/dist/esm/v6/deploymentDiagnostics.js.map +1 -0
- package/dist/esm/v6/distributions.d.ts +90 -0
- package/dist/esm/v6/distributions.d.ts.map +1 -0
- package/dist/esm/v6/distributions.js +283 -0
- package/dist/esm/v6/distributions.js.map +1 -0
- package/dist/esm/v6/index.d.ts +4 -0
- package/dist/esm/v6/index.d.ts.map +1 -1
- package/dist/esm/v6/index.js +4 -0
- package/dist/esm/v6/index.js.map +1 -1
- package/dist/esm/v6/nft.d.ts +15 -0
- package/dist/esm/v6/nft.d.ts.map +1 -1
- package/dist/esm/v6/nft.js +48 -29
- package/dist/esm/v6/nft.js.map +1 -1
- package/dist/esm/v6/omnichain.d.ts +3 -2
- package/dist/esm/v6/omnichain.d.ts.map +1 -1
- package/dist/esm/v6/omnichain.js +9 -4
- package/dist/esm/v6/omnichain.js.map +1 -1
- package/dist/esm/v6/permissionIds.d.ts +62 -0
- package/dist/esm/v6/permissionIds.d.ts.map +1 -0
- package/dist/esm/v6/permissionIds.js +66 -0
- package/dist/esm/v6/permissionIds.js.map +1 -0
- package/dist/esm/v6/permissions.d.ts +3 -62
- package/dist/esm/v6/permissions.d.ts.map +1 -1
- package/dist/esm/v6/permissions.js +2 -64
- package/dist/esm/v6/permissions.js.map +1 -1
- package/dist/esm/v6/revnet721.d.ts +42 -0
- package/dist/esm/v6/revnet721.d.ts.map +1 -0
- package/dist/esm/v6/revnet721.js +70 -0
- package/dist/esm/v6/revnet721.js.map +1 -0
- package/dist/esm/v6/revnets.d.ts +11 -3
- package/dist/esm/v6/revnets.d.ts.map +1 -1
- package/dist/esm/v6/revnets.js +36 -7
- package/dist/esm/v6/revnets.js.map +1 -1
- package/dist/esm/v6/splits.d.ts +1 -1
- package/dist/esm/v6/splits.d.ts.map +1 -1
- package/dist/esm/v6/splits.js +1 -1
- package/dist/esm/v6/splits.js.map +1 -1
- package/dist/esm/v6/sticky.d.ts +2 -2
- package/dist/esm/v6/sticky.d.ts.map +1 -1
- package/dist/esm/v6/types.d.ts +1 -1
- package/dist/esm/v6/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cjs/safeService.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SAFE_NONCE_GUIDANCE = exports.SAFE_SERVICE_PREFIX = exports.SAFE_PREFIX = void 0;
|
|
3
|
+
exports.SAFE_EXEC_ABI = exports.SAFE_TX_TYPES = exports.SAFE_NONCE_GUIDANCE = exports.SAFE_SERVICE_PREFIX = exports.SAFE_PREFIX = void 0;
|
|
4
4
|
exports.safeServiceBase = safeServiceBase;
|
|
5
5
|
exports.swapDeadline = swapDeadline;
|
|
6
6
|
exports.safeQueueUrl = safeQueueUrl;
|
|
7
7
|
exports.isSafeWalletPeer = isSafeWalletPeer;
|
|
8
8
|
exports.waitForSafeExecutionHash = waitForSafeExecutionHash;
|
|
9
|
+
exports.safeTransactionMessage = safeTransactionMessage;
|
|
10
|
+
exports.safeTransactionHash = safeTransactionHash;
|
|
11
|
+
exports.canonicalSafeTxHash = canonicalSafeTxHash;
|
|
12
|
+
exports.safeExecutionResult = safeExecutionResult;
|
|
13
|
+
exports.requireSafeExecutionSuccess = requireSafeExecutionSuccess;
|
|
14
|
+
exports.usableSafeConfirmations = usableSafeConfirmations;
|
|
15
|
+
exports.safeExecutionSignatures = safeExecutionSignatures;
|
|
16
|
+
exports.safeExecutionArgs = safeExecutionArgs;
|
|
17
|
+
exports.safeTransactionHasRefund = safeTransactionHasRefund;
|
|
18
|
+
exports.safeProposalFor = safeProposalFor;
|
|
19
|
+
exports.safeBatchProposalFor = safeBatchProposalFor;
|
|
20
|
+
exports.safeTransactionMatchesCall = safeTransactionMatchesCall;
|
|
21
|
+
exports.nextProposalNonce = nextProposalNonce;
|
|
22
|
+
exports.onchainApprovalStep = onchainApprovalStep;
|
|
23
|
+
exports.hasSafeService = hasSafeService;
|
|
24
|
+
exports.safeTransactionUrl = safeTransactionUrl;
|
|
25
|
+
exports.listPendingSafeTransactions = listPendingSafeTransactions;
|
|
26
|
+
exports.findPendingSafeTransaction = findPendingSafeTransaction;
|
|
27
|
+
exports.readSafeTransaction = readSafeTransaction;
|
|
28
|
+
exports.proposeSafeTransaction = proposeSafeTransaction;
|
|
29
|
+
exports.submitSafeConfirmation = submitSafeConfirmation;
|
|
30
|
+
exports.fetchSafesOwnedBy = fetchSafesOwnedBy;
|
|
31
|
+
exports.safeCreationUrl = safeCreationUrl;
|
|
32
|
+
exports.parseSafeCreationPayload = parseSafeCreationPayload;
|
|
33
|
+
exports.fetchSafeCreation = fetchSafeCreation;
|
|
34
|
+
const viem_1 = require("viem");
|
|
35
|
+
const safe_js_1 = require("./safe.js");
|
|
36
|
+
const untrusted_js_1 = require("./untrusted.js");
|
|
9
37
|
/**
|
|
10
38
|
* Safe's per-chain app URL prefix. Wider than {@link SAFE_SERVICE_PREFIX}:
|
|
11
39
|
* Safe app links work on chains without a hosted transaction service.
|
|
@@ -93,6 +121,9 @@ function isSafeWalletPeer(url) {
|
|
|
93
121
|
function noHostedService(chainId) {
|
|
94
122
|
return new Error(`Safe does not host a transaction service on chain ${chainId}, so this proposal cannot be tracked here. Execute it from the Safe app; the action takes effect once it is executed there.`);
|
|
95
123
|
}
|
|
124
|
+
/** A service answer the wait cannot get past: it ends the wait, unlike a network error. */
|
|
125
|
+
class SafeExecutionRecordError extends Error {
|
|
126
|
+
}
|
|
96
127
|
/**
|
|
97
128
|
* Resolve a Safe proposal identifier to its actual onchain execution hash.
|
|
98
129
|
* A safeTxHash is not a transaction hash and must never be receipt-polled.
|
|
@@ -107,6 +138,9 @@ function noHostedService(chainId) {
|
|
|
107
138
|
* the execution's own hash when the owner executes at once.
|
|
108
139
|
*/
|
|
109
140
|
async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
|
|
141
|
+
if (!(0, untrusted_js_1.isBytes32)(safeTxHash)) {
|
|
142
|
+
throw new Error(`Invalid Safe proposal hash: ${String(safeTxHash)}.`);
|
|
143
|
+
}
|
|
110
144
|
const base = safeServiceBase(chainId);
|
|
111
145
|
const { client } = options;
|
|
112
146
|
if (!base && !client)
|
|
@@ -135,24 +169,25 @@ async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
|
|
|
135
169
|
consecutiveNotFound = 0;
|
|
136
170
|
const transaction = (await response.json());
|
|
137
171
|
if (transaction.isExecuted && transaction.isSuccessful === false) {
|
|
138
|
-
throw new
|
|
172
|
+
throw new SafeExecutionRecordError("Safe executed the proposal, but the onchain transaction failed.");
|
|
139
173
|
}
|
|
140
174
|
if (transaction.isExecuted && transaction.transactionHash) {
|
|
175
|
+
if (!(0, untrusted_js_1.isBytes32)(transaction.transactionHash)) {
|
|
176
|
+
throw new SafeExecutionRecordError(`Safe’s transaction service reported ${safeTxHash} executed in a malformed transaction hash: ${String(transaction.transactionHash)}. Check the proposal in the Safe app.`);
|
|
177
|
+
}
|
|
141
178
|
return transaction.transactionHash;
|
|
142
179
|
}
|
|
143
180
|
}
|
|
144
181
|
else if (response.status === 404) {
|
|
145
182
|
consecutiveNotFound += 1;
|
|
146
183
|
if (consecutiveNotFound >= SAFE_EXECUTION_NOT_FOUND_LIMIT) {
|
|
147
|
-
throw new
|
|
184
|
+
throw new SafeExecutionRecordError("Safe’s transaction service has no record of this proposal. Tracking cannot continue here — check the proposal in the Safe app; if it exists there, it will still take effect once executed.");
|
|
148
185
|
}
|
|
149
186
|
}
|
|
150
187
|
}
|
|
151
188
|
catch (error) {
|
|
152
|
-
if (error instanceof
|
|
153
|
-
/executed the proposal.*failed|no record of this proposal/i.test(error.message)) {
|
|
189
|
+
if (error instanceof SafeExecutionRecordError)
|
|
154
190
|
throw error;
|
|
155
|
-
}
|
|
156
191
|
// Other service/network failures are transient. Keep the already-created
|
|
157
192
|
// proposal pending instead of inviting a duplicate submission.
|
|
158
193
|
}
|
|
@@ -172,4 +207,807 @@ async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
|
|
|
172
207
|
});
|
|
173
208
|
}
|
|
174
209
|
}
|
|
210
|
+
// ── Safe transactions ────────────────────────────────────────────────────────
|
|
211
|
+
/** The EIP-712 types a Safe transaction is signed and hashed with. */
|
|
212
|
+
exports.SAFE_TX_TYPES = {
|
|
213
|
+
SafeTx: [
|
|
214
|
+
{ name: "to", type: "address" },
|
|
215
|
+
{ name: "value", type: "uint256" },
|
|
216
|
+
{ name: "data", type: "bytes" },
|
|
217
|
+
{ name: "operation", type: "uint8" },
|
|
218
|
+
{ name: "safeTxGas", type: "uint256" },
|
|
219
|
+
{ name: "baseGas", type: "uint256" },
|
|
220
|
+
{ name: "gasPrice", type: "uint256" },
|
|
221
|
+
{ name: "gasToken", type: "address" },
|
|
222
|
+
{ name: "refundReceiver", type: "address" },
|
|
223
|
+
{ name: "nonce", type: "uint256" },
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
/** `execTransaction` and `approveHash`, and the execution events of Safe 1.4. */
|
|
227
|
+
exports.SAFE_EXEC_ABI = [
|
|
228
|
+
{
|
|
229
|
+
type: "function",
|
|
230
|
+
name: "execTransaction",
|
|
231
|
+
stateMutability: "payable",
|
|
232
|
+
inputs: [
|
|
233
|
+
{ name: "to", type: "address" },
|
|
234
|
+
{ name: "value", type: "uint256" },
|
|
235
|
+
{ name: "data", type: "bytes" },
|
|
236
|
+
{ name: "operation", type: "uint8" },
|
|
237
|
+
{ name: "safeTxGas", type: "uint256" },
|
|
238
|
+
{ name: "baseGas", type: "uint256" },
|
|
239
|
+
{ name: "gasPrice", type: "uint256" },
|
|
240
|
+
{ name: "gasToken", type: "address" },
|
|
241
|
+
{ name: "refundReceiver", type: "address" },
|
|
242
|
+
{ name: "signatures", type: "bytes" },
|
|
243
|
+
],
|
|
244
|
+
outputs: [{ name: "success", type: "bool" }],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: "function",
|
|
248
|
+
name: "approveHash",
|
|
249
|
+
stateMutability: "nonpayable",
|
|
250
|
+
inputs: [{ name: "hashToApprove", type: "bytes32" }],
|
|
251
|
+
outputs: [],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: "event",
|
|
255
|
+
name: "ExecutionSuccess",
|
|
256
|
+
anonymous: false,
|
|
257
|
+
inputs: [
|
|
258
|
+
{ name: "txHash", type: "bytes32", indexed: true },
|
|
259
|
+
{ name: "payment", type: "uint256", indexed: false },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
type: "event",
|
|
264
|
+
name: "ExecutionFailure",
|
|
265
|
+
anonymous: false,
|
|
266
|
+
inputs: [
|
|
267
|
+
{ name: "txHash", type: "bytes32", indexed: true },
|
|
268
|
+
{ name: "payment", type: "uint256", indexed: false },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
function invalidField(name, value) {
|
|
273
|
+
return new Error(`The Safe transaction's ${name} is invalid: ${String(value)}.`);
|
|
274
|
+
}
|
|
275
|
+
function fieldAddress(name, value) {
|
|
276
|
+
if (typeof value !== "string" || !(0, viem_1.isAddress)(value)) {
|
|
277
|
+
throw invalidField(name, value);
|
|
278
|
+
}
|
|
279
|
+
return (0, viem_1.getAddress)(value);
|
|
280
|
+
}
|
|
281
|
+
function fieldUint(name, value) {
|
|
282
|
+
const parsed = (0, untrusted_js_1.uint256)(value);
|
|
283
|
+
if (parsed === null)
|
|
284
|
+
throw invalidField(name, value);
|
|
285
|
+
return parsed;
|
|
286
|
+
}
|
|
287
|
+
/** A nonce as a number: apps order and compare them. */
|
|
288
|
+
function nonceOf(value) {
|
|
289
|
+
const parsed = (0, untrusted_js_1.uint256)(value);
|
|
290
|
+
if (parsed === null || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
291
|
+
throw invalidField("nonce", value);
|
|
292
|
+
}
|
|
293
|
+
return Number(parsed);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The exact EIP-712 message of `tx`. Every field is required and read
|
|
297
|
+
* strictly: addresses checksummed, amounts as uint256 in any form, operation 0
|
|
298
|
+
* (CALL) or 1 (DELEGATECALL), null data as empty, and a null gas token or
|
|
299
|
+
* refund receiver as the zero address. Throws naming the first field that is
|
|
300
|
+
* not.
|
|
301
|
+
*/
|
|
302
|
+
function safeTransactionMessage(tx) {
|
|
303
|
+
if (!tx || typeof tx !== "object")
|
|
304
|
+
throw invalidField("record", tx);
|
|
305
|
+
const operation = fieldUint("operation", tx.operation);
|
|
306
|
+
if (operation > 1n)
|
|
307
|
+
throw invalidField("operation", tx.operation);
|
|
308
|
+
const data = tx.data ?? "0x";
|
|
309
|
+
if (!(0, untrusted_js_1.isHexBytes)(data))
|
|
310
|
+
throw invalidField("data", tx.data);
|
|
311
|
+
return {
|
|
312
|
+
to: fieldAddress("to", tx.to),
|
|
313
|
+
value: fieldUint("value", tx.value),
|
|
314
|
+
data,
|
|
315
|
+
operation: operation === 1n ? 1 : 0,
|
|
316
|
+
safeTxGas: fieldUint("safeTxGas", tx.safeTxGas),
|
|
317
|
+
baseGas: fieldUint("baseGas", tx.baseGas),
|
|
318
|
+
gasPrice: fieldUint("gasPrice", tx.gasPrice),
|
|
319
|
+
// The service stores an omitted gas token or refund receiver as null and
|
|
320
|
+
// hashes it as the zero address, as Safe does; nothing else may be absent.
|
|
321
|
+
gasToken: tx.gasToken === null
|
|
322
|
+
? viem_1.zeroAddress
|
|
323
|
+
: fieldAddress("gasToken", tx.gasToken),
|
|
324
|
+
refundReceiver: tx.refundReceiver === null
|
|
325
|
+
? viem_1.zeroAddress
|
|
326
|
+
: fieldAddress("refundReceiver", tx.refundReceiver),
|
|
327
|
+
nonce: BigInt(nonceOf(tx.nonce)),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function requireChainId(chainId) {
|
|
331
|
+
if (!Number.isSafeInteger(chainId) || chainId <= 0) {
|
|
332
|
+
throw new Error(`Invalid chain ID: ${String(chainId)}.`);
|
|
333
|
+
}
|
|
334
|
+
return chainId;
|
|
335
|
+
}
|
|
336
|
+
function requireSafe(safe) {
|
|
337
|
+
if (typeof safe !== "string" || !(0, viem_1.isAddress)(safe)) {
|
|
338
|
+
throw new Error(`Invalid Safe address: ${String(safe)}.`);
|
|
339
|
+
}
|
|
340
|
+
return (0, viem_1.getAddress)(safe);
|
|
341
|
+
}
|
|
342
|
+
/** The EIP-712 hash `safe` signs and executes `tx` under on `chainId`. */
|
|
343
|
+
function safeTransactionHash(chainId, safe, tx) {
|
|
344
|
+
return (0, viem_1.hashTypedData)({
|
|
345
|
+
domain: {
|
|
346
|
+
chainId: requireChainId(chainId),
|
|
347
|
+
verifyingContract: requireSafe(safe),
|
|
348
|
+
},
|
|
349
|
+
types: exports.SAFE_TX_TYPES,
|
|
350
|
+
primaryType: "SafeTx",
|
|
351
|
+
message: safeTransactionMessage(tx),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The hash of `tx`'s exact fields, refusing a record that names another Safe,
|
|
356
|
+
* advertises a different `safeTxHash` or `contractTransactionHash`, or, given
|
|
357
|
+
* `expected`, no longer hashes to the reviewed hash.
|
|
358
|
+
*/
|
|
359
|
+
function canonicalSafeTxHash(chainId, safe, tx, expected) {
|
|
360
|
+
const address = requireSafe(safe);
|
|
361
|
+
if (tx?.safe !== undefined &&
|
|
362
|
+
tx.safe !== null &&
|
|
363
|
+
(typeof tx.safe !== "string" ||
|
|
364
|
+
!(0, viem_1.isAddress)(tx.safe) ||
|
|
365
|
+
!(0, viem_1.isAddressEqual)(tx.safe, address))) {
|
|
366
|
+
throw new Error(`The Safe transaction belongs to ${String(tx.safe)}, not ${address}.`);
|
|
367
|
+
}
|
|
368
|
+
const computed = safeTransactionHash(chainId, address, tx);
|
|
369
|
+
for (const [name, advertised] of [
|
|
370
|
+
["safeTxHash", tx.safeTxHash],
|
|
371
|
+
["contractTransactionHash", tx.contractTransactionHash],
|
|
372
|
+
]) {
|
|
373
|
+
if (advertised === undefined || advertised === null)
|
|
374
|
+
continue;
|
|
375
|
+
if (!(0, untrusted_js_1.isBytes32)(advertised) ||
|
|
376
|
+
advertised.toLowerCase() !== computed.toLowerCase()) {
|
|
377
|
+
throw new Error(`The Safe transaction's ${name} ${String(advertised)} does not match its fields, which hash to ${computed}.`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (expected !== undefined && !(0, untrusted_js_1.isBytes32)(expected)) {
|
|
381
|
+
throw new Error(`Invalid reviewed Safe transaction hash: ${String(expected)}.`);
|
|
382
|
+
}
|
|
383
|
+
if (expected !== undefined &&
|
|
384
|
+
expected.toLowerCase() !== computed.toLowerCase()) {
|
|
385
|
+
throw new Error(`The Safe transaction changed: it hashes to ${computed}, not the reviewed ${expected}.`);
|
|
386
|
+
}
|
|
387
|
+
return computed;
|
|
388
|
+
}
|
|
389
|
+
// ── Execution results ────────────────────────────────────────────────────────
|
|
390
|
+
/** `ExecutionSuccess(bytes32,uint256)` and `ExecutionFailure(bytes32,uint256)`, the same in Safe 1.3 and 1.4. */
|
|
391
|
+
const EXECUTION_SUCCESS_TOPIC = "0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e";
|
|
392
|
+
const EXECUTION_FAILURE_TOPIC = "0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23";
|
|
393
|
+
const ZERO_WORD = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
394
|
+
/**
|
|
395
|
+
* A Safe execution event in exactly the Safe 1.4 layout (txHash indexed) or
|
|
396
|
+
* the Safe 1.3 layout (txHash the first data word), with its payment. Null for
|
|
397
|
+
* another event; a string naming what is wrong for a malformed one.
|
|
398
|
+
*/
|
|
399
|
+
function safeExecutionEvent(log) {
|
|
400
|
+
const topics = Array.isArray(log.topics) ? log.topics : [];
|
|
401
|
+
const topic = typeof topics[0] === "string" ? topics[0].toLowerCase() : "";
|
|
402
|
+
if (topic !== EXECUTION_SUCCESS_TOPIC && topic !== EXECUTION_FAILURE_TOPIC) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const failed = topic === EXECUTION_FAILURE_TOPIC;
|
|
406
|
+
const name = failed ? "ExecutionFailure" : "ExecutionSuccess";
|
|
407
|
+
const data = typeof log.data === "string" ? log.data.toLowerCase() : "";
|
|
408
|
+
if (topics.length === 2) {
|
|
409
|
+
if (!(0, untrusted_js_1.isBytes32)(topics[1]) || !/^0x[\da-f]{64}$/.test(data)) {
|
|
410
|
+
return `a malformed ${name} event`;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
failed,
|
|
414
|
+
txHash: topics[1].toLowerCase(),
|
|
415
|
+
payment: BigInt(data),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (topics.length === 1) {
|
|
419
|
+
if (!/^0x[\da-f]{128}$/.test(data))
|
|
420
|
+
return `a malformed ${name} event`;
|
|
421
|
+
return {
|
|
422
|
+
failed,
|
|
423
|
+
txHash: data.slice(0, 66),
|
|
424
|
+
payment: BigInt(`0x${data.slice(66)}`),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return `a malformed ${name} event`;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* What `receipt` proves about the Safe transaction `hash` on `safe`. `hash` is
|
|
431
|
+
* what the wallet returned: the safeTxHash, or, when Safe{Wallet} executed the
|
|
432
|
+
* proposal at once, the execution's own transaction hash. Then the receipt
|
|
433
|
+
* must carry exactly one execution event of `safe`, whatever its hash.
|
|
434
|
+
* Otherwise exactly one event for `hash`, so another proposal in the same
|
|
435
|
+
* batch cannot stand in for this one, and another proposal's event, refund
|
|
436
|
+
* included, never decides this one. Any malformed execution event of `safe`
|
|
437
|
+
* leaves the result unproven, since it cannot be told apart from this one.
|
|
438
|
+
*
|
|
439
|
+
* `receipt` is viem's parsed receipt: a raw RPC receipt (status "0x1") reads
|
|
440
|
+
* unproven.
|
|
441
|
+
*
|
|
442
|
+
* The at-once reading proves only that the Safe ran one transaction in that
|
|
443
|
+
* receipt. A caller who holds the executing transaction's input should also
|
|
444
|
+
* bind it to the reviewed call, as an `execTransaction` with exactly the
|
|
445
|
+
* reviewed fields.
|
|
446
|
+
*/
|
|
447
|
+
function safeExecutionResult(receipt, safe, hash) {
|
|
448
|
+
const address = requireSafe(safe).toLowerCase();
|
|
449
|
+
if (!(0, untrusted_js_1.isBytes32)(hash)) {
|
|
450
|
+
throw new Error(`Invalid Safe transaction hash: ${String(hash)}.`);
|
|
451
|
+
}
|
|
452
|
+
if (receipt?.status === "reverted")
|
|
453
|
+
return { status: "reverted" };
|
|
454
|
+
const unproven = (reason) => ({
|
|
455
|
+
status: "unproven",
|
|
456
|
+
reason,
|
|
457
|
+
});
|
|
458
|
+
if (receipt?.status !== "success" || !Array.isArray(receipt.logs)) {
|
|
459
|
+
return unproven(`The receipt has no success status and logs to prove ${hash} on Safe ${safe}.`);
|
|
460
|
+
}
|
|
461
|
+
const atOnce = typeof receipt.transactionHash === "string" &&
|
|
462
|
+
receipt.transactionHash.toLowerCase() === hash.toLowerCase();
|
|
463
|
+
const events = [];
|
|
464
|
+
for (const log of receipt.logs) {
|
|
465
|
+
if (typeof log?.address !== "string" ||
|
|
466
|
+
log.address.toLowerCase() !== address) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const event = safeExecutionEvent(log);
|
|
470
|
+
if (typeof event === "string") {
|
|
471
|
+
return unproven(`Safe ${safe} logged ${event}.`);
|
|
472
|
+
}
|
|
473
|
+
if (event && (atOnce || event.txHash === hash.toLowerCase())) {
|
|
474
|
+
events.push(event);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const subject = atOnce ? `transaction ${hash}` : `Safe transaction ${hash}`;
|
|
478
|
+
if (events.length === 0) {
|
|
479
|
+
return unproven(`The receipt has no ExecutionSuccess or ExecutionFailure from Safe ${safe} for ${subject}.`);
|
|
480
|
+
}
|
|
481
|
+
if (events.length > 1) {
|
|
482
|
+
return unproven(`Safe ${safe} logged ${events.length} execution results (${events.map((event) => (event.failed ? "ExecutionFailure" : "ExecutionSuccess")).join(", ")}) for ${subject}, so the receipt proves none of them.`);
|
|
483
|
+
}
|
|
484
|
+
const [event] = events;
|
|
485
|
+
return {
|
|
486
|
+
status: event.failed ? "failed" : "success",
|
|
487
|
+
payment: event.payment,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/** Throws unless `receipt` proves the Safe transaction `hash` ran and succeeded ({@link safeExecutionResult}). */
|
|
491
|
+
function requireSafeExecutionSuccess(receipt, safe, hash) {
|
|
492
|
+
const result = safeExecutionResult(receipt, safe, hash);
|
|
493
|
+
if (result.status === "success")
|
|
494
|
+
return;
|
|
495
|
+
if (result.status === "reverted") {
|
|
496
|
+
throw new Error(`The transaction executing ${hash} on Safe ${safe} reverted, so the Safe ran nothing.`);
|
|
497
|
+
}
|
|
498
|
+
if (result.status === "failed") {
|
|
499
|
+
throw new Error(`Safe ${safe} ran ${hash}, but its call failed (ExecutionFailure).`);
|
|
500
|
+
}
|
|
501
|
+
throw new Error(result.reason);
|
|
502
|
+
}
|
|
503
|
+
// ── Signatures ───────────────────────────────────────────────────────────────
|
|
504
|
+
/**
|
|
505
|
+
* One confirmation's part of Safe's `signatures` bytes, without 0x:
|
|
506
|
+
*
|
|
507
|
+
* - none at all: the `approveHash` form (v = 1) naming the owner, a head;
|
|
508
|
+
* - an ECDSA, eth_sign or approved-hash signature: exactly 65 bytes, a head;
|
|
509
|
+
* an approved hash (v = 1) must name its own owner;
|
|
510
|
+
* - an EIP-1271 contract signature in the transaction service's standalone
|
|
511
|
+
* form: r = owner, s = 65, v = 0, then a length word and the signature,
|
|
512
|
+
* optionally zero-padded to a word. Its dynamic part becomes a tail that
|
|
513
|
+
* {@link safeExecutionSignatures} places after every head.
|
|
514
|
+
*
|
|
515
|
+
* Null for anything else, which never counts as a confirmation.
|
|
516
|
+
*/
|
|
517
|
+
function signaturePart(owner, signature) {
|
|
518
|
+
const word = owner.slice(2).toLowerCase().padStart(64, "0");
|
|
519
|
+
if (signature === undefined || signature === null || signature === "") {
|
|
520
|
+
return { head: `${word}${ZERO_WORD}01` };
|
|
521
|
+
}
|
|
522
|
+
if (!(0, untrusted_js_1.isHexBytes)(signature) || signature.length < 132)
|
|
523
|
+
return null;
|
|
524
|
+
const bytes = signature.slice(2).toLowerCase();
|
|
525
|
+
const v = bytes.slice(128, 130);
|
|
526
|
+
if (v !== "00") {
|
|
527
|
+
if (bytes.length !== 130 || (v === "01" && bytes.slice(0, 64) !== word)) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
return { head: bytes };
|
|
531
|
+
}
|
|
532
|
+
if (bytes.length < 194 ||
|
|
533
|
+
bytes.slice(0, 64) !== word ||
|
|
534
|
+
BigInt(`0x${bytes.slice(64, 128)}`) !== 65n) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const end = 194n + 2n * BigInt(`0x${bytes.slice(130, 194)}`);
|
|
538
|
+
if (end > BigInt(bytes.length) ||
|
|
539
|
+
BigInt(bytes.length) - end >= 64n ||
|
|
540
|
+
/[^0]/.test(bytes.slice(Number(end)))) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
return { contract: bytes.slice(194, Number(end)) };
|
|
544
|
+
}
|
|
545
|
+
function requireOwners(owners) {
|
|
546
|
+
if (!Array.isArray(owners)) {
|
|
547
|
+
throw new Error(`Invalid Safe owners: ${String(owners)}.`);
|
|
548
|
+
}
|
|
549
|
+
return new Set(owners.map((owner) => {
|
|
550
|
+
if (typeof owner !== "string" || !(0, viem_1.isAddress)(owner)) {
|
|
551
|
+
throw new Error(`Invalid Safe owner: ${String(owner)}.`);
|
|
552
|
+
}
|
|
553
|
+
return owner.toLowerCase();
|
|
554
|
+
}));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The confirmations of `tx` that count: one per current owner in `owners`,
|
|
558
|
+
* with a well-formed signature or none (an onchain approval), a signature
|
|
559
|
+
* preferred, in ascending numeric owner order as Safe requires (a string sort
|
|
560
|
+
* disagrees and reverts with GS026). Owners are checksummed.
|
|
561
|
+
*/
|
|
562
|
+
function usableSafeConfirmations(tx, owners) {
|
|
563
|
+
const allowed = requireOwners(owners);
|
|
564
|
+
const byOwner = new Map();
|
|
565
|
+
for (const confirmation of Array.isArray(tx?.confirmations)
|
|
566
|
+
? tx.confirmations
|
|
567
|
+
: []) {
|
|
568
|
+
const owner = confirmation?.owner;
|
|
569
|
+
if (typeof owner !== "string" ||
|
|
570
|
+
!(0, viem_1.isAddress)(owner) ||
|
|
571
|
+
!allowed.has(owner.toLowerCase()) ||
|
|
572
|
+
signaturePart(owner, confirmation.signature) === null) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const key = owner.toLowerCase();
|
|
576
|
+
const existing = byOwner.get(key);
|
|
577
|
+
if (!existing || (!existing.signature && confirmation.signature)) {
|
|
578
|
+
byOwner.set(key, {
|
|
579
|
+
owner: (0, viem_1.getAddress)(owner),
|
|
580
|
+
signature: confirmation.signature ?? null,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// Owners are unique here, so no two compare equal.
|
|
585
|
+
return [...byOwner.values()].sort((left, right) => BigInt(left.owner) < BigInt(right.owner) ? -1 : 1);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* The `signatures` bytes for `execTransaction`, as Safe's checkNSignatures
|
|
589
|
+
* reads them: one 65-byte head per {@link usableSafeConfirmations} entry, in
|
|
590
|
+
* owner order, then each contract signature's length and bytes. A contract
|
|
591
|
+
* signature's head points at its tail by byte offset, which is never below
|
|
592
|
+
* the heads' total length, so it passes Safe's GS021 check.
|
|
593
|
+
*/
|
|
594
|
+
function safeExecutionSignatures(tx, owners) {
|
|
595
|
+
const parts = usableSafeConfirmations(tx, owners).map(({ owner, signature }) => ({
|
|
596
|
+
owner,
|
|
597
|
+
part: signaturePart(owner, signature),
|
|
598
|
+
}));
|
|
599
|
+
let head = "";
|
|
600
|
+
let tail = "";
|
|
601
|
+
for (const { owner, part } of parts) {
|
|
602
|
+
if ("head" in part) {
|
|
603
|
+
head += part.head;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const offset = parts.length * 65 + tail.length / 2;
|
|
607
|
+
head += `${owner.slice(2).toLowerCase().padStart(64, "0")}${offset.toString(16).padStart(64, "0")}00`;
|
|
608
|
+
tail += `${(part.contract.length / 2).toString(16).padStart(64, "0")}${part.contract}`;
|
|
609
|
+
}
|
|
610
|
+
return `0x${head}${tail}`;
|
|
611
|
+
}
|
|
612
|
+
/** `execTransaction`'s arguments for `tx`, signed by its usable confirmations from `owners`. */
|
|
613
|
+
function safeExecutionArgs(tx, owners) {
|
|
614
|
+
const message = safeTransactionMessage(tx);
|
|
615
|
+
return [
|
|
616
|
+
message.to,
|
|
617
|
+
message.value,
|
|
618
|
+
message.data,
|
|
619
|
+
message.operation,
|
|
620
|
+
message.safeTxGas,
|
|
621
|
+
message.baseGas,
|
|
622
|
+
message.gasPrice,
|
|
623
|
+
message.gasToken,
|
|
624
|
+
message.refundReceiver,
|
|
625
|
+
safeExecutionSignatures(tx, owners),
|
|
626
|
+
];
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Whether executing `tx` pays its executor a refund out of the Safe: Safe pays
|
|
630
|
+
* one only when gasPrice is above zero (in the gas token, or ETH, to the refund
|
|
631
|
+
* receiver or the executor). This SDK never proposes one. A queue card shows
|
|
632
|
+
* or refuses such a transaction before executing it. Throws on a malformed
|
|
633
|
+
* record, as {@link safeTransactionMessage} does.
|
|
634
|
+
*/
|
|
635
|
+
function safeTransactionHasRefund(tx) {
|
|
636
|
+
return safeTransactionMessage(tx).gasPrice > 0n;
|
|
637
|
+
}
|
|
638
|
+
// ── Proposals ────────────────────────────────────────────────────────────────
|
|
639
|
+
/** The zero-refund Safe transaction this SDK proposes: one CALL, no gas fields. */
|
|
640
|
+
function safeProposalFor(call, nonce) {
|
|
641
|
+
const tx = {
|
|
642
|
+
to: fieldAddress("to", call?.to),
|
|
643
|
+
value: fieldUint("value", call?.value ?? 0n).toString(),
|
|
644
|
+
data: call?.data,
|
|
645
|
+
operation: 0,
|
|
646
|
+
safeTxGas: "0",
|
|
647
|
+
baseGas: "0",
|
|
648
|
+
gasPrice: "0",
|
|
649
|
+
gasToken: viem_1.zeroAddress,
|
|
650
|
+
refundReceiver: viem_1.zeroAddress,
|
|
651
|
+
nonce: nonceOf(nonce),
|
|
652
|
+
confirmations: [],
|
|
653
|
+
};
|
|
654
|
+
safeTransactionMessage(tx);
|
|
655
|
+
return tx;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* One Safe transaction for a batch: a DELEGATECALL into MultiSendCallOnly,
|
|
659
|
+
* which runs each call from the Safe, in order, or reverts them all.
|
|
660
|
+
*/
|
|
661
|
+
function safeBatchProposalFor(calls, nonce) {
|
|
662
|
+
return {
|
|
663
|
+
...safeProposalFor({ to: safe_js_1.MULTI_SEND_CALL_ONLY, data: (0, safe_js_1.encodeMultiSend)(calls) }, nonce),
|
|
664
|
+
operation: 1,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Whether `tx` is exactly `call` as a zero-refund proposal: the same target,
|
|
669
|
+
* value, data and operation, and no safeTxGas, baseGas, gasPrice, gas token or
|
|
670
|
+
* refund receiver. A malformed `tx` never matches.
|
|
671
|
+
*/
|
|
672
|
+
function safeTransactionMatchesCall(tx, call) {
|
|
673
|
+
let message;
|
|
674
|
+
try {
|
|
675
|
+
message = safeTransactionMessage(tx);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
return (typeof call?.to === "string" &&
|
|
681
|
+
(0, viem_1.isAddress)(call.to) &&
|
|
682
|
+
(0, viem_1.isAddressEqual)(message.to, call.to) &&
|
|
683
|
+
message.value === (call.value ?? 0n) &&
|
|
684
|
+
message.data.toLowerCase() === String(call.data).toLowerCase() &&
|
|
685
|
+
message.operation === (call.operation ?? 0) &&
|
|
686
|
+
message.safeTxGas === 0n &&
|
|
687
|
+
message.baseGas === 0n &&
|
|
688
|
+
message.gasPrice === 0n &&
|
|
689
|
+
(0, viem_1.isAddressEqual)(message.gasToken, viem_1.zeroAddress) &&
|
|
690
|
+
(0, viem_1.isAddressEqual)(message.refundReceiver, viem_1.zeroAddress));
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* The nonce a new proposal takes: after everything already queued, never below
|
|
694
|
+
* the Safe's own. Reusing a queued nonce would offer a replacement instead.
|
|
695
|
+
*/
|
|
696
|
+
function nextProposalNonce(currentNonce, pending) {
|
|
697
|
+
return pending.reduce((next, tx) => Math.max(next, nonceOf(tx?.nonce) + 1), nonceOf(currentNonce));
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* The next onchain step for `account` on a chain with no Safe service. Safe
|
|
701
|
+
* counts the executing owner as a signature, so the owner who would complete
|
|
702
|
+
* the threshold executes instead of approving first.
|
|
703
|
+
*/
|
|
704
|
+
function onchainApprovalStep({ account, approved, threshold, }) {
|
|
705
|
+
const others = approved.filter((owner) => !(0, viem_1.isAddressEqual)(owner, account));
|
|
706
|
+
if (others.length + 1 >= threshold) {
|
|
707
|
+
return { kind: "execute", signers: [...others, account] };
|
|
708
|
+
}
|
|
709
|
+
return others.length === approved.length
|
|
710
|
+
? { kind: "approve" }
|
|
711
|
+
: { kind: "waiting" };
|
|
712
|
+
}
|
|
713
|
+
const PENDING_PAGE_SIZE = 50;
|
|
714
|
+
const MAX_PENDING = 250;
|
|
715
|
+
const SERVICE_DETAIL_CHARACTERS = 200;
|
|
716
|
+
/** Whether Safe hosts a transaction service for `chainId`; without one, owners approve onchain. */
|
|
717
|
+
function hasSafeService(chainId) {
|
|
718
|
+
return safeServiceBase(chainId) !== null;
|
|
719
|
+
}
|
|
720
|
+
/** The Safe app's page for one queued transaction. */
|
|
721
|
+
function safeTransactionUrl(chainId, safe, safeTxHash) {
|
|
722
|
+
const prefix = exports.SAFE_PREFIX[chainId];
|
|
723
|
+
if (!prefix || typeof safe !== "string" || !(0, viem_1.isAddress)(safe))
|
|
724
|
+
return null;
|
|
725
|
+
if (!(0, untrusted_js_1.isBytes32)(safeTxHash))
|
|
726
|
+
return null;
|
|
727
|
+
const address = (0, viem_1.getAddress)(safe);
|
|
728
|
+
return `https://app.safe.global/transactions/tx?safe=${prefix}:${address}&id=multisig_${address}_${safeTxHash}`;
|
|
729
|
+
}
|
|
730
|
+
function serviceOrThrow(chainId) {
|
|
731
|
+
const base = safeServiceBase(chainId);
|
|
732
|
+
if (!base) {
|
|
733
|
+
throw new Error(`Safe does not host a transaction service on chain ${chainId}.`);
|
|
734
|
+
}
|
|
735
|
+
return base;
|
|
736
|
+
}
|
|
737
|
+
/** The optional `jb-safe-api-key` from local storage, as a bearer token. */
|
|
738
|
+
function serviceHeaders(json = false) {
|
|
739
|
+
const headers = { accept: "application/json" };
|
|
740
|
+
if (json)
|
|
741
|
+
headers["content-type"] = "application/json";
|
|
742
|
+
try {
|
|
743
|
+
const key = globalThis.localStorage?.getItem("jb-safe-api-key");
|
|
744
|
+
if (key)
|
|
745
|
+
headers.authorization = `Bearer ${key}`;
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// The key is optional.
|
|
749
|
+
}
|
|
750
|
+
return headers;
|
|
751
|
+
}
|
|
752
|
+
/** One service request. A 429 is refused before processing, so it waits and repeats, up to three times. */
|
|
753
|
+
async function serviceFetch(url, init, options) {
|
|
754
|
+
const request = options.fetch ?? fetch;
|
|
755
|
+
for (let attempt = 0;; attempt += 1) {
|
|
756
|
+
const response = await request(url, init);
|
|
757
|
+
if (response.status !== 429 || attempt >= 3)
|
|
758
|
+
return response;
|
|
759
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
760
|
+
await new Promise((resolve) => setTimeout(resolve, Number.isFinite(retryAfter) && retryAfter > 0
|
|
761
|
+
? retryAfter * 1000
|
|
762
|
+
: 1000 * (attempt + 1)));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
async function serviceDetail(response) {
|
|
766
|
+
const text = await response.text().catch(() => "");
|
|
767
|
+
return text ? `: ${text.slice(0, SERVICE_DETAIL_CHARACTERS)}` : "";
|
|
768
|
+
}
|
|
769
|
+
/** The most confirmations a service row may carry; each must be an owner's. */
|
|
770
|
+
const MAX_ROW_CONFIRMATIONS = 100;
|
|
771
|
+
/**
|
|
772
|
+
* Every field of a service row read strictly, its advertised hash required and
|
|
773
|
+
* checked, its confirmations bounded: the row with its nonce as a number and
|
|
774
|
+
* its own Safe and hash, or a refusal naming what is wrong.
|
|
775
|
+
*/
|
|
776
|
+
function checkedRow(chainId, safe, row) {
|
|
777
|
+
try {
|
|
778
|
+
const tx = row;
|
|
779
|
+
if ((tx?.safeTxHash === undefined || tx.safeTxHash === null) &&
|
|
780
|
+
(tx?.contractTransactionHash === undefined ||
|
|
781
|
+
tx.contractTransactionHash === null)) {
|
|
782
|
+
throw new Error("It advertises no safeTxHash.");
|
|
783
|
+
}
|
|
784
|
+
if (tx.confirmations !== undefined &&
|
|
785
|
+
tx.confirmations !== null &&
|
|
786
|
+
(!Array.isArray(tx.confirmations) ||
|
|
787
|
+
tx.confirmations.length > MAX_ROW_CONFIRMATIONS)) {
|
|
788
|
+
throw new Error(`Its confirmations are not a list of at most ${MAX_ROW_CONFIRMATIONS}.`);
|
|
789
|
+
}
|
|
790
|
+
const hash = canonicalSafeTxHash(chainId, safe, tx);
|
|
791
|
+
return { ...tx, safe, safeTxHash: hash, nonce: nonceOf(tx.nonce) };
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
throw new Error(`Safe's transaction service listed a transaction for Safe ${safe} on chain ${chainId} that cannot be trusted. ${error.message}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Every queued, unexecuted transaction of `safe` from nonce `currentNonce`
|
|
799
|
+
* on, read from the chain's transaction service. Each row must belong to
|
|
800
|
+
* `safe` and hash to its advertised safeTxHash. Throws when the chain has no
|
|
801
|
+
* service or the service fails, so an outage never reads as an empty queue,
|
|
802
|
+
* and when more than 250 are queued.
|
|
803
|
+
*/
|
|
804
|
+
async function listPendingSafeTransactions(chainId, safe, currentNonce, options = {}) {
|
|
805
|
+
const base = serviceOrThrow(chainId);
|
|
806
|
+
const address = requireSafe(safe);
|
|
807
|
+
const fromNonce = nonceOf(currentNonce);
|
|
808
|
+
const rows = [];
|
|
809
|
+
for (let offset = 0; offset < MAX_PENDING; offset += PENDING_PAGE_SIZE) {
|
|
810
|
+
const url = `${base}/api/v1/safes/${address}/multisig-transactions/` +
|
|
811
|
+
`?executed=false&trusted=true&ordering=nonce&limit=${PENDING_PAGE_SIZE}` +
|
|
812
|
+
`&offset=${offset}&nonce__gte=${fromNonce}`;
|
|
813
|
+
let response = await serviceFetch(url, { headers: serviceHeaders() }, options);
|
|
814
|
+
if (!response.ok) {
|
|
815
|
+
// A failed page is retried once before the listing gives up.
|
|
816
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
817
|
+
response = await serviceFetch(url, { headers: serviceHeaders() }, options);
|
|
818
|
+
}
|
|
819
|
+
if (!response.ok) {
|
|
820
|
+
throw new Error(`Safe's transaction service answered ${response.status} listing the queue of Safe ${address} on chain ${chainId}${await serviceDetail(response)}`);
|
|
821
|
+
}
|
|
822
|
+
const body = (await response.json());
|
|
823
|
+
if (!body || !Array.isArray(body.results)) {
|
|
824
|
+
throw new Error(`Safe's transaction service returned no transaction list for Safe ${address} on chain ${chainId}.`);
|
|
825
|
+
}
|
|
826
|
+
if (body.results.length > PENDING_PAGE_SIZE) {
|
|
827
|
+
throw new Error(`Safe's transaction service returned ${body.results.length} transactions in a page of ${PENDING_PAGE_SIZE} for Safe ${address} on chain ${chainId}.`);
|
|
828
|
+
}
|
|
829
|
+
rows.push(...body.results.map((row) => checkedRow(chainId, address, row)));
|
|
830
|
+
if (body.results.length < PENDING_PAGE_SIZE || !body.next) {
|
|
831
|
+
return rows.filter((row) => row.isExecuted !== true && row.nonce >= fromNonce);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
throw new Error(`Safe ${address} has more than ${MAX_PENDING} queued transactions on chain ${chainId}. Nothing was proposed; execute or replace older ones first.`);
|
|
835
|
+
}
|
|
836
|
+
/** The queued zero-refund proposal of exactly `call`, so a retry can confirm it instead of proposing it twice. */
|
|
837
|
+
async function findPendingSafeTransaction(chainId, safe, currentNonce, call, options = {}) {
|
|
838
|
+
const pending = await listPendingSafeTransactions(chainId, safe, currentNonce, options);
|
|
839
|
+
return pending.find((tx) => safeTransactionMatchesCall(tx, call)) ?? null;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* The service's record of the proposal `safeTxHash`, authenticated: it must
|
|
843
|
+
* belong to `safe` and its fields must hash to `safeTxHash`.
|
|
844
|
+
*/
|
|
845
|
+
async function readSafeTransaction(chainId, safe, safeTxHash, options = {}) {
|
|
846
|
+
const base = serviceOrThrow(chainId);
|
|
847
|
+
const address = requireSafe(safe);
|
|
848
|
+
if (!(0, untrusted_js_1.isBytes32)(safeTxHash)) {
|
|
849
|
+
throw new Error(`Invalid Safe transaction hash: ${String(safeTxHash)}.`);
|
|
850
|
+
}
|
|
851
|
+
const response = await serviceFetch(`${base}/api/v1/multisig-transactions/${safeTxHash}/`, { headers: serviceHeaders() }, options);
|
|
852
|
+
if (!response.ok) {
|
|
853
|
+
throw new Error(`Safe's transaction service answered ${response.status} for proposal ${safeTxHash} on chain ${chainId}. Try again when it is available.`);
|
|
854
|
+
}
|
|
855
|
+
const record = (await response.json());
|
|
856
|
+
try {
|
|
857
|
+
if (record?.safe === undefined || record.safe === null) {
|
|
858
|
+
throw new Error("The record names no Safe.");
|
|
859
|
+
}
|
|
860
|
+
canonicalSafeTxHash(chainId, address, record, safeTxHash);
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
throw new Error(`Safe's record of proposal ${safeTxHash} does not match Safe ${address} on chain ${chainId}. ${error.message}`);
|
|
864
|
+
}
|
|
865
|
+
return record;
|
|
866
|
+
}
|
|
867
|
+
async function postToService(url, body, what, options) {
|
|
868
|
+
const response = await serviceFetch(url, {
|
|
869
|
+
method: "POST",
|
|
870
|
+
headers: serviceHeaders(true),
|
|
871
|
+
body: JSON.stringify(body),
|
|
872
|
+
}, options);
|
|
873
|
+
if (!response.ok && response.status !== 201) {
|
|
874
|
+
throw new Error(`Safe's transaction service refused ${what} (${response.status})${await serviceDetail(response)}`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function requireSignature(signature) {
|
|
878
|
+
if (!(0, untrusted_js_1.isHexBytes)(signature) || signature.length < 132) {
|
|
879
|
+
throw new Error(`Invalid Safe signature: ${String(signature)}.`);
|
|
880
|
+
}
|
|
881
|
+
return signature;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Queue `tx`, signed by `sender`, with the transaction service so the Safe's
|
|
885
|
+
* other owners can confirm and execute it. The body is built from the exact
|
|
886
|
+
* fields, checksummed as the service requires, and the hash is recomputed,
|
|
887
|
+
* never taken from the record. Returns the safeTxHash.
|
|
888
|
+
*/
|
|
889
|
+
async function proposeSafeTransaction(chainId, safe, tx, { sender, signature, origin, }, options = {}) {
|
|
890
|
+
const base = serviceOrThrow(chainId);
|
|
891
|
+
const address = requireSafe(safe);
|
|
892
|
+
const safeTxHash = canonicalSafeTxHash(chainId, address, tx);
|
|
893
|
+
const message = safeTransactionMessage(tx);
|
|
894
|
+
if (typeof origin !== "string" || !origin.trim()) {
|
|
895
|
+
throw new Error("A Safe proposal needs an origin naming the app.");
|
|
896
|
+
}
|
|
897
|
+
await postToService(`${base}/api/v1/safes/${address}/multisig-transactions/`, {
|
|
898
|
+
to: message.to,
|
|
899
|
+
value: message.value.toString(),
|
|
900
|
+
data: message.data,
|
|
901
|
+
operation: message.operation,
|
|
902
|
+
safeTxGas: message.safeTxGas.toString(),
|
|
903
|
+
baseGas: message.baseGas.toString(),
|
|
904
|
+
gasPrice: message.gasPrice.toString(),
|
|
905
|
+
gasToken: message.gasToken,
|
|
906
|
+
refundReceiver: message.refundReceiver,
|
|
907
|
+
nonce: message.nonce.toString(),
|
|
908
|
+
contractTransactionHash: safeTxHash,
|
|
909
|
+
sender: fieldAddress("sender", sender),
|
|
910
|
+
signature: requireSignature(signature),
|
|
911
|
+
origin,
|
|
912
|
+
}, `the proposal ${safeTxHash} for Safe ${address} on chain ${chainId}`, options);
|
|
913
|
+
return safeTxHash;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Add an owner's signature to the queued `tx`. The confirmation is posted to
|
|
917
|
+
* the hash of `tx`'s exact fields, never to a hash the record advertises.
|
|
918
|
+
*/
|
|
919
|
+
async function submitSafeConfirmation(chainId, safe, tx, signature, options = {}) {
|
|
920
|
+
const base = serviceOrThrow(chainId);
|
|
921
|
+
const safeTxHash = canonicalSafeTxHash(chainId, safe, tx);
|
|
922
|
+
await postToService(`${base}/api/v1/multisig-transactions/${safeTxHash}/confirmations/`, { signature: requireSignature(signature) }, `the confirmation of ${safeTxHash} on chain ${chainId}`, options);
|
|
923
|
+
}
|
|
924
|
+
/** The most Safes listed for one owner on one chain; the rest are not read. */
|
|
925
|
+
const MAX_OWNED_SAFES = 200;
|
|
926
|
+
/**
|
|
927
|
+
* Every Safe `owner` signs for on `chainIds`, from each chain's transaction
|
|
928
|
+
* service, at most 200 per chain. A chain without a service, a failed request
|
|
929
|
+
* or a malformed answer contributes nothing; listed Safes are checksummed and
|
|
930
|
+
* others dropped.
|
|
931
|
+
*/
|
|
932
|
+
async function fetchSafesOwnedBy(owner, chainIds, options = {}) {
|
|
933
|
+
if (typeof owner !== "string" || !(0, viem_1.isAddress)(owner))
|
|
934
|
+
return [];
|
|
935
|
+
const account = (0, viem_1.getAddress)(owner);
|
|
936
|
+
const perChain = await Promise.all(chainIds.map(async (chainId) => {
|
|
937
|
+
const base = safeServiceBase(chainId);
|
|
938
|
+
if (!base)
|
|
939
|
+
return [];
|
|
940
|
+
try {
|
|
941
|
+
const response = await serviceFetch(`${base}/api/v1/owners/${account}/safes/`, { headers: serviceHeaders() }, options);
|
|
942
|
+
if (!response.ok)
|
|
943
|
+
return [];
|
|
944
|
+
const body = (await response.json());
|
|
945
|
+
return Array.isArray(body?.safes)
|
|
946
|
+
? body.safes
|
|
947
|
+
.slice(0, MAX_OWNED_SAFES)
|
|
948
|
+
.flatMap((safe) => typeof safe === "string" && (0, viem_1.isAddress)(safe)
|
|
949
|
+
? [{ chainId, safe: (0, viem_1.getAddress)(safe) }]
|
|
950
|
+
: [])
|
|
951
|
+
: [];
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
return [];
|
|
955
|
+
}
|
|
956
|
+
}));
|
|
957
|
+
return perChain.flat();
|
|
958
|
+
}
|
|
959
|
+
/** The source chain's service URL for a Safe's creation record, or null. */
|
|
960
|
+
function safeCreationUrl(chainId, safe) {
|
|
961
|
+
const base = safeServiceBase(chainId);
|
|
962
|
+
if (!base || typeof safe !== "string" || !(0, viem_1.isAddress)(safe))
|
|
963
|
+
return null;
|
|
964
|
+
return `${base}/api/v1/safes/${(0, viem_1.getAddress)(safe)}/creation/`;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* A service creation record read strictly: the factory and singleton as
|
|
968
|
+
* addresses of one recognized Safe release, whole-hex setup data with at
|
|
969
|
+
* least a selector, and a decimal uint256 salt. Null for anything else.
|
|
970
|
+
*/
|
|
971
|
+
function parseSafeCreationPayload(payload) {
|
|
972
|
+
if (!payload || typeof payload !== "object")
|
|
973
|
+
return null;
|
|
974
|
+
const record = payload;
|
|
975
|
+
const { factoryAddress, masterCopy, setupData, saltNonce } = record;
|
|
976
|
+
if (typeof factoryAddress !== "string" ||
|
|
977
|
+
typeof masterCopy !== "string" ||
|
|
978
|
+
!(0, viem_1.isAddress)(factoryAddress) ||
|
|
979
|
+
!(0, viem_1.isAddress)(masterCopy) ||
|
|
980
|
+
!(0, untrusted_js_1.isHexBytes)(setupData) ||
|
|
981
|
+
setupData.length < 10 ||
|
|
982
|
+
typeof saltNonce !== "string" ||
|
|
983
|
+
!/^\d+$/.test(saltNonce) ||
|
|
984
|
+
(0, untrusted_js_1.uint256)(saltNonce) === null ||
|
|
985
|
+
!(0, safe_js_1.isRecognizedSafeDeployment)(factoryAddress, masterCopy)) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
factory: (0, viem_1.getAddress)(factoryAddress),
|
|
990
|
+
singleton: (0, viem_1.getAddress)(masterCopy),
|
|
991
|
+
initializer: setupData,
|
|
992
|
+
saltNonce: BigInt(saltNonce),
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* The Safe's creation record from its source chain's service only: a record
|
|
997
|
+
* for the same address from another chain could replay unrelated setup data.
|
|
998
|
+
* Null when the chain has no service, the request fails, or the record is
|
|
999
|
+
* not {@link parseSafeCreationPayload strictly valid}.
|
|
1000
|
+
*/
|
|
1001
|
+
async function fetchSafeCreation(safe, sourceChainId, options = {}) {
|
|
1002
|
+
const url = safeCreationUrl(sourceChainId, safe);
|
|
1003
|
+
if (!url)
|
|
1004
|
+
return null;
|
|
1005
|
+
try {
|
|
1006
|
+
const response = await serviceFetch(url, { headers: serviceHeaders() }, options);
|
|
1007
|
+
return response.ok ? parseSafeCreationPayload(await response.json()) : null;
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
175
1013
|
//# sourceMappingURL=safeService.js.map
|