Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "21"
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Install Foundry
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "21"
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Install Foundry
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-npm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "21"
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Install Foundry
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "21"
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Install Foundry
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-periphery": "^1.4.4",
"@zetachain/networks": "14.0.0-rc1",
"@zetachain/protocol-contracts": "13.0.0",
"@zetachain/protocol-contracts": "13.1.0-rc3",
"@zetachain/protocol-contracts-solana": "^5.0.0",
"@zetachain/protocol-contracts-ton": "1.0.0-rc4",
"@zetachain/standard-contracts": "^2.0.1",
Expand Down
11 changes: 11 additions & 0 deletions packages/commands/src/query/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command } from "commander";

import { listCommand } from "./list";
import { showCommand } from "./show";

export const contractsCommand = new Command("contracts")
.alias("c")
.description("Contract registry commands")
.addCommand(listCommand)
.addCommand(showCommand)
.helpCommand(false);
118 changes: 118 additions & 0 deletions packages/commands/src/query/contracts/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import RegistryABI from "@zetachain/protocol-contracts/abi/Registry.sol/Registry.json";
import chalk from "chalk";
import { Command, Option } from "commander";
import { ethers } from "ethers";
import ora from "ora";
import { getBorderCharacters, table } from "table";
import { z } from "zod";

import { CONTRACT_REGISTRY_ADDRESS } from "../../../../../src/constants/addresses";
import { DEFAULT_EVM_RPC_URL } from "../../../../../src/constants/api";
import { contractsListOptionsSchema } from "../../../../../src/schemas/commands/contracts";
import { formatAddress } from "../../../../../utils/addressResolver";

type ContractsListOptions = z.infer<typeof contractsListOptionsSchema>;

interface ContractData {
addressBytes: string;
chainId: ethers.BigNumberish;
contractType: string;
}

export const fetchContracts = async (
rpcUrl: string
): Promise<ContractData[]> => {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const contractRegistry = new ethers.Contract(
CONTRACT_REGISTRY_ADDRESS,
RegistryABI.abi,
provider
);

const contracts =
(await contractRegistry.getAllContracts()) as ContractData[];
return contracts;
};

const formatContractsTable = (
contracts: ContractData[],
columns: ("type" | "address")[]
): string[][] => {
const headers = ["Chain ID"];

if (columns.includes("type")) headers.push("Type");
if (columns.includes("address")) headers.push("Address");

const rows = contracts.map((contract) => {
const baseRow = [contract.chainId.toString()];

if (columns.includes("type")) baseRow.push(contract.contractType);
if (columns.includes("address"))
baseRow.push(formatAddress(contract.addressBytes));

return baseRow;
});

return [headers, ...rows];
};

const main = async (options: ContractsListOptions) => {
const spinner = options.json
? null
: ora("Fetching contracts from registry...").start();

try {
const contracts = await fetchContracts(options.rpc);
if (!options.json) {
spinner?.succeed(`Successfully fetched ${contracts.length} contracts`);
}

const sortedContracts = [...contracts].sort(
(a, b) => parseInt(a.chainId.toString()) - parseInt(b.chainId.toString())
);

if (options.json) {
const jsonOutput = sortedContracts.map((c: ContractData) => ({
address: formatAddress(c.addressBytes),
chainId: c.chainId.toString(),
type: c.contractType,
}));
console.log(JSON.stringify(jsonOutput, null, 2));
return;
}

if (contracts.length === 0) {
console.log(chalk.yellow("No contracts found in the registry"));
return;
}

const tableData = formatContractsTable(sortedContracts, options.columns);
const tableOutput = table(tableData, {
border: getBorderCharacters("norc"),
});

console.log(tableOutput);
} catch (error) {
if (!options.json) {
spinner?.fail("Failed to fetch contracts");
}
console.error(chalk.red("Error details:"), error);
}
};

export const listCommand = new Command("list")
.alias("l")
.description("List all contracts from the registry")
.addOption(
new Option("--rpc <url>", "Custom RPC URL").default(DEFAULT_EVM_RPC_URL)
)
.option("--json", "Output contracts as JSON")
.addOption(
new Option("--columns <values...>", "Additional columns to show")
.choices(["type", "address"])
.default(["type", "address"])
)
.action(async (options: ContractsListOptions) => {
const validatedOptions = contractsListOptionsSchema.parse(options);
await main(validatedOptions);
});
81 changes: 81 additions & 0 deletions packages/commands/src/query/contracts/show.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import chalk from "chalk";
import { Command, Option } from "commander";
import { ethers } from "ethers";
import { z } from "zod";

