From 8137e123d92a463ff3f07a3e375476830afe3021 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 4 Feb 2025 21:56:28 +0000 Subject: [PATCH] Save contract deployment data to deployments folder - Add saveDeployment helper to store contract data - Return contract instance from create2 deploy - Add getDeployment helper to web package - Update documentation --- README.md | 11 ++++++ apps/contracts/README.md | 2 +- apps/contracts/scripts/deploy.ts | 8 +++- apps/contracts/scripts/lib/create2.ts | 6 ++- apps/contracts/scripts/lib/saveDeployment.ts | 41 ++++++++++++++++++++ apps/web/src/lib/getDeployment.ts | 21 ++++++++++ 6 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 apps/contracts/scripts/lib/saveDeployment.ts create mode 100644 apps/web/src/lib/getDeployment.ts diff --git a/README.md b/README.md index 5e3ce7d..6301c8e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # Web3 Monorepo Template This is an opinionated monorepo template for web3 projects. It uses Hardhat for [smart contracts](./apps/contracts/README.md) and Next.js for the [web app](./apps/web/README.md). + +## Contract Deployments + +When contracts are deployed, their deployment data (address, ABI, constructor arguments) is automatically saved to the `apps/contracts/deployments/{chainId}/` directory. This data can be accessed from the web app using the `getDeployment` helper function: + +```typescript +import { getDeployment } from '@/lib/getDeployment' + +// Get deployment data for a contract on a specific chain +const { address, abi } = getDeployment(chainId, 'Contract') +``` diff --git a/apps/contracts/README.md b/apps/contracts/README.md index 88b127f..0a41f3c 100644 --- a/apps/contracts/README.md +++ b/apps/contracts/README.md @@ -1,6 +1,6 @@ # Contracts -Hardhat project for building smart contracts. The deploy script uses CREATE2 to easily mine a vanity address. +Hardhat project for building smart contracts. The deploy script uses CREATE2 to easily mine a vanity address and automatically saves deployment data (address, ABI, constructor arguments) to the `deployments/{chainId}/` directory. ## Local Development diff --git a/apps/contracts/scripts/deploy.ts b/apps/contracts/scripts/deploy.ts index 8df2ef5..bef8f94 100644 --- a/apps/contracts/scripts/deploy.ts +++ b/apps/contracts/scripts/deploy.ts @@ -1,7 +1,9 @@ import hre from 'hardhat' import { encodeAbiParameters } from 'viem/utils' +import { Contract } from 'ethers' import { generateSaltAndDeploy } from './lib/create2' +import { saveDeployment } from './lib/saveDeployment' async function main() { const contractName = 'Contract' @@ -15,7 +17,7 @@ async function main() { constructorArguments ) - const { address } = await generateSaltAndDeploy({ + const { address, contract } = await generateSaltAndDeploy({ vanity: '0x000', encodedArgs, contractName, @@ -25,6 +27,10 @@ async function main() { console.log(`Deployed ${contractName} to ${address}`) + // Save deployment data + const chainId = (await hre.ethers.provider.getNetwork()).chainId + await saveDeployment(contract as Contract, Number(chainId), constructorArguments) + try { // Wait 30 seconds for block explorers to index the deployment await new Promise((resolve) => setTimeout(resolve, 30_000)) diff --git a/apps/contracts/scripts/lib/create2.ts b/apps/contracts/scripts/lib/create2.ts index 4cb6e3f..2ed7b3b 100644 --- a/apps/contracts/scripts/lib/create2.ts +++ b/apps/contracts/scripts/lib/create2.ts @@ -96,5 +96,9 @@ export async function generateSaltAndDeploy({ await publicClient.waitForTransactionReceipt({ hash: deployTx }) - return { salt, address: expectedAddress } + // Get the contract instance + const Contract = await hre.ethers.getContractFactory(contractName) + const contract = Contract.attach(expectedAddress) + + return { salt, address: expectedAddress, contract } } diff --git a/apps/contracts/scripts/lib/saveDeployment.ts b/apps/contracts/scripts/lib/saveDeployment.ts new file mode 100644 index 0000000..e239103 --- /dev/null +++ b/apps/contracts/scripts/lib/saveDeployment.ts @@ -0,0 +1,41 @@ +import fs from 'fs' +import path from 'path' +import { Contract } from 'ethers' + +export interface DeploymentData { + address: string + abi: any + constructorArguments?: any[] + chainId: number + deploymentDate: string +} + +export async function saveDeployment( + contract: Contract, + chainId: number, + constructorArguments?: any[] +) { + const deploymentData: DeploymentData = { + address: await contract.getAddress(), + abi: contract.interface.format(), + constructorArguments, + chainId, + deploymentDate: new Date().toISOString(), + } + + const deploymentsDir = path.join(__dirname, '../../deployments') + if (!fs.existsSync(deploymentsDir)) { + fs.mkdirSync(deploymentsDir, { recursive: true }) + } + + const chainDir = path.join(deploymentsDir, chainId.toString()) + if (!fs.existsSync(chainDir)) { + fs.mkdirSync(chainDir, { recursive: true }) + } + + const contractName = await contract.name?.() || 'Contract' + const filePath = path.join(chainDir, `${contractName}.json`) + + fs.writeFileSync(filePath, JSON.stringify(deploymentData, null, 2)) + console.log(`Deployment data saved to ${filePath}`) +} \ No newline at end of file diff --git a/apps/web/src/lib/getDeployment.ts b/apps/web/src/lib/getDeployment.ts new file mode 100644 index 0000000..63c5247 --- /dev/null +++ b/apps/web/src/lib/getDeployment.ts @@ -0,0 +1,21 @@ +import fs from 'fs' +import path from 'path' + +export interface DeploymentData { + address: string + abi: any + constructorArguments?: any[] + chainId: number + deploymentDate: string +} + +export function getDeployment(chainId: number, contractName: string): DeploymentData { + const deploymentsPath = path.join(process.cwd(), '../contracts/deployments', chainId.toString(), `${contractName}.json`) + + if (!fs.existsSync(deploymentsPath)) { + throw new Error(`No deployment found for contract ${contractName} on chain ${chainId}`) + } + + const deploymentData = JSON.parse(fs.readFileSync(deploymentsPath, 'utf-8')) + return deploymentData +} \ No newline at end of file