From e67145096acf4f616be18d8a6118837b079bde47 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Sun, 22 Sep 2024 15:53:04 +0200 Subject: [PATCH 1/2] no more async deployment fetching it now generated into the build as a TS constant --- package.json | 7 +- scripts/fetch-deployments.ts | 82 ++ scripts/safe-deployments.ts | 78 ++ src/_gen/deployments.ts | 1889 ++++++++++++++++++++++++++++++++++ src/lib/safe.ts | 118 +-- src/near-safe.ts | 6 +- src/types.ts | 15 +- tests/lib/safe.spec.ts | 2 +- tsconfig.json | 2 +- 9 files changed, 2087 insertions(+), 112 deletions(-) create mode 100644 scripts/fetch-deployments.ts create mode 100644 scripts/safe-deployments.ts create mode 100644 src/_gen/deployments.ts diff --git a/package.json b/package.json index 15781bf..8c182d5 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,10 @@ "dist/**/*" ], "scripts": { - "build": "rm -fr dist/* && yarn build:esm && yarn build:cjs", + "build": "rm -fr dist/* && yarn build:deployments && yarn build:esm && yarn build:cjs", "build:esm": "tsc -p tsconfig.esm.json", "build:cjs": "tsc -p tsconfig.cjs.json", + "build:deployments": "tsx scripts/safe-deployments.ts && prettier --write 'src/_gen/**/*.ts'", "start": "yarn example", "example": "tsx examples/send-tx.ts", "lint": "eslint . --ignore-pattern dist/", @@ -40,15 +41,15 @@ "all": "yarn fmt && yarn lint && yarn build" }, "dependencies": { - "@safe-global/safe-deployments": "^1.37.0", "@safe-global/safe-gateway-typescript-sdk": "^3.22.2", - "@safe-global/safe-modules-deployments": "^2.2.0", "near-api-js": "^5.0.0", "near-ca": "^0.5.6", "semver": "^7.6.3", "viem": "^2.16.5" }, "devDependencies": { + "@safe-global/safe-deployments": "^1.37.0", + "@safe-global/safe-modules-deployments": "^2.2.0", "@types/jest": "^29.5.12", "@types/node": "^22.3.0", "@types/semver": "^7.5.8", diff --git a/scripts/fetch-deployments.ts b/scripts/fetch-deployments.ts new file mode 100644 index 0000000..9028f17 --- /dev/null +++ b/scripts/fetch-deployments.ts @@ -0,0 +1,82 @@ +import { + getProxyFactoryDeployment, + getSafeL2SingletonDeployment, +} from "@safe-global/safe-deployments"; +import { + getSafe4337ModuleDeployment, + getSafeModuleSetupDeployment, +} from "@safe-global/safe-modules-deployments"; +import { Address, parseAbi } from "viem"; + +import { Deployment, SafeDeployments } from "../src/types"; +import { getClient } from "../src/util"; + +// Define the deployment version and chain ID (e.g., "1.4.1" for Safe contracts, "0.3.0" for modules) +export const SAFE_VERSION = "1.4.1"; +export const MODULE_VERSION = "0.3.0"; + +type DeploymentFn = (filter?: { + version: string; +}) => + | { networkAddresses: { [chainId: string]: string }; abi: unknown[] } + | undefined; + +type DeploymentArgs = { version: string }; + +export async function getDeployment( + fn: DeploymentFn, + { version }: DeploymentArgs +): Promise { + const deployment = fn({ version }); + if (!deployment) { + throw new Error(`Deployment not found for ${fn.name} version ${version}`); + } + // TODO: maybe call parseAbi on deployment.abi here. + return { + address: deployment.networkAddresses["11155111"] as Address, + abi: deployment.abi, + }; +} + +export async function fetchDeployments( + safeVersion: string = SAFE_VERSION, + moduleVersion: string = MODULE_VERSION +): Promise { + console.log("Fetching deployments..."); + const safeDeployment = async (fn: DeploymentFn): Promise => + getDeployment(fn, { version: safeVersion }); + + const m4337Deployment = async (fn: DeploymentFn): Promise => + getDeployment(fn, { version: moduleVersion }); + + try { + // Fetch deployments for Safe and 4337 modules + const [singleton, proxyFactory, moduleSetup, m4337] = await Promise.all([ + safeDeployment(getSafeL2SingletonDeployment), + safeDeployment(getProxyFactoryDeployment), + m4337Deployment(getSafeModuleSetupDeployment), + m4337Deployment(getSafe4337ModuleDeployment), + ]); + // TODO - this is a cheeky hack. + const client = getClient(11155111); + const entryPoint = { + address: (await client.readContract({ + address: m4337.address, + abi: m4337.abi, + functionName: "SUPPORTED_ENTRYPOINT", + })) as Address, + abi: parseAbi([ + "function getNonce(address, uint192 key) view returns (uint256 nonce)", + ]), + }; + return { + singleton, + proxyFactory, + moduleSetup, + m4337, + entryPoint, + }; + } catch (error) { + throw new Error(`Error fetching deployments: ${error}`); + } +} diff --git a/scripts/safe-deployments.ts b/scripts/safe-deployments.ts new file mode 100644 index 0000000..5c1317d --- /dev/null +++ b/scripts/safe-deployments.ts @@ -0,0 +1,78 @@ +import fs from "fs"; +import path from "path"; + +import { + fetchDeployments, + MODULE_VERSION, + SAFE_VERSION, +} from "./fetch-deployments"; + +// Main function to fetch and write deployment data +export async function fetchAndWriteDeployments( + outPath: string = "src/_gen", + safeVersion: string = SAFE_VERSION, + moduleVersion: string = MODULE_VERSION +): Promise { + const { singleton, proxyFactory, moduleSetup, m4337, entryPoint } = + await fetchDeployments(safeVersion, moduleVersion); + + try { + // Specify output file path + const outputPath = path.join(process.cwd(), outPath, "deployments.ts"); + // const outputPath = path.join( + // process.cwd(), + // outPath, + // `safe_v${safeVersion}_module_v${moduleVersion}.json` + // ); + + // Ensure the directory exists + if (!fs.existsSync(path.dirname(outputPath))) { + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + } + + // Write deployment data to file + // fs.writeFileSync(outputPath, JSON.stringify(deployments, null, 2)); + const tsContent = ` + // Auto-generated file from build script + import { SafeDeployments } from "../types"; + + export const SAFE_DEPLOYMENTS: SafeDeployments = { + singleton: { + address: "${singleton.address}", + abi: ${JSON.stringify(singleton.abi, null, 2)}, + }, + proxyFactory: { + address: "${proxyFactory.address}", + abi: ${JSON.stringify(proxyFactory.abi, null, 2)}, + }, + moduleSetup: { + address: "${moduleSetup.address}", + abi: ${JSON.stringify(moduleSetup.abi, null, 2)}, + }, + m4337: { + address: "${m4337.address}", + abi: ${JSON.stringify(m4337.abi, null, 2)}, + }, + entryPoint: { + address: "${entryPoint.address}", + abi: ${JSON.stringify(entryPoint.abi, null, 2)}, + }, + }; + `; + fs.writeFileSync(outputPath, tsContent, "utf-8"); + console.log( + `TypeScript constants generated at ${path.join(outPath, "deployments.ts")}` + ); + } catch (error) { + console.error("Error fetching deployments:", error); + } +} + +async function main(): Promise { + await fetchAndWriteDeployments(); +} + +main().catch((err) => { + console.error(err); + process.exitCode = 1; +}); diff --git a/src/_gen/deployments.ts b/src/_gen/deployments.ts new file mode 100644 index 0000000..650110d --- /dev/null +++ b/src/_gen/deployments.ts @@ -0,0 +1,1889 @@ +// Auto-generated file from build script +import { SafeDeployments } from "../types"; + +export const SAFE_DEPLOYMENTS: SafeDeployments = { + singleton: { + address: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762", + abi: [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "AddedOwner", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "approvedHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "ApproveHash", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "handler", + type: "address", + }, + ], + name: "ChangedFallbackHandler", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "guard", + type: "address", + }, + ], + name: "ChangedGuard", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "threshold", + type: "uint256", + }, + ], + name: "ChangedThreshold", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "DisabledModule", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "EnabledModule", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "txHash", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "payment", + type: "uint256", + }, + ], + name: "ExecutionFailure", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "ExecutionFromModuleFailure", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "ExecutionFromModuleSuccess", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "txHash", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "payment", + type: "uint256", + }, + ], + name: "ExecutionSuccess", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "RemovedOwner", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "module", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + indexed: false, + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + ], + name: "SafeModuleTransaction", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + indexed: false, + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + { + indexed: false, + internalType: "uint256", + name: "safeTxGas", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "baseGas", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "gasPrice", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "gasToken", + type: "address", + }, + { + indexed: false, + internalType: "address payable", + name: "refundReceiver", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "signatures", + type: "bytes", + }, + { + indexed: false, + internalType: "bytes", + name: "additionalInfo", + type: "bytes", + }, + ], + name: "SafeMultiSigTransaction", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "SafeReceived", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "initiator", + type: "address", + }, + { + indexed: false, + internalType: "address[]", + name: "owners", + type: "address[]", + }, + { + indexed: false, + internalType: "uint256", + name: "threshold", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "initializer", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "fallbackHandler", + type: "address", + }, + ], + name: "SafeSetup", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "msgHash", + type: "bytes32", + }, + ], + name: "SignMsg", + type: "event", + }, + { + stateMutability: "nonpayable", + type: "fallback", + }, + { + inputs: [], + name: "VERSION", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + ], + name: "addOwnerWithThreshold", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hashToApprove", + type: "bytes32", + }, + ], + name: "approveHash", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + name: "approvedHashes", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + ], + name: "changeThreshold", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "dataHash", + type: "bytes32", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "bytes", + name: "signatures", + type: "bytes", + }, + { + internalType: "uint256", + name: "requiredSignatures", + type: "uint256", + }, + ], + name: "checkNSignatures", + outputs: [], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "dataHash", + type: "bytes32", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "bytes", + name: "signatures", + type: "bytes", + }, + ], + name: "checkSignatures", + outputs: [], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "prevModule", + type: "address", + }, + { + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "disableModule", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "domainSeparator", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "enableModule", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + { + internalType: "uint256", + name: "safeTxGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "baseGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "gasPrice", + type: "uint256", + }, + { + internalType: "address", + name: "gasToken", + type: "address", + }, + { + internalType: "address", + name: "refundReceiver", + type: "address", + }, + { + internalType: "uint256", + name: "_nonce", + type: "uint256", + }, + ], + name: "encodeTransactionData", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + { + internalType: "uint256", + name: "safeTxGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "baseGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "gasPrice", + type: "uint256", + }, + { + internalType: "address", + name: "gasToken", + type: "address", + }, + { + internalType: "address payable", + name: "refundReceiver", + type: "address", + }, + { + internalType: "bytes", + name: "signatures", + type: "bytes", + }, + ], + name: "execTransaction", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + ], + name: "execTransactionFromModule", + outputs: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + ], + name: "execTransactionFromModuleReturnData", + outputs: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getChainId", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "start", + type: "address", + }, + { + internalType: "uint256", + name: "pageSize", + type: "uint256", + }, + ], + name: "getModulesPaginated", + outputs: [ + { + internalType: "address[]", + name: "array", + type: "address[]", + }, + { + internalType: "address", + name: "next", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getOwners", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "offset", + type: "uint256", + }, + { + internalType: "uint256", + name: "length", + type: "uint256", + }, + ], + name: "getStorageAt", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getThreshold", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + { + internalType: "uint256", + name: "safeTxGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "baseGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "gasPrice", + type: "uint256", + }, + { + internalType: "address", + name: "gasToken", + type: "address", + }, + { + internalType: "address", + name: "refundReceiver", + type: "address", + }, + { + internalType: "uint256", + name: "_nonce", + type: "uint256", + }, + ], + name: "getTransactionHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "module", + type: "address", + }, + ], + name: "isModuleEnabled", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "isOwner", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "nonce", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "prevOwner", + type: "address", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + ], + name: "removeOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "handler", + type: "address", + }, + ], + name: "setFallbackHandler", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "guard", + type: "address", + }, + ], + name: "setGuard", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address[]", + name: "_owners", + type: "address[]", + }, + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "address", + name: "fallbackHandler", + type: "address", + }, + { + internalType: "address", + name: "paymentToken", + type: "address", + }, + { + internalType: "uint256", + name: "payment", + type: "uint256", + }, + { + internalType: "address payable", + name: "paymentReceiver", + type: "address", + }, + ], + name: "setup", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + name: "signedMessages", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "targetContract", + type: "address", + }, + { + internalType: "bytes", + name: "calldataPayload", + type: "bytes", + }, + ], + name: "simulateAndRevert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "prevOwner", + type: "address", + }, + { + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "swapOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, + ], + }, + proxyFactory: { + address: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + abi: [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "singleton", + type: "address", + }, + ], + name: "ProxyCreation", + type: "event", + }, + { + inputs: [ + { + internalType: "address", + name: "_singleton", + type: "address", + }, + { + internalType: "bytes", + name: "initializer", + type: "bytes", + }, + { + internalType: "uint256", + name: "saltNonce", + type: "uint256", + }, + ], + name: "createChainSpecificProxyWithNonce", + outputs: [ + { + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_singleton", + type: "address", + }, + { + internalType: "bytes", + name: "initializer", + type: "bytes", + }, + { + internalType: "uint256", + name: "saltNonce", + type: "uint256", + }, + { + internalType: "contract IProxyCreationCallback", + name: "callback", + type: "address", + }, + ], + name: "createProxyWithCallback", + outputs: [ + { + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_singleton", + type: "address", + }, + { + internalType: "bytes", + name: "initializer", + type: "bytes", + }, + { + internalType: "uint256", + name: "saltNonce", + type: "uint256", + }, + ], + name: "createProxyWithNonce", + outputs: [ + { + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getChainId", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "proxyCreationCode", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "pure", + type: "function", + }, + ], + }, + moduleSetup: { + address: "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47", + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "modules", + type: "address[]", + }, + ], + name: "enableModules", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + }, + m4337: { + address: "0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226", + abi: [ + { + inputs: [ + { + internalType: "address", + name: "entryPoint", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ExecutionFailed", + type: "error", + }, + { + inputs: [], + name: "InvalidCaller", + type: "error", + }, + { + inputs: [], + name: "InvalidEntryPoint", + type: "error", + }, + { + inputs: [], + name: "UnsupportedEntryPoint", + type: "error", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "UnsupportedExecutionFunction", + type: "error", + }, + { + inputs: [], + name: "SUPPORTED_ENTRYPOINT", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "domainSeparator", + outputs: [ + { + internalType: "bytes32", + name: "domainSeparatorHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract Safe", + name: "safe", + type: "address", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + name: "encodeMessageDataForSafe", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint8", + name: "operation", + type: "uint8", + }, + ], + name: "executeUserOp", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint8", + name: "operation", + type: "uint8", + }, + ], + name: "executeUserOpWithErrorString", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + name: "getMessageHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract Safe", + name: "safe", + type: "address", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + name: "getMessageHashForSafe", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getModules", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { + internalType: "bytes32", + name: "gasFees", + type: "bytes32", + }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + ], + internalType: "struct PackedUserOperation", + name: "userOp", + type: "tuple", + }, + ], + name: "getOperationHash", + outputs: [ + { + internalType: "bytes32", + name: "operationHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "_dataHash", + type: "bytes32", + }, + { + internalType: "bytes", + name: "_signature", + type: "bytes", + }, + ], + name: "isValidSignature", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + { + internalType: "bytes", + name: "_signature", + type: "bytes", + }, + ], + name: "isValidSignature", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155BatchReceived", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC721Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "targetContract", + type: "address", + }, + { + internalType: "bytes", + name: "calldataPayload", + type: "bytes", + }, + ], + name: "simulate", + outputs: [ + { + internalType: "bytes", + name: "response", + type: "bytes", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "tokensReceived", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { + internalType: "bytes32", + name: "gasFees", + type: "bytes32", + }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + ], + internalType: "struct PackedUserOperation", + name: "userOp", + type: "tuple", + }, + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + { + internalType: "uint256", + name: "missingAccountFunds", + type: "uint256", + }, + ], + name: "validateUserOp", + outputs: [ + { + internalType: "uint256", + name: "validationData", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + }, + entryPoint: { + address: "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + abi: [ + { + name: "getNonce", + type: "function", + stateMutability: "view", + inputs: [ + { + type: "address", + }, + { + type: "uint192", + name: "key", + }, + ], + outputs: [ + { + type: "uint256", + name: "nonce", + }, + ], + }, + ], + }, +}; diff --git a/src/lib/safe.ts b/src/lib/safe.ts index 481ffb5..3e7c111 100644 --- a/src/lib/safe.ts +++ b/src/lib/safe.ts @@ -1,11 +1,3 @@ -import { - getProxyFactoryDeployment, - getSafeL2SingletonDeployment, -} from "@safe-global/safe-deployments"; -import { - getSafe4337ModuleDeployment, - getSafeModuleSetupDeployment, -} from "@safe-global/safe-modules-deployments"; import { Address, encodeFunctionData, @@ -14,14 +6,14 @@ import { Hash, Hex, keccak256, - ParseAbi, - parseAbi, PublicClient, toHex, zeroAddress, } from "viem"; +import { SAFE_DEPLOYMENTS } from "../_gen/deployments"; import { + Deployment, GasPrice, MetaTransaction, UnsignedUserOperation, @@ -34,82 +26,26 @@ import { packPaymasterData, } from "../util"; -interface DeploymentData { - abi: unknown[] | ParseAbi; - address: `0x${string}`; -} - /** * All contracts used in account creation & execution */ export class SafeContractSuite { // Used only for stateless contract reads. dummyClient: PublicClient; - singleton: DeploymentData; - proxyFactory: DeploymentData; - m4337: DeploymentData; - moduleSetup: DeploymentData; - entryPoint: DeploymentData; - - constructor( - client: PublicClient, - singleton: DeploymentData, - proxyFactory: DeploymentData, - m4337: DeploymentData, - moduleSetup: DeploymentData, - entryPoint: DeploymentData - ) { - this.dummyClient = client; - this.singleton = singleton; - this.proxyFactory = proxyFactory; - this.m4337 = m4337; - this.moduleSetup = moduleSetup; - this.entryPoint = entryPoint; - } - - static async init(): Promise { - // TODO - this is a cheeky hack. - const client = getClient(11155111); - const safeDeployment = (fn: DeploymentFunction): Promise => - getDeployment(fn, { version: "1.4.1" }); - const m4337Deployment = async ( - fn: DeploymentFunction - ): Promise => { - return getDeployment(fn, { version: "0.3.0" }); - }; - - const [singleton, proxyFactory, moduleSetup, m4337] = await Promise.all([ - safeDeployment(getSafeL2SingletonDeployment), - safeDeployment(getProxyFactoryDeployment), - m4337Deployment(getSafeModuleSetupDeployment), - m4337Deployment(getSafe4337ModuleDeployment), - ]); - - // console.log("Initialized ERC4337 & Safe Module Contracts:", { - // singleton: await singleton.getAddress(), - // proxyFactory: await proxyFactory.getAddress(), - // m4337: await m4337.getAddress(), - // moduleSetup: await moduleSetup.getAddress(), - // entryPoint: await entryPoint.getAddress(), - // }); - return new SafeContractSuite( - client, - singleton, - proxyFactory, - m4337, - moduleSetup, - // EntryPoint: - { - address: (await client.readContract({ - address: m4337.address, - abi: m4337.abi, - functionName: "SUPPORTED_ENTRYPOINT", - })) as Address, - abi: parseAbi([ - "function getNonce(address, uint192 key) view returns (uint256 nonce)", - ]), - } - ); + singleton: Deployment; + proxyFactory: Deployment; + m4337: Deployment; + moduleSetup: Deployment; + entryPoint: Deployment; + + constructor() { + this.dummyClient = getClient(11155111); + const deployments = SAFE_DEPLOYMENTS; + this.singleton = deployments.singleton; + this.proxyFactory = deployments.proxyFactory; + this.m4337 = deployments.m4337; + this.moduleSetup = deployments.moduleSetup; + this.entryPoint = deployments.entryPoint; } async addressForSetup(setup: Hex, saltNonce?: string): Promise
{ @@ -255,25 +191,3 @@ export class SafeContractSuite { return nonce; } } - -type DeploymentFunction = (filter?: { - version: string; -}) => - | { networkAddresses: { [chainId: string]: string }; abi: unknown[] } - | undefined; -type DeploymentArgs = { version: string }; - -async function getDeployment( - fn: DeploymentFunction, - { version }: DeploymentArgs -): Promise { - const deployment = fn({ version }); - if (!deployment) { - throw new Error(`Deployment not found for ${fn.name} version ${version}`); - } - // TODO: maybe call parseAbi on deployment.abi here. - return { - address: deployment.networkAddresses["11155111"] as Address, - abi: deployment.abi, - }; -} diff --git a/src/near-safe.ts b/src/near-safe.ts index 9d9ea25..0852c44 100644 --- a/src/near-safe.ts +++ b/src/near-safe.ts @@ -48,10 +48,8 @@ export class NearSafe { static async create(config: NearSafeConfig): Promise { const { pimlicoKey, safeSaltNonce } = config; - const [nearAdapter, safePack] = await Promise.all([ - setupAdapter({ ...config }), - SafeContractSuite.init(), - ]); + const nearAdapter = await setupAdapter({ ...config }); + const safePack = new SafeContractSuite(); const setup = safePack.getSetup([nearAdapter.address]); const safeAddress = await safePack.addressForSetup(setup, safeSaltNonce); diff --git a/src/types.ts b/src/types.ts index c27c153..2203ddd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,18 @@ import { FunctionCallTransaction, SignArgs } from "near-ca"; -import { Address, Hash, Hex, TransactionSerializable } from "viem"; +import { Address, Hash, Hex, ParseAbi, TransactionSerializable } from "viem"; + +export type SafeDeployments = { + singleton: Deployment; + proxyFactory: Deployment; + moduleSetup: Deployment; + m4337: Deployment; + entryPoint: Deployment; +}; + +export interface Deployment { + abi: unknown[] | ParseAbi; + address: Address; +} export interface UnsignedUserOperation { sender: Address; diff --git a/tests/lib/safe.spec.ts b/tests/lib/safe.spec.ts index 53d6688..7fad3f8 100644 --- a/tests/lib/safe.spec.ts +++ b/tests/lib/safe.spec.ts @@ -8,7 +8,7 @@ describe("Safe Pack", () => { let viemPack: ViemPack; beforeAll(async () => { ethersPack = await EthPack.init(); - viemPack = await ViemPack.init(); + viemPack = new ViemPack(); }); it("init", async () => { diff --git a/tsconfig.json b/tsconfig.json index 6f0cf31..97d87ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,7 @@ }, "include": [ "src/**/*.ts" - ], +, "scripts/fetch-deployments.ts" ], "exclude": [ "node_modules", "dist", From 984f80fae6d5924b28f379abd627f4f19c49a728 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Sun, 22 Sep 2024 15:56:20 +0200 Subject: [PATCH 2/2] no need to vendor deployments on every build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c182d5..3ba1cb4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dist/**/*" ], "scripts": { - "build": "rm -fr dist/* && yarn build:deployments && yarn build:esm && yarn build:cjs", + "build": "rm -fr dist/* && yarn build:esm && yarn build:cjs", "build:esm": "tsc -p tsconfig.esm.json", "build:cjs": "tsc -p tsconfig.cjs.json", "build:deployments": "tsx scripts/safe-deployments.ts && prettier --write 'src/_gen/**/*.ts'",