diff --git a/DEPLOY_AT_NEW_NETWORK.md b/DEPLOY_AT_NEW_NETWORK.md new file mode 100644 index 0000000..ef9529c --- /dev/null +++ b/DEPLOY_AT_NEW_NETWORK.md @@ -0,0 +1,58 @@ +# Example of deploying ASTR token on new network +Let's take example of adding **Base network** and deploying AstarToken contract on Base + +1. Add network config +``` + "base": { + "chainId": 8453, + ... + }, +``` + +2. Add type in config/types.ts +``` +export enum Chains { + ... + base = "base", +} +``` + +3. Add Base to config/networks.ts +``` + [Chains.base]: { + ...configData.base, + url: process.env.BASE_RPC_URL || "https://mainnet.base.org", + gasPrice: undefined, + nonce: undefined, + accounts, + }, +``` + +4. (Optionally) Add shortcut to package.json +``` +"deploy:base": "hardhat deployAtNonce --network base --nonce 15 --verifycontract true" +``` + +5. Fund the deployer account at the Base network with enought ETH for gas +Check that deployer balance is sufficient +``` +cast balance 0x448dAe9299366ff611678fdA2f3a386791C95d41 --rpc-url="https://mainnet.base.org" +``` + +6. Set the `PRIVATE_KEY` in `.env` to use account `0x448dAe9299366ff611678fdA2f3a386791C95d41` + +7. Compile contract +``` +npx hardhat compile +``` + +8. Deploy ASTR contract at Base + +``` +npx hardhat deployAtNonce --network base --verifycontract true +``` +or +``` +pnpm run deploy:base +``` + diff --git a/config/config.json b/config/config.json index fa9e8af..5ff03b1 100644 --- a/config/config.json +++ b/config/config.json @@ -20,5 +20,27 @@ "link": "0x32D8F819C8080ae44375F8d383Ffd39FC642f3Ec", "confirmations": 8, "nativeCurrencySymbol": "ETH" + }, + "optimism": { + "chainId": 10, + "chainSelector": "", + "router": "", + "rmnProxy": "", + "tokenAdminRegistry": "", + "registryModuleOwnerCustom": "", + "link": "", + "confirmations": 8, + "nativeCurrencySymbol": "ETH" + }, + "opSepolia": { + "chainId": 11155420, + "chainSelector": "", + "router": "", + "rmnProxy": "", + "tokenAdminRegistry": "", + "registryModuleOwnerCustom": "", + "link": "", + "confirmations": 8, + "nativeCurrencySymbol": "ETH" } } diff --git a/config/networks.ts b/config/networks.ts index 7c527d4..d7dd215 100644 --- a/config/networks.ts +++ b/config/networks.ts @@ -1,21 +1,18 @@ import { Chains, EtherscanConfig, Networks } from "./types"; import configData from "./config.json"; - require("@chainlink/env-enc").config(); -const PRIVATE_KEY = process.env.PRIVATE_KEY; +const PRIVATE_KEY = process.env.PRIVATE_KEY || ""; const PRIVATE_KEY_2 = process.env.PRIVATE_KEY_2; const accounts = []; if (PRIVATE_KEY) { accounts.push(PRIVATE_KEY); } - if (PRIVATE_KEY_2) { accounts.push(PRIVATE_KEY_2); } - const networks: Networks = { [Chains.soneiumMinato]: { ...configData.soneiumMinato, @@ -31,12 +28,28 @@ const networks: Networks = { nonce: undefined, accounts, }, + [Chains.optimism]: { + ...configData.optimism, + url: process.env.OPTIMISM_RPC_URL || "https://mainnet.optimism.io", + gasPrice: undefined, + nonce: undefined, + accounts, + }, + [Chains.opSepolia]: { + ...configData.opSepolia, + url: process.env.OPTIMISM_SEPOLIA_RPC_URL || "https://sepolia.optimism.io", + gasPrice: undefined, + nonce: undefined, + accounts, + }, }; const etherscan: EtherscanConfig = { apiKey: { [Chains.soneiumMinato]: " ", [Chains.soneium]: " ", + [Chains.opSepolia]: " ", + [Chains.optimism]: " ", }, customChains: [ { @@ -55,6 +68,22 @@ const etherscan: EtherscanConfig = { browserURL: "https://soneium.blockscout.com", }, }, + { + network: Chains.opSepolia, + chainId: 11155420, + urls: { + apiURL: "https://optimism-sepolia.blockscout.com/api", + browserURL: "https://optimism-sepolia.blockscout.com", + }, + }, + { + network: Chains.optimism, + chainId: 10, + urls: { + apiURL: "https://optimism.blockscout.com/api", + browserURL: "https://optimism.blockscout.com", + }, + }, ], }; diff --git a/config/types.ts b/config/types.ts index 57c2251..e5563a6 100644 --- a/config/types.ts +++ b/config/types.ts @@ -13,6 +13,8 @@ export interface ChainConfig { export enum Chains { soneiumMinato = "soneiumMinato", soneium = "soneium", + optimism = "optimism", + opSepolia = "opSepolia", } export type Configs = { diff --git a/logs/deploy-optimism.log b/logs/deploy-optimism.log new file mode 100644 index 0000000..52cb5de --- /dev/null +++ b/logs/deploy-optimism.log @@ -0,0 +1,25 @@ +❯ hardhat deployAtNonce --network optimism +2025-06-19T12:45:00.114Z info: Using deployer address: 0x448dAe9299366ff611678fdA2f3a386791C95d41 +2025-06-19T12:45:00.279Z info: Current nonce: 0 +2025-06-19T12:45:00.280Z info: Fast forwarding nonce from 0 to 14... +2025-06-19T12:45:00.280Z info: Sending transaction to increment nonce to 1... +2025-06-19T12:45:06.215Z info: Sending transaction to increment nonce to 2... +2025-06-19T12:45:07.888Z info: Sending transaction to increment nonce to 3... +2025-06-19T12:45:09.539Z info: Sending transaction to increment nonce to 4... +2025-06-19T12:45:15.355Z info: Sending transaction to increment nonce to 5... +2025-06-19T12:45:21.196Z info: Sending transaction to increment nonce to 6... +2025-06-19T12:45:26.971Z info: Sending transaction to increment nonce to 7... +2025-06-19T12:45:33.099Z info: Sending transaction to increment nonce to 8... +2025-06-19T12:45:38.894Z info: Sending transaction to increment nonce to 9... +2025-06-19T12:45:44.736Z info: Sending transaction to increment nonce to 10... +2025-06-19T12:45:50.547Z info: Sending transaction to increment nonce to 11... +2025-06-19T12:45:56.337Z info: Sending transaction to increment nonce to 12... +2025-06-19T12:45:58.045Z info: Sending transaction to increment nonce to 13... +2025-06-19T12:46:03.845Z info: Sending transaction to increment nonce to 14... +2025-06-19T12:46:09.637Z info: Deploying AstarToken implementation at nonce 14... +2025-06-19T12:46:11.394Z info: Waiting for implementation deployment to be confirmed... +2025-06-19T12:46:25.813Z info: AstarToken implementation deployed to: 0xE01338496c8b07490Ae642AF53AAa5A8e6645B4C +2025-06-19T12:46:26.012Z info: Deploying proxy with nonce 15... +2025-06-19T12:46:27.641Z info: Waiting for proxy deployment to be confirmed... +2025-06-19T12:46:41.324Z info: Proxy deployed to: 0x2CAE934a1e84F693fbb78CA5ED3B0A6893259441 +2025-06-19T12:46:41.324Z info: ✅ SUCCESS: Proxy deployed to expected address 0x2CAE934a1e84F693fbb78Ca5ED3B0A6893259441 \ No newline at end of file diff --git a/package.json b/package.json index c396f03..a5df1be 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "hardhat test", "coverage": "hardhat coverage", "deploy:soneiumMinato": "hardhat deployAstarTokenAndPool --network soneiumMinato --pooltype burnMint --verifycontract true", - "deploy:soneium": "hardhat deployAstarTokenAndPool --network soneium --pooltype burnMint --verifycontract true" + "deploy:soneium": "hardhat deployAstarTokenAndPool --network soneium --pooltype burnMint --verifycontract true", + "deploy:optimism": "hardhat deployAtNonce --network optimism --nonce 15 --verifycontract true" }, "keywords": [], "author": "", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a764c15..3d0276e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,7 +38,7 @@ importers: version: 1.0.12(hardhat@2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3)) '@nomicfoundation/hardhat-toolbox': specifier: ^5.0.0 - version: 5.0.0(ko2umxykg7tmm4rm7w2jbpc3sy) + version: 5.0.0(52a4327497f445546532f18569d76bf1) '@nomicfoundation/hardhat-verify': specifier: ^2.0.0 version: 2.0.13(hardhat@2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3)) @@ -4652,7 +4652,7 @@ snapshots: ethereumjs-util: 7.1.5 hardhat: 2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3) - '@nomicfoundation/hardhat-toolbox@5.0.0(ko2umxykg7tmm4rm7w2jbpc3sy)': + '@nomicfoundation/hardhat-toolbox@5.0.0(52a4327497f445546532f18569d76bf1)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.5)(hardhat@2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3)))(chai@4.5.0)(ethers@6.13.5)(hardhat@2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3)) '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.5)(hardhat@2.23.0(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))(typescript@5.8.3)) diff --git a/tasks/deployAtNonce.ts b/tasks/deployAtNonce.ts new file mode 100644 index 0000000..86d71ec --- /dev/null +++ b/tasks/deployAtNonce.ts @@ -0,0 +1,127 @@ +import { task, types } from "hardhat/config"; +import { Chains, networks, logger, TokenContractName, ProxyContractName } from "../config"; + +task("deployAtNonce", "Deploys AstarToken implementation and proxy at specific nonces") + .addParam("nonce", "Target nonce for proxy deployment", 15, types.int) + .addOptionalParam("verifycontract", "Verify the contract on Blockchain scan", false, types.boolean) + .setAction(async (taskArgs, hre) => { + const { nonce: TARGET_PROXY_NONCE, verifycontract: verifyContract } = taskArgs; + const networkName = hre.network.name as Chains; + + // Ensure the network is configured + const networkConfig = networks[networkName]; + if (!networkConfig) { + throw new Error(`Network ${networkName} not found in config`); + } + + const { confirmations } = networkConfig; + + // Get signer + const [deployer] = await hre.ethers.getSigners(); + const deployerAddress = deployer.address; + logger.info(`Using deployer address: ${deployerAddress}`); + + // Check current nonce + const currentNonce = await hre.ethers.provider.getTransactionCount(deployerAddress); + logger.info(`Current nonce: ${currentNonce}`); + + // If we're at nonce 15 or higher already, we can't use nonce 15 for the proxy + if (currentNonce > TARGET_PROXY_NONCE) { + throw new Error(`Current nonce (${currentNonce}) is already higher than target proxy nonce (${TARGET_PROXY_NONCE})`); + } + + // Step 1: First deploy the implementation + // If current nonce is 14, deploy implementation; if lower, fast-forward to 14 first + const implNonce = TARGET_PROXY_NONCE - 1; // Implementation should be at nonce 14 if proxy is at 15 + + // Fast forward to implementation nonce if needed + if (currentNonce < implNonce) { + logger.info(`Fast forwarding nonce from ${currentNonce} to ${implNonce}...`); + + // Send dummy transactions to increment nonce + for (let i = currentNonce; i < implNonce; i++) { + logger.info(`Sending transaction to increment nonce to ${i + 1}...`); + + const response = await deployer.sendTransaction({ + to: deployerAddress, + value: hre.ethers.parseEther("0"), + gasLimit: 21000, + }); + + await response.wait(1); + } + } + + // Deploy the implementation + logger.info(`Deploying AstarToken implementation at nonce ${implNonce}...`); + const TokenFactory = await hre.ethers.getContractFactory(TokenContractName.AstarToken); + const implementation = await TokenFactory.deploy(); + + const implDeploymentTx = implementation.deploymentTransaction(); + if (!implDeploymentTx) throw new Error("Implementation deployment transaction not found"); + + logger.info(`Waiting for implementation deployment to be confirmed...`); + await implDeploymentTx.wait(confirmations); + + const implementationAddress = await implementation.getAddress(); + logger.info(`AstarToken implementation deployed to: ${implementationAddress}`); + + // Check that current nonce is now exactly TARGET_PROXY_NONCE + const proxyNonce = await hre.ethers.provider.getTransactionCount(deployerAddress); + + if (proxyNonce !== TARGET_PROXY_NONCE) { + throw new Error(`Expected nonce to be ${TARGET_PROXY_NONCE} for proxy deployment, but it's ${proxyNonce}`); + } + + // Step 2: Deploy the proxy at exactly nonce 15 + logger.info(`Deploying proxy with nonce ${TARGET_PROXY_NONCE}...`); + + // Encode the initialization call + const initData = implementation.interface.encodeFunctionData("initialize", [deployerAddress]); + + // Deploy the proxy + const ProxyFactory = await hre.ethers.getContractFactory(ProxyContractName); + const proxy = await ProxyFactory.deploy(implementationAddress, initData); + + const proxyDeploymentTx = proxy.deploymentTransaction(); + if (!proxyDeploymentTx) throw new Error("Proxy deployment transaction not found"); + + logger.info(`Waiting for proxy deployment to be confirmed...`); + await proxyDeploymentTx.wait(confirmations); + + const proxyAddress = await proxy.getAddress(); + logger.info(`Proxy deployed to: ${proxyAddress}`); + + // Check if proxy deployed to expected address + const expectedAddress = "0x2CAE934a1e84F693fbb78Ca5ED3B0A6893259441"; // The address from Soneium + if (proxyAddress.toLowerCase() === expectedAddress.toLowerCase()) { + logger.info(`✅ SUCCESS: Proxy deployed to expected address ${expectedAddress}`); + } else { + logger.error(`❌ ERROR: Proxy address ${proxyAddress} does not match expected address ${expectedAddress}`); + } + + // Verify contracts if requested + if (verifyContract) { + try { + // Verify implementation + logger.info("Verifying implementation contract..."); + await hre.run("verify:verify", { + address: implementationAddress, + constructorArguments: [], + }); + + // Verify proxy + logger.info("Verifying proxy contract..."); + await hre.run("verify:verify", { + address: proxyAddress, + constructorArguments: [implementationAddress, initData], + }); + + logger.info("Contracts verified successfully"); + } catch (error) { + logger.error("Verification failed:", error); + } + } + + return { implementation: implementationAddress, proxy: proxyAddress }; + }); \ No newline at end of file diff --git a/tasks/index.ts b/tasks/index.ts index 07d0698..b6e3b97 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -1 +1,2 @@ import "./deployAstarTokenAndPool"; +import "./deployAtNonce";