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
60 changes: 3 additions & 57 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,7 @@

PRIVATE_KEY="PRIVATE_KEY"

# Set up zerodev project id for each network you want to deploy on
# Sign up for project IDs here: https://dashboard.zerodev.app/
ZERODEV_API_KEY=""
ZERODEV_PROJECT_ID=""

ARBITRUM_PROJECT_ID=""
ARBITRUM_NOVA_PROJECT_ID=""
ARBITRUM_SEPOLIA_PROJECT_ID=""
ASTAR_ZKEVM_PROJECT_ID=""
ASTAR_ZKYOTO_PROJECT_ID=""
AVALANCHE_PROJECT_ID=""
AVALANCHE_FUJI_PROJECT_ID=""
BASE_PROJECT_ID=""
BASE_SEPOLIA_PROJECT_ID=""
BLAST_PROJECT_ID=""
BLAST_SEPOLIA_PROJECT_ID=""
BSC_PROJECT_ID=""
CELO_PROJECT_ID=""
CELO_ALFAJORES_PROJECT_ID=""
CYBER_PROJECT_ID=""
CYBER_TESTNET_PROJECT_ID=""
DEGEN_PROJECT_ID=""
MAINNET_PROJECT_ID=""
SEPOILA_PROJECT_ID=""
OPTIMISM_PROJECT_ID=""
OPTIMISM_SEPOLIA_PROJECT_ID=""
OPBNB_PROJECT_ID=""
POLYGON_PROJECT_ID=""
POLYGON_AMOY_PROJECT_ID=""
LINEA_PROJECT_ID=""
LINEA_TESTNET_PROJECT_ID=""

# Set up etherscan API key for each network you want to verify on

ARBITRUM_ETHERSCAN_API_KEY=""
ARBITRUM_NOVA_ETHERSCAN_API_KEY=""
ARBITRUM_SEPOLIA_ETHERSCAN_API_KEY=""
ASTAR_ZKEVM_ETHERSCAN_API_KEY=""
ASTAR_ZKYOTO_ETHERSCAN_API_KEY=""
AVALANCHE_ETHERSCAN_API_KEY=""
AVALANCHE_FUJI_ETHERSCAN_API_KEY=""
BASE_ETHERSCAN_API_KEY=""
BASE_SEPOLIA_ETHERSCAN_API_KEY=""
BLAST_ETHERSCAN_API_KEY=""
BLAST_SEPOLIA_ETHERSCAN_API_KEY=""
BSC_ETHERSCAN_API_KEY=""
CELO_ETHERSCAN_API_KEY=""
CELO_ALFAJORES_ETHERSCAN_API_KEY=""
CYBER_ETHERSCAN_API_KEY=""
CYBER_TESTNET_ETHERSCAN_API_KEY=""
DEGEN_ETHERSCAN_API_KEY=""
MAINNET_ETHERSCAN_API_KEY=""
SEPOILA_ETHERSCAN_API_KEY=""
OPTIMISM_ETHERSCAN_API_KEY=""
OPTIMISM_SEPOLIA_ETHERSCAN_API_KEY=""
OPBNB_ETHERSCAN_API_KEY=""
POLYGON_ETHERSCAN_API_KEY=""
POLYGON_AMOY_ETHERSCAN_API_KEY=""
LINEA_ETHERSCAN_API_KEY=""
LINEA_TESTNET_ETHERSCAN_API_KEY=""
ETHERSCAN_API_KEY=""
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @zerodev/orchestra

## 0.2.0

### Minor Changes

