Skip to content

Commit 88635ef

Browse files
committed
Commander.js CLI
1 parent 2b83efb commit 88635ef

File tree

9 files changed

+468
-2
lines changed

9 files changed

+468
-2
lines changed

examples/call/commands/common.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import path from "path";
2+
import fs from "fs";
3+
import { ethers } from "ethers";
4+
import { Command } from "commander";
5+
6+
export const getAbi = (name: string) => {
7+
const abiPath = path.resolve(
8+
__dirname,
9+
path.join("..", "artifacts", "contracts", `${name}.sol`, `${name}.json`)
10+
);
11+
return JSON.parse(fs.readFileSync(abiPath, "utf8"));
12+
};
13+
14+
export const createRevertOptions = (options: {
15+
abortAddress: string;
16+
callOnRevert: boolean;
17+
onRevertGasLimit: string;
18+
revertAddress: string;
19+
revertMessage: string;
20+
}) => {
21+
return {
22+
abortAddress: options.abortAddress,
23+
callOnRevert: options.callOnRevert,
24+
onRevertGasLimit: BigInt(options.onRevertGasLimit),
25+
revertAddress: options.revertAddress,
26+
revertMessage: ethers.hexlify(ethers.toUtf8Bytes(options.revertMessage)),
27+
};
28+
};
29+
30+
export const createCommand = (name: string) => {
31+
return new Command(name)
32+
.requiredOption(
33+
"-c, --contract <address>",
34+
"The address of the deployed contract"
35+
)
36+
.requiredOption(
37+
"-r, --receiver <address>",
38+
"The address of the receiver contract"
39+
)
40+
.option("--call-on-revert", "Whether to call on revert", false)
41+
.option(
42+
"--revert-address <address>",
43+
"Revert address",
44+
"0x0000000000000000000000000000000000000000"
45+
)
46+
.option(
47+
"--abort-address <address>",
48+
"Abort address",
49+
"0x0000000000000000000000000000000000000000"
50+
)
51+
.option("--revert-message <string>", "Revert message", "0x")
52+
.option(
53+
"--on-revert-gas-limit <number>",
54+
"Gas limit for revert tx",
55+
"500000"
56+
)
57+
.option("-n, --name <contract>", "Contract name", "Connected")
58+
.option(
59+
"--rpc <url>",
60+
"RPC endpoint",
61+
"https://zetachain-athens-evm.blockpi.network/v1/rpc/public"
62+
)
63+
.option("--private-key <key>", "Private key to sign the transaction");
64+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ethers } from "ethers";
2+
3+
import { createCommand, createRevertOptions, getAbi } from "../common";
4+
5+
const main = async (options: any) => {
6+
const provider = new ethers.JsonRpcProvider(options.rpc);
7+
const signer = new ethers.Wallet(options.privateKey, provider);
8+
9+
const message = ethers.AbiCoder.defaultAbiCoder().encode(
10+
options.types,
11+
options.values
12+
);
13+
14+
const { abi } = getAbi(options.name);
15+
const contract = new ethers.Contract(options.contract, abi, signer);
16+
17+
const tx = await contract.call(
18+
options.receiver,
19+
message,
20+
createRevertOptions(options)
21+
);
22+
await tx.wait();
23+
24+
console.log(`✅ Transaction sent: ${tx.hash}`);
25+
};
26+
27+
export const connectedCallCommand = createCommand("connected-call")
28+
.requiredOption("-t, --types <types...>", "Parameter types as JSON string")
29+
.requiredOption("-v, --values <values...>", "The values of the parameters")
30+
.action(main);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ethers } from "ethers";
2+
3+
import { createCommand, createRevertOptions, getAbi } from "../common";
4+
5+
const main = async (options: any) => {
6+
const provider = new ethers.JsonRpcProvider(options.rpc);
7+
const signer = new ethers.Wallet(options.privateKey, provider);
8+
9+
const { abi } = getAbi(options.name);
10+
const contract = new ethers.Contract(options.contract, abi, signer);
11+
12+
const tx = await contract.deposit(
13+
options.receiver,
14+
createRevertOptions(options),
15+
{ value: ethers.parseEther(options.amount) }
16+
);
17+
await tx.wait();
18+
19+
console.log(`✅ Transaction sent: ${tx.hash}`);
20+
};
21+
22+
export const connectedDepositCommand = createCommand("connected-deposit")
23+
.requiredOption("-a, --amount <number>", "Amount to deposit")
24+
.action(main);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ethers } from "ethers";
2+
3+
import { createCommand, createRevertOptions, getAbi } from "../common";
4+
5+
const main = async (options: any) => {
6+
const provider = new ethers.JsonRpcProvider(options.rpc);
7+
const signer = new ethers.Wallet(options.privateKey, provider);
8+
9+
const message = ethers.AbiCoder.defaultAbiCoder().encode(
10+
options.types,
11+
options.values
12+
);
13+
14+
const { abi } = getAbi(options.name);
15+
const contract = new ethers.Contract(options.contract, abi, signer);
16+
17+
const tx = await contract.depositAndCall(
18+
options.receiver,
19+
message,
20+
createRevertOptions(options),
21+
{ value: ethers.parseEther(options.amount) }
22+
);
23+
await tx.wait();
24+
25+
console.log(`✅ Transaction sent: ${tx.hash}`);
26+
};
27+
28+
export const connectedDepositAndCallCommand = createCommand(
29+
"connected-deposit-and-call"
30+
)
31+
.requiredOption("-t, --types <types...>", "Parameter types as JSON string")
32+
.requiredOption("-v, --values <values...>", "The values of the parameters")
33+
.requiredOption("-a, --amount <number>", "Amount to deposit")
34+
.action(main);

