Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vendor Safe Deployments at Build Time #61

Merged
merged 2 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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