- update to use zerodev v3 api
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Orchestra is a CLI for deterministically deploying contracts to multiple chains,
1. `npm install -g @zerodev/orchestra`
2. Create a `.env` file based on `.env.example`
- You can acquire the project IDs from [the ZeroDev dashboard](https://dashboard.zerodev.app/)
- Use [this link](https://dashboard.zerodev.app/account/api-key) to get the API key for the team
3. Test the installation by running `zerodev -h`

## Usage
Expand Down
Binary file modified bun.lockb
Binary file not shown.
29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zerodev/orchestra",
"version": "0.1.3",
"version": "0.2.0",
"description": "",
"main": "dist/index.js",
"type": "module",
Expand Down Expand Up @@ -43,29 +43,30 @@
],
"license": "MIT",
"dependencies": {
"@zerodev/ecdsa-validator": "^5.4.0",
"@zerodev/sdk": "^5.4.3",
"@zerodev/ecdsa-validator": "^5.4.9",
"@zerodev/sdk": "^5.4.36",
"chalk": "4.1.2",
"cli-table3": "^0.6.3",
"cli-table3": "^0.6.5",
"commander": "^11.1.0",
"dotenv": "^16.3.1",
"figlet": "^1.7.0",
"ora": "^8.0.1",
"tslib": "^2.6.2"
"dotenv": "^16.5.0",
"figlet": "^1.8.1",
"ora": "^8.2.0",
"tslib": "^2.8.1",
"viem": "^2.30.6"
},
"devDependencies": {
"@biomejs/biome": "^1.4.1",
"@biomejs/biome": "^1.9.4",
"@changesets/changelog-git": "^0.1.14",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.27.1",
"@changesets/cli": "^2.29.4",
"@size-limit/esbuild-why": "^9.0.0",
"@size-limit/preset-small-lib": "^9.0.0",
"@types/figlet": "^1.5.8",
"@types/node": "^18.19.4",
"@types/figlet": "^1.7.0",
"@types/node": "^18.19.110",
"@types/ora": "^3.2.0",
"simple-git-hooks": "^2.9.0",
"simple-git-hooks": "^2.13.0",
"ts-node": "^10.9.2",
"typescript": "^5.2.2"
"typescript": "^5.8.3"
},
"simple-git-hooks": {
"pre-commit": "bun run format && bun run lint"
Expand Down
26 changes: 20 additions & 6 deletions src/action/deployContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ora from "ora"
import type { Address, Hex } from "viem"
import { http, createPublicClient, getAddress } from "viem"
import { createKernelClient, getZeroDevBundlerRPC } from "../clients/index.js"
import { type Chain, DEPLOYER_CONTRACT_ADDRESS } from "../constant.js"
import { DEPLOYER_CONTRACT_ADDRESS, type ZerodevChain } from "../constant.js"
import { ensureHex, writeErrorLogToFile } from "../utils/index.js"
import { computeContractAddress } from "./computeAddress.js"
import { DeploymentStatus, checkDeploymentOnChain } from "./findDeployment.js"
Expand All @@ -21,15 +21,15 @@ type DeployResult = [string, string]

export const deployToChain = async (
privateKey: Hex,
chain: Chain,
chain: ZerodevChain,
bytecode: Hex,
salt: Hex,
expectedAddress: string | undefined,
callGasLimit: bigint | undefined
): Promise<DeployResult> => {
const publicClient = createPublicClient({
chain: chain.viemChainObject,
transport: http(getZeroDevBundlerRPC(chain.projectId))
chain: chain,
transport: http()

Copilot AI Jun 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createPublicClient is configured with http() without a URL; it should be provided with the chain's RPC URL, e.g., transport: http(chain.rpcUrls.default.http[0]).

Suggested change
transport: http()
transport: http(chain.rpcUrls.default.http[0])

Copilot uses AI. Check for mistakes.
})
const kernelAccountClient = await createKernelClient(privateKey, chain)

Expand Down Expand Up @@ -86,20 +86,28 @@ export const deployToChain = async (
])
})

await kernelAccountClient.waitForUserOperationReceipt({
hash: opHash
})
await kernelAccountClient.getUserOperationReceipt({
hash: opHash
})

return [getAddress(result.data as Address), opHash]
}