import { DEFAULT_EVM_RPC_URL } from "../../../../../src/constants/api";
import { contractsShowOptionsSchema } from "../../../../../src/schemas/commands/contracts";
import { formatAddress } from "../../../../../utils/addressResolver";
import { fetchContracts } from "./list";

type ContractsShowOptions = z.infer<typeof contractsShowOptionsSchema>;

interface ContractData {
addressBytes: string;
chainId: ethers.BigNumberish;
contractType: string;
}

const findContractByChainId = (
contracts: ContractData[],
chainId: string,
type: string
): ContractData | null => {
const matchingContracts = contracts.filter(
(contract) => contract.chainId.toString() === chainId
);

return (
matchingContracts.find(
(contract) => contract.contractType.toLowerCase() === type.toLowerCase()
) || null
);
};

const main = async (options: ContractsShowOptions) => {
try {
const contracts = await fetchContracts(options.rpc);

const contract = findContractByChainId(
contracts,
options.chainId,
options.type
);

if (!contract) {
console.error(
chalk.red(
`Contract on chain '${options.chainId}' with type '${options.type}' not found`
)
);
console.log(chalk.yellow("Available contracts:"));
const availableContracts = contracts
.map((c) => `${c.chainId.toString()}:${c.contractType}`)
.sort();
console.log(availableContracts.join(", "));
process.exit(1);
}

const address = formatAddress(contract.addressBytes);
console.log(address);
} catch (error) {
console.error(chalk.red("Error details:"), error);
}
};

export const showCommand = new Command("show")
.alias("s")
.description("Show contract address for a specific chain and type")
.addOption(
new Option("--rpc <url>", "Custom RPC URL").default(DEFAULT_EVM_RPC_URL)
)
.addOption(
new Option("--chain-id -c <chainId>", "Chain ID").makeOptionMandatory()
)
.addOption(
new Option("--type -t <type>", "Contract type").makeOptionMandatory()
)
.action(async (options: ContractsShowOptions) => {
const validatedOptions = contractsShowOptionsSchema.parse(options);
await main(validatedOptions);
});
2 changes: 2 additions & 0 deletions packages/commands/src/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from "commander";
import { balancesCommand } from "./balances";
import { cctxCommand } from "./cctx";
import { chainsCommand } from "./chains";
import { contractsCommand } from "./contracts";
import { feesCommand } from "./fees";
import { tokensCommand } from "./tokens";

Expand All @@ -11,6 +12,7 @@ export const queryCommand = new Command("query")
.summary("Query commands")
.addCommand(balancesCommand)
.addCommand(cctxCommand)
.addCommand(contractsCommand)
.addCommand(feesCommand)
.addCommand(tokensCommand)
.addCommand(chainsCommand)
Expand Down
2 changes: 2 additions & 0 deletions src/constants/addresses.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const MULTICALL_ADDRESS = "0xca11bde05977b3631167028862be2a173976ca11";
export const CONTRACT_REGISTRY_ADDRESS =
"0x7cce3eb018bf23e1fe2a32692f2c77592d110394";
15 changes: 15 additions & 0 deletions src/schemas/commands/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from "zod";

import { DEFAULT_EVM_RPC_URL } from "../../constants/api";

export const contractsListOptionsSchema = z.object({
columns: z.array(z.enum(["type", "address"])).default(["type", "address"]),
json: z.boolean().default(false),
rpc: z.string().default(DEFAULT_EVM_RPC_URL),
});

export const contractsShowOptionsSchema = z.object({
chainId: z.string(),
rpc: z.string().default(DEFAULT_EVM_RPC_URL),
type: z.string(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export interface Revertable extends BaseContract {
onRevert: TypedContractMethod<
[revertContext: RevertContextStruct],
[void],
"nonpayable"
"payable"
>;

getFunction<T extends ContractMethod = ContractMethod>(
Expand All @@ -104,7 +104,7 @@ export interface Revertable extends BaseContract {
): TypedContractMethod<
[revertContext: RevertContextStruct],
[void],
"nonpayable"
"payable"
>;

filters: {};
Expand Down
Loading
Loading