Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
"@uniswap/v3-periphery": "^1.4.4",
"@zetachain/faucet-cli": "^4.1.1",
"@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 @@ -2,6 +2,7 @@ import { Command } from "commander";

import { balancesCommand } from "./balances";
import { cctxCommand } from "./cctx";
import { contractsCommand } from "./contracts";
import { feesCommand } from "./fees";
import { tokensCommand } from "./tokens";

Expand All @@ -10,6 +11,7 @@ export const queryCommand = new Command("query")
.summary("Query commands")
.addCommand(balancesCommand)
.addCommand(cctxCommand)
.addCommand(contractsCommand)
.addCommand(feesCommand)
.addCommand(tokensCommand)
.helpCommand(false);
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";
16 changes: 16 additions & 0 deletions src/schemas/commands/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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([]),
json: z.boolean().default(false),
rpc: z.string().default(DEFAULT_EVM_RPC_URL),
});

export const contractsShowOptionsSchema = z.object({
chainId: z.string(),
json: z.boolean().default(false),
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