export const deployContracts = async (
privateKey: Hex,
bytecode: Hex,
chains: Chain[],
chains: ZerodevChain[],
salt: Hex,
expectedAddress: string | undefined,
callGasLimit: bigint | undefined
) => {
const spinner = ora(
`Deploying contract on ${chains.map((chain) => chain.name).join(", ")}`
).start()
let anyError = false
const deployments = chains.map(async (chain) => {
return deployToChain(
privateKey,
Expand Down Expand Up @@ -130,6 +138,7 @@ export const deployContracts = async (
)
} else {
writeErrorLogToFile(chain.name, error)
anyError = true
spinner.fail(
`Deployment for ${chalk.redBright(
chain.name
Expand All @@ -141,5 +150,10 @@ export const deployContracts = async (

await Promise.allSettled(deployments)
spinner.stop()
console.log("✅ All deployments process successfully finished!")
if (anyError) {
console.log("❌ Some deployments failed!")
process.exit(1)
} else {
console.log("✅ All deployments process successfully finished!")
}
}
13 changes: 7 additions & 6 deletions src/action/findDeployment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Address, Hex, PublicClient } from "viem"
import { http, createPublicClient } from "viem"
import { getZeroDevBundlerRPC } from "../clients/index.js"
import { type Chain, DEPLOYER_CONTRACT_ADDRESS } from "../constant.js"
import { DEPLOYER_CONTRACT_ADDRESS, type ZerodevChain } from "../constant.js"
import { computeContractAddress } from "./computeAddress.js"
export enum DeploymentStatus {
Deployed = 0,
Expand Down Expand Up @@ -29,12 +29,12 @@ export const checkDeploymentOnChain = async (
export const findDeployment = async (
bytecode: Hex,
salt: Hex,
chains: Chain[]
chains: ZerodevChain[]
): Promise<{
address: Address
deployedChains: Chain[]
notDeployedChains: Chain[]
errorChains?: Chain[]
deployedChains: ZerodevChain[]
notDeployedChains: ZerodevChain[]
errorChains?: ZerodevChain[]
}> => {
const address = computeContractAddress(
DEPLOYER_CONTRACT_ADDRESS,
Expand All @@ -46,7 +46,8 @@ export const findDeployment = async (
chains.map((chain) => {
return checkDeploymentOnChain(
createPublicClient({
transport: http(getZeroDevBundlerRPC(chain.projectId))
chain: chain,
transport: http()

Copilot AI Jun 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In createPublicClient call inside findDeployment, http() is invoked without providing the RPC URL. It should use the chain's RPC URL, for example: transport: http(chain.rpcUrls.default.http[0]).

Suggested change
transport: http()
transport: http(chain.rpcUrls.default.http[0])

Copilot uses AI. Check for mistakes.
}),
address
).catch(() => DeploymentStatus.Error)
Expand Down
42 changes: 15 additions & 27 deletions src/action/verifyContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import util from "node:util"
import chalk from "chalk"
import ora from "ora"
import type { Address } from "viem"
import type { Chain } from "../constant.js"
import type { ZerodevChain } from "../constant.js"

const execPromise = util.promisify(exec)

Expand All @@ -20,31 +20,16 @@ async function checkForgeAvailability() {
async function verifyContract(
contractName: string,
contractAddress: Address,
chain: Chain
chain: ZerodevChain
): Promise<string> {
if (
!chain.etherscanApiKey &&
chain.name !== "avalanche" &&
chain.name !== "avalanche-fuji" &&
chain.name !== "opbnb" &&
chain.name !== "astar-zkatana"
) {
if (!chain.explorerAPI) {
throw new Error(
`Etherscan API key is not provided for ${chalk.yellowBright(
`Explorer API key is not provided for ${chalk.yellowBright(
chain.name
)}`
)
}

if (["opbnb", "astar-zkatana"].includes(chain.name)) {
throw new Error(
`Verification is not supported on ${chalk.yellowBright(chain.name)}`
)
}

const effectiveChainName =
chain.name === "linea-testnet" ? "linea-goerli" : chain.name
const command = `forge verify-contract -c ${effectiveChainName} ${contractAddress} ${contractName} -e ${chain.etherscanApiKey}`
const command = `forge verify-contract --chain ${chain.id} --verifier etherscan ${contractAddress} ${contractName} -e ${chain.explorerAPI} -a v2`

try {
const { stdout, stderr } = await execPromise(command)
Expand All @@ -69,12 +54,11 @@ async function verifyContract(
export const verifyContracts = async (
contractName: string,
contractAddress: Address,
chains: Chain[]
chains: ZerodevChain[]
) => {
await checkForgeAvailability()

const spinner = ora().start("Verifying contracts...")

let anyError = false
const verificationPromises = chains.map((chain) =>
verifyContract(contractName, contractAddress, chain)
.then((message) => {
Expand All @@ -85,18 +69,22 @@ export const verifyContracts = async (
}
})
.catch((error) => {
ora()
anyError = true
return ora()
.fail(
`Verification failed on ${chain.name}: ${error.message}`
)
.start()
.stop()
})
)

// Wait for all verifications to complete
await Promise.all(verificationPromises)

spinner.stop()
console.log("✅ All verifications process successfully finished!")
if (anyError) {
console.log("❌ Some verifications failed!")
process.exit(1)
} else {
console.log("✅ All verifications process successfully finished!")
}
}
22 changes: 13 additions & 9 deletions src/clients/createKernelClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ import {
createKernelAccountClient,
createZeroDevPaymasterClient
} from "@zerodev/sdk"
import { getUserOperationGasPrice } from "@zerodev/sdk"
import { KERNEL_V3_1, getEntryPoint } from "@zerodev/sdk/constants"
import type { Hex } from "viem"

import { http, createPublicClient } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import type { Chain } from "../constant.js"
import type { ZerodevChain } from "../constant.js"
import { getZeroDevBundlerRPC, getZeroDevPaymasterRPC } from "./index.js"

export const createKernelClient = async (
privateKey: Hex,
chain: Chain
chain: ZerodevChain
): Promise<KernelAccountClient> => {
const rpcUrl = getZeroDevBundlerRPC(chain.projectId, "PIMLICO")
const paymasterRpcUrl = getZeroDevPaymasterRPC(chain.projectId, "PIMLICO")
const rpcUrl = getZeroDevBundlerRPC(chain.id, "PIMLICO")
const paymasterRpcUrl = getZeroDevPaymasterRPC(chain.id, "PIMLICO")
const entryPoint = getEntryPoint("0.7")

const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain.viemChainObject
chain: chain,
transport: http(chain.rpcUrls.default.http[0])
})
const signer = privateKeyToAccount(privateKey)

Expand All @@ -41,13 +44,14 @@ export const createKernelClient = async (
})

const zerodevPaymaster = createZeroDevPaymasterClient({
chain: chain.viemChainObject,
chain: chain,
transport: http(paymasterRpcUrl)
})
// Construct a Kernel account client
const kernelClient = createKernelAccountClient({
account,
chain: chain.viemChainObject,
account: account,
client: publicClient,
chain: chain,
bundlerTransport: http(rpcUrl),
paymaster: {
getPaymasterData(userOperation) {
Expand Down
Loading
Loading