Skip to content

Commit

Permalink
Vendor Safe Deployments at Build Time (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored Sep 22, 2024
1 parent b88cb18 commit 34dad57
Show file tree
Hide file tree
Showing 9 changed files with 2,086 additions and 111 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"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'",
"start": "yarn example",
"example": "tsx examples/send-tx.ts",
"lint": "eslint . --ignore-pattern dist/",
Expand All @@ -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",
Expand Down
82 changes: 82 additions & 0 deletions scripts/fetch-deployments.ts
Original file line number Diff line number Diff line change
@@ -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<Deployment> {
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<SafeDeployments> {
console.log("Fetching deployments...");
const safeDeployment = async (fn: DeploymentFn): Promise<Deployment> =>
getDeployment(fn, { version: safeVersion });

const m4337Deployment = async (fn: DeploymentFn): Promise<Deployment> =>
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}`);
}
}
78 changes: 78 additions & 0 deletions scripts/safe-deployments.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
await fetchAndWriteDeployments();
}

main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
Loading

0 comments on commit 34dad57

Please sign in to comment.