Skip to content
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
58 changes: 58 additions & 0 deletions DEPLOY_AT_NEW_NETWORK.md
Original file line number Diff line number Diff line change
@@ -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
```

22 changes: 22 additions & 0 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
37 changes: 33 additions & 4 deletions config/networks.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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: [
{
Expand All @@ -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",
},
},
],
};

Expand Down
2 changes: 2 additions & 0 deletions config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface ChainConfig {
export enum Chains {
soneiumMinato = "soneiumMinato",
soneium = "soneium",
optimism = "optimism",
opSepolia = "opSepolia",
}

export type Configs = {
Expand Down
25 changes: 25 additions & 0 deletions logs/deploy-optimism.log
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 127 additions & 0 deletions tasks/deployAtNonce.ts
Original file line number Diff line number Diff line change
@@ -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 };
});
1 change: 1 addition & 0 deletions tasks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import "./deployAstarTokenAndPool";
import "./deployAtNonce";