examples/call/commands/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env npx tsx
2+
import { Command } from "commander";
3+
4+
import { connectedCallCommand } from "./connected/call";
5+
import { connectedDepositCommand } from "./connected/deposit";
6+
import { connectedDepositAndCallCommand } from "./connected/depositAndCall";
7+
import { universalCallCommand } from "./universal/call";
8+
import { universalWithdrawCommand } from "./universal/withdraw";
9+
10+
const program = new Command()
11+
.helpCommand(false)
12+
.addCommand(connectedCallCommand)
13+
.addCommand(connectedDepositCommand)
14+
.addCommand(connectedDepositAndCallCommand)
15+
.addCommand(universalCallCommand)
16+
.addCommand(universalWithdrawCommand);
17+
18+
if (require.main === module) program.parse();
19+
20+
export default program;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { ethers } from "ethers";
2+
3+
import { createCommand, createRevertOptions, getAbi } from "../common";
4+
import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json";
5+
6+
const main = async (options: any) => {
7+
const provider = new ethers.JsonRpcProvider(options.rpc);
8+
const signer = new ethers.Wallet(options.privateKey, provider);
9+
10+
const message = ethers.AbiCoder.defaultAbiCoder().encode(
11+
options.types,
12+
options.values
13+
);
14+
15+
const functionSignature = ethers.id(options.function).slice(0, 10);
16+
17+
const payload = ethers.hexlify(ethers.concat([functionSignature, message]));
18+
19+
const { abi } = getAbi(options.name);
20+
const contract = new ethers.Contract(options.contract, abi, signer);
21+
22+
const zrc20 = new ethers.Contract(options.zrc20, ZRC20ABI.abi, signer);
23+
const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(
24+
options.callOptionsGasLimit
25+
);
26+
const zrc20TransferTx = await zrc20.approve(options.contract, gasFee);
27+
28+
await zrc20TransferTx.wait();
29+
30+
const tx = await contract.call(
31+
options.receiver,
32+
options.zrc20,
33+
payload,
34+
{
35+
gasLimit: options.callOptionsGasLimit,
36+
isArbitraryCall: options.callOptionsIsArbitraryCall,
37+
},
38+
createRevertOptions(options),
39+
{ gasLimit: 2000000 }
40+
);
41+
await tx.wait();
42+
43+
console.log(`✅ Transaction sent: ${tx.hash}`);
44+
};
45+
46+
export const universalCallCommand = createCommand("universal-call")
47+
.requiredOption("-t, --types <types...>", "Parameter types as JSON string")
48+
.requiredOption("-v, --values <values...>", "The values of the parameters")
49+
.option("--call-options-is-arbitrary-call", "Call any function", false)
50+
.option("--call-options-gas-limit", "The gas limit for the call", "500000")
51+
.option(
52+
"--function <function>",
53+
`Function to call (example: "hello(string)")`
54+
)
55+
.option("--zrc20 <address>", "The address of ZRC-20 to pay fees")
56+
.action(main);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ethers } from "ethers";
2+
3+
import { createCommand, createRevertOptions, getAbi } from "../common";
4+
import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json";
5+
6+
const main = async (options: any) => {
7+
const provider = new ethers.JsonRpcProvider(options.rpc);
8+
const signer = new ethers.Wallet(options.privateKey, provider);
9+
10+
const { abi } = getAbi(options.name);
11+
const contract = new ethers.Contract(options.contract, abi, signer);
12+
13+
const zrc20 = new ethers.Contract(options.zrc20, ZRC20ABI.abi, signer);
14+
const [gasZRC20, gasFee] = await zrc20.withdrawGasFee();
15+
16+
const zrc20TransferTx = await zrc20.approve(options.contract, gasFee);
17+
18+
await zrc20TransferTx.wait();
19+
20+
const decimals = await zrc20.decimals();
21+
if (gasZRC20 === options.zrc20) {
22+
const targetTokenApprove = await zrc20.approve(
23+
options.contract,
24+
gasFee + ethers.parseUnits(options.amount, decimals)
25+
);
26+
await targetTokenApprove.wait();
27+
} else {
28+
const targetTokenApprove = await zrc20.approve(
29+
options.contract,
30+
ethers.parseUnits(options.amount, decimals)
31+
);
32+
await targetTokenApprove.wait();
33+
const gasFeeApprove = await gasZRC20.approve(options.contract, gasFee);
34+
await gasFeeApprove.wait();
35+
}
36+
37+
const tx = await contract.withdraw(
38+
options.receiver,
39+
ethers.parseUnits(options.amount, decimals),
40+
options.zrc20,
41+
createRevertOptions(options)
42+
);
43+
await tx.wait();
44+
45+
console.log(`✅ Transaction sent: ${tx.hash}`);
46+
};
47+
48+
export const universalWithdrawCommand = createCommand("universal-withdraw")
49+
.requiredOption("--amount <number>", "Amount to withdraw")
50+
.option("--zrc20 <address>", "The address of ZRC-20 to pay fees")
51+
.action(main);

examples/call/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@
4242
"eslint-plugin-simple-import-sort": "^10.0.0",
4343
"eslint-plugin-sort-keys-fix": "^1.1.2",
4444
"eslint-plugin-typescript-sort-keys": "^2.3.0",
45-
"ethers": "^5.4.7",
45+
"ethers": "^6.14.4",
4646
"hardhat": "^2.17.2",
4747
"hardhat-gas-reporter": "^1.0.8",
4848
"prettier": "^2.8.8",
4949
"solidity-coverage": "^0.8.0",
5050
"ts-node": ">=8.0.0",
51+
"tsx": "^4.20.3",
5152
"typechain": "^8.1.0",
5253
"typescript": ">=4.5.0"
5354
},

0 commit comments

Comments
 (0)