-
Notifications
You must be signed in to change notification settings - Fork 3
Deploy: harden Base mainnet release workflow #16
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ on: | |
| workflow_dispatch: | ||
| inputs: | ||
| confirm: | ||
| description: 'Type DEPLOY to authorize mainnet deployment' | ||
| description: 'Type DEPLOY to authorize Base mainnet deployment' | ||
| required: true | ||
| default: '' | ||
|
|
||
|
|
@@ -27,23 +27,72 @@ jobs: | |
| node-version: 24 | ||
| cache: npm | ||
|
|
||
| - name: Install | ||
| run: cd base-agent && npm ci | ||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Preflight Base mainnet signer and RPC | ||
| env: | ||
| BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} | ||
| PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY || secrets.PRIVATE_KEY }} | ||
| run: | | ||
| node <<'NODE' | ||
| const { JsonRpcProvider, Wallet, formatEther } = require('ethers'); | ||
|
|
||
| async function main() { | ||
| const rpc = process.env.BASE_RPC_URL; | ||
| const key = process.env.PRIVATE_KEY; | ||
| if (!rpc) throw new Error('BASE_RPC_URL secret is required'); | ||
| if (!key) throw new Error('DEPLOYER_PRIVATE_KEY or PRIVATE_KEY secret is required'); | ||
|
|
||
| const provider = new JsonRpcProvider(rpc); | ||
| const network = await provider.getNetwork(); | ||
| if (network.chainId !== 8453n) { | ||
| throw new Error(`Refusing deployment: expected Base mainnet chainId 8453, got ${network.chainId}`); | ||
| } | ||
|
|
||
| const wallet = new Wallet(key, provider); | ||
| const balance = await provider.getBalance(wallet.address); | ||
| console.log(`Base mainnet chainId verified: ${network.chainId}`); | ||
| console.log(`Deployer address: ${wallet.address}`); | ||
| console.log(`Deployer balance: ${formatEther(balance)} ETH`); | ||
| if (balance === 0n) throw new Error('Deployer has no ETH for gas'); | ||
| } | ||
|
|
||
| main().catch((error) => { | ||
| console.error(error.message || error); | ||
| process.exit(1); | ||
| }); | ||
| NODE | ||
|
|
||
| - name: Compile | ||
| run: cd base-agent && npm run compile | ||
| run: npm run compile | ||
|
|
||
| - name: Test | ||
| run: cd base-agent && npm test | ||
| run: npm test | ||
|
|
||
| - name: Deploy Base Mainnet | ||
| - name: Deploy AgentExecutor to Base mainnet | ||
| env: | ||
| BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} | ||
| PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} | ||
| BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} | ||
| run: cd base-agent && npm run deploy:base | ||
| PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY || secrets.PRIVATE_KEY }} | ||
| DESTINATION_ADDRESS: ${{ secrets.DESTINATION_ADDRESS }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The root deploy script merely copies this environment value into Useful? React with 👍 / 👎. |
||
| run: npm run deploy:base | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
| - name: Capture deployment address | ||
| run: | | ||
| test -f deployment-base.json | ||
| CONTRACT_ADDRESS="$(node -p "require('./deployment-base.json').address")" | ||
| test -n "$CONTRACT_ADDRESS" | ||
| echo "CONTRACT_ADDRESS=$CONTRACT_ADDRESS" >> "$GITHUB_ENV" | ||
| echo "Deployed AgentExecutor: $CONTRACT_ADDRESS" | ||
|
|
||
| - name: Verify Deployment | ||
| - name: Verify on BaseScan | ||
| env: | ||
| CONTRACT_ADDRESS: ${{ secrets.CONTRACT_ADDRESS }} | ||
| run: cd base-agent && node tasks/verify.js | ||
| BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} | ||
| ETHERSCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} | ||
| run: npx hardhat verify --network base "$CONTRACT_ADDRESS" | ||
|
|
||
| - name: Upload deployment record | ||
| uses: actions/upload-artifact@v4 | ||
|
Comment on lines
+94
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If BaseScan verification fails—for example because its API key is missing/invalid or the new contract has not been indexed yet—the upload step is skipped by the workflow's implicit Useful? React with 👍 / 👎. |
||
| with: | ||
| name: deployment-base-mainnet | ||
| path: deployment-base.json | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the deployer has any dust balance greater than zero but less than the deployment transaction's gas cost, this preflight succeeds even though the gated mainnet deployment is guaranteed to fail with insufficient funds. The check should estimate the
AgentExecutordeployment gas and account for current fee data, or enforce a conservative minimum balance, rather than treating every nonzero balance as gas-ready.Useful? React with 👍 / 👎.