-
Notifications
You must be signed in to change notification settings - Fork 58
feat: cross-chain messaging #275
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
Conversation
📝 WalkthroughWalkthroughThis update introduces a new messaging example project, adding configuration files, a Solidity contract, TypeScript CLI commands for deploying and interacting with the contract, and supporting scripts for development and testing. The implementation includes robust contract artifact loading, cross-chain message handling, and comprehensive environment setup for both Hardhat and Foundry. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI (TS)
participant Ethers.js
participant Ethereum Node
participant Example Contract
User->>CLI (TS): Run deploy/connect/message command
CLI (TS)->>Ethers.js: Load contract ABI/bytecode, prepare transaction
Ethers.js->>Ethereum Node: Send transaction (deploy/connect/message)
Ethereum Node->>Example Contract: Execute contract logic
Example Contract-->>Ethereum Node: Emit events, handle logic
Ethereum Node-->>Ethers.js: Return transaction receipt
Ethers.js-->>CLI (TS): Return result
CLI (TS)-->>User: Output JSON summary
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~45 minutes 📜 Recent review detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Bug: Gas Parsing Error in Native Transfers
The message
command incorrectly uses options.amount
to parse the gas amount for native transfers. It should use options.gasAmount
. The --amount
CLI option is intended for ERC20 token transfers, while --gas-amount
is the required option for native gas. This leads to options.amount
being undefined or incorrect when no ERC20 transfer is involved, causing transaction failures or incorrect gas values.
examples/messaging/commands/message.ts#L98-L99
example-contracts/examples/messaging/commands/message.ts
Lines 98 to 99 in 0dd1307
// Handle both gasAmount and amount parameters | |
const gasAmount = ethers.utils.parseUnits(options.amount, 18); |
Comment bugbot run
to trigger another review on this PR
Was this report helpful? Give feedback by reacting with 👍 or 👎
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.
Actionable comments posted: 10
🧹 Nitpick comments (9)
examples/messaging/LICENSE (1)
3-4
: Double-check year and copyright holder for consistency.If the rest of the repository uses a different year (e.g., 2025) or lists multiple contributors, align this header accordingly to avoid ambiguity.
examples/messaging/tsconfig.json (1)
2-9
: Consider adding target and output directory configurations.The TypeScript configuration correctly enables modern Node.js ESM support with strict typing. However, consider adding these options for a more complete configuration:
{ "compilerOptions": { + "target": "ES2022", + "outDir": "./dist", "module": "nodenext", "moduleResolution": "nodenext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "resolveJsonModule": true } }examples/messaging/.eslintrc.js (1)
25-33
: Consider the impact of strict sorting rules on development workflow.The configuration enforces strict sorting for various code elements. While this ensures consistency, it may impact development velocity. Ensure the team is aligned on these requirements.
examples/messaging/commands/common.ts (1)
18-18
: Consider asynchronous file operations for better performance.Synchronous file reading can block the event loop in Node.js applications. Consider implementing an async version for production use.
- const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8")); + const artifactData = await fs.promises.readFile(artifactPath, "utf8"); + const artifact = JSON.parse(artifactData);examples/messaging/package.json (1)
7-7
: Implement proper test suite.The test script is currently a placeholder that exits with an error. For a production-grade messaging system handling cross-chain transactions, comprehensive testing is essential.
Do you want me to generate a test suite implementation or open an issue to track this requirement?
examples/messaging/commands/connect.ts (1)
28-35
: Fix misleading command description.The description mentions "ZRC20 token" but this command connects messaging contracts across chains, not specifically ZRC20 tokens.
- .description("Connect a ZRC20 token to its corresponding contract") + .description("Connect messaging contracts across chains")examples/messaging/commands/deploy.ts (1)
35-44
: Document or make network-aware default addresses.The hard-coded default addresses should be documented or made network-aware to prevent deployment failures on unsupported networks:
.requiredOption( "-g, --gateway <address>", - "Gateway address", + "Gateway address (default is for Sepolia testnet)", "0x0c487a766110c85d301d96e33579c5b317fa4995" ) .requiredOption( "-t, --router <address>", - "Router address", + "Router address (default is for Sepolia testnet)", "0xC0d3CB60f17eF2044380dC969f933a8615023c68" )examples/messaging/commands/message.ts (1)
97-112
: Consider chain-specific native token decimals.While 18 decimals is correct for Ethereum, other chains may use different decimals for their native tokens. Consider making this configurable:
} else { - const amount = ethers.utils.parseUnits(options.amount, 18); + const decimals = options.nativeDecimals || 18; + const amount = ethers.utils.parseUnits(options.amount, decimals);examples/messaging/contracts/Messaging.sol (1)
66-91
: Enhance ERC20 sendMessage error handling.The ERC20 implementation correctly handles transfer and approval failures, but could benefit from more descriptive error messages:
- if (!IERC20(asset).transferFrom(msg.sender, address(this), amount)) { - revert TransferFailed(); - } - if (!IERC20(asset).approve(address(gateway), amount)) { - revert ApprovalFailed(); - } + if (!IERC20(asset).transferFrom(msg.sender, address(this), amount)) { + revert TransferFailed(); + } + if (!IERC20(asset).approve(address(gateway), amount)) { + revert ApprovalFailed(); + }Consider adding input validation for the asset address:
+ if (asset == address(0)) revert InvalidAsset(); if (!IERC20(asset).transferFrom(msg.sender, address(this), amount)) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
examples/messaging/soldeer.lock
is excluded by!**/*.lock
examples/messaging/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (16)
examples/messaging/.eslintignore
(1 hunks)examples/messaging/.eslintrc.js
(1 hunks)examples/messaging/.gitignore
(1 hunks)examples/messaging/LICENSE
(1 hunks)examples/messaging/README.md
(1 hunks)examples/messaging/commands/common.ts
(1 hunks)examples/messaging/commands/connect.ts
(1 hunks)examples/messaging/commands/deploy.ts
(1 hunks)examples/messaging/commands/index.ts
(1 hunks)examples/messaging/commands/message.ts
(1 hunks)examples/messaging/contracts/Messaging.sol
(1 hunks)examples/messaging/foundry.toml
(1 hunks)examples/messaging/hardhat.config.ts
(1 hunks)examples/messaging/package.json
(1 hunks)examples/messaging/remappings.txt
(1 hunks)examples/messaging/tsconfig.json
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
examples/messaging/commands/connect.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
examples/messaging/commands/deploy.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
examples/messaging/commands/message.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
🪛 markdownlint-cli2 (0.17.2)
examples/messaging/README.md
3-3: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/nft)
- GitHub Check: slither (examples/hello, hello.sarif)
- GitHub Check: slither (examples/call, call.sarif)
- GitHub Check: slither (examples/swap, swap.sarif)
🔇 Additional comments (15)
examples/messaging/LICENSE (1)
1-22
: MIT license addition looks correct and complete.The text matches the canonical MIT template verbatim, so no legal wording concerns.
examples/messaging/.eslintignore (1)
1-6
: Standard ESLint ignore patterns are correctly configured.The ignore patterns appropriately exclude build artifacts, dependencies, and generated files from linting, which aligns with best practices for TypeScript and Solidity projects.
examples/messaging/remappings.txt (1)
1-1
: Foundry remapping correctly configured with version pinning.The remapping appropriately directs forge-std imports to the versioned dependency path, ensuring reproducible builds and consistent dependency resolution.
examples/messaging/commands/index.ts (1)
1-13
: Well-structured CLI implementation with proper command separation.The CLI entry point demonstrates excellent separation of concerns by organizing commands into separate modules. The use of commander.js and the executable shebang provides a professional command-line interface structure.
examples/messaging/hardhat.config.ts (2)
14-26
: Configuration structure is well-organized.The Solidity compiler configuration with optimization enabled and the modular approach using external configuration utilities demonstrates good practices for production deployment.
5-5
: Confirm external toolkit dependencyBoth required packages are declared in
examples/messaging/package.json
:
@zetachain/toolkit
@^16.0.0 (line 62)hardhat
@^2.17.2 (line 45)Please verify that the
getHardhatConfig
export from toolkit v16 is compatible with Hardhat v2.17.2. Check the toolkit’s documentation or source for any breaking-change notes and update the version or usage if necessary.examples/messaging/foundry.toml (2)
1-12
: Foundry configuration follows best practices.The configuration appropriately sets up compilation with IR optimization, proper directory structure, and secure file system permissions. The verbosity level and custom cache path enhance development experience.
15-15
: forge-std v1.9.7 is up-to-date and secure
The specified dependency (forge-std v1.9.7) is the latest stable release (Oct 24, 2024) and currently has no known security vulnerabilities. No further changes needed. [1]Citations:
examples/messaging/.gitignore (1)
1-23
: Comprehensive .gitignore coverage for blockchain development.The .gitignore file properly excludes all necessary development artifacts, build outputs, security-sensitive files, and tool-specific cache directories. This ensures a clean repository without exposing sensitive information or build artifacts.
examples/messaging/.eslintrc.js (1)
6-47
: Comprehensive ESLint configuration with strict code organization.The ESLint configuration implements robust code quality standards with extensive sorting rules for imports, exports, object keys, and TypeScript constructs. The setup properly integrates TypeScript parsing, Prettier formatting, and import resolution.
examples/messaging/commands/common.ts (1)
17-28
: Robust error handling and artifact loading logic.The function properly handles file reading errors and provides descriptive error messages. The path construction and artifact extraction logic is well-implemented for Hardhat project structure.
examples/messaging/commands/message.ts (1)
17-19
: LGTM on validation logic.Good validation to ensure function is provided when arbitrary call is requested.
examples/messaging/contracts/Messaging.sol (3)
1-16
: LGTM on contract structure and setup.Clean contract structure with proper licensing, pragma directive, and inheritance. Constructor properly delegates to the parent Messaging contract.
18-43
: LGTM on message handling overrides.The override functions provide clean, minimal implementations appropriate for an example contract. The
onRevert
function correctly validates the sender against the router address before emitting events.
45-64
: LGTM on native token sendMessage implementation.Clean implementation for sending messages with native tokens. The encoding and gateway call are properly structured.
const fs = require("fs"); | ||
const path = require("path"); |
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.
🛠️ Refactor suggestion
Move module imports to the top level.
Using require()
inside the function is inefficient and goes against modern ES module practices. Move these imports to the top of the file.
+import * as fs from "fs";
+import * as path from "path";
+
/**
* Load contract artifacts (ABI and bytecode)
*/
export const loadContractArtifacts = (
contractName: string,
sourceName?: string
) => {
- const fs = require("fs");
- const path = require("path");
🤖 Prompt for AI Agents
In examples/messaging/commands/common.ts around lines 8 to 9, the require
statements for 'fs' and 'path' are inside a function or block. Move these
require calls to the top of the file, outside any functions, to follow ES module
best practices and improve efficiency.
const main = async (options: any) => { | ||
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | ||
const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
||
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | ||
const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
||
const tx = await contract.setConnected( | ||
options.targetChainId, | ||
ethers.utils.arrayify(options.targetContract) | ||
); | ||
await tx.wait(); | ||
|
||
console.log( | ||
JSON.stringify({ | ||
contractAddress: options.contract, | ||
chainId: options.targetChainId, | ||
connected: options.targetContract, | ||
transactionHash: tx.hash, | ||
}) | ||
); | ||
}; |
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.
🛠️ Refactor suggestion
Add input validation and error handling.
The function lacks essential input validation and error handling for production use:
const main = async (options: any) => {
+ // Validate inputs
+ if (!ethers.utils.isAddress(options.contract)) {
+ throw new Error(`Invalid contract address: ${options.contract}`);
+ }
+ if (!ethers.utils.isAddress(options.targetContract)) {
+ throw new Error(`Invalid target contract address: ${options.targetContract}`);
+ }
+
+ try {
const provider = new ethers.providers.JsonRpcProvider(options.rpc);
const signer = new ethers.Wallet(options.privateKey, provider);
const { abi } = loadContractArtifacts("Example", "Messaging.sol");
const contract = new ethers.Contract(options.contract, abi, signer);
const tx = await contract.setConnected(
options.targetChainId,
ethers.utils.arrayify(options.targetContract)
);
await tx.wait();
+ } catch (error) {
+ console.error("Connection failed:", error);
+ process.exit(1);
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const main = async (options: any) => { | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contract = new ethers.Contract(options.contract, abi, signer); | |
const tx = await contract.setConnected( | |
options.targetChainId, | |
ethers.utils.arrayify(options.targetContract) | |
); | |
await tx.wait(); | |
console.log( | |
JSON.stringify({ | |
contractAddress: options.contract, | |
chainId: options.targetChainId, | |
connected: options.targetContract, | |
transactionHash: tx.hash, | |
}) | |
); | |
}; | |
const main = async (options: any) => { | |
// Validate inputs | |
if (!ethers.utils.isAddress(options.contract)) { | |
throw new Error(`Invalid contract address: ${options.contract}`); | |
} | |
if (!ethers.utils.isAddress(options.targetContract)) { | |
throw new Error(`Invalid target contract address: ${options.targetContract}`); | |
} | |
try { | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contract = new ethers.Contract(options.contract, abi, signer); | |
const tx = await contract.setConnected( | |
options.targetChainId, | |
ethers.utils.arrayify(options.targetContract) | |
); | |
await tx.wait(); | |
} catch (error) { | |
console.error("Connection failed:", error); | |
process.exit(1); | |
} | |
console.log( | |
JSON.stringify({ | |
contractAddress: options.contract, | |
chainId: options.targetChainId, | |
connected: options.targetContract, | |
transactionHash: tx.hash, | |
}) | |
); | |
}; |
🤖 Prompt for AI Agents
In examples/messaging/commands/connect.ts from lines 5 to 26, the main function
lacks input validation and error handling. Add checks to validate that
options.rpc, options.privateKey, options.contract, options.targetChainId, and
options.targetContract are present and correctly formatted before proceeding.
Wrap the asynchronous contract interaction code in a try-catch block to handle
and log any errors gracefully, preventing unhandled promise rejections and
improving robustness.
const main = async (options: any) => { | ||
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | ||
const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
||
const { abi, bytecode } = loadContractArtifacts("Example", "Messaging.sol"); | ||
|
||
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer); | ||
|
||
const contract = await contractFactory.deploy( | ||
options.gateway, | ||
signer.address, // owner | ||
options.router | ||
); | ||
|
||
await contract.deployed(); | ||
const contractAddress = contract.address; | ||
|
||
console.log( | ||
JSON.stringify({ | ||
contractAddress: contractAddress, | ||
deployer: signer.address, | ||
transactionHash: contract.deployTransaction?.hash, | ||
}) | ||
); | ||
}; |
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.
🛠️ Refactor suggestion
Enhance error handling and address validation.
The deployment function needs robust error handling and input validation for production use:
const main = async (options: any) => {
+ // Validate addresses
+ if (!ethers.utils.isAddress(options.gateway)) {
+ throw new Error(`Invalid gateway address: ${options.gateway}`);
+ }
+ if (!ethers.utils.isAddress(options.router)) {
+ throw new Error(`Invalid router address: ${options.router}`);
+ }
+
+ try {
const provider = new ethers.providers.JsonRpcProvider(options.rpc);
const signer = new ethers.Wallet(options.privateKey, provider);
const { abi, bytecode } = loadContractArtifacts("Example", "Messaging.sol");
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);
const contract = await contractFactory.deploy(
options.gateway,
signer.address,
options.router
);
await contract.deployed();
const contractAddress = contract.address;
console.log(
JSON.stringify({
contractAddress: contractAddress,
deployer: signer.address,
- transactionHash: contract.deployTransaction?.hash,
+ transactionHash: contract.deployTransaction.hash,
})
);
+ } catch (error) {
+ console.error("Deployment failed:", error);
+ process.exit(1);
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const main = async (options: any) => { | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi, bytecode } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer); | |
const contract = await contractFactory.deploy( | |
options.gateway, | |
signer.address, // owner | |
options.router | |
); | |
await contract.deployed(); | |
const contractAddress = contract.address; | |
console.log( | |
JSON.stringify({ | |
contractAddress: contractAddress, | |
deployer: signer.address, | |
transactionHash: contract.deployTransaction?.hash, | |
}) | |
); | |
}; | |
const main = async (options: any) => { | |
// Validate addresses | |
if (!ethers.utils.isAddress(options.gateway)) { | |
throw new Error(`Invalid gateway address: ${options.gateway}`); | |
} | |
if (!ethers.utils.isAddress(options.router)) { | |
throw new Error(`Invalid router address: ${options.router}`); | |
} | |
try { | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi, bytecode } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer); | |
const contract = await contractFactory.deploy( | |
options.gateway, | |
signer.address, // owner | |
options.router | |
); | |
await contract.deployed(); | |
const contractAddress = contract.address; | |
console.log( | |
JSON.stringify({ | |
contractAddress: contractAddress, | |
deployer: signer.address, | |
transactionHash: contract.deployTransaction.hash, | |
}) | |
); | |
} catch (error) { | |
console.error("Deployment failed:", error); | |
process.exit(1); | |
} | |
}; |
🤖 Prompt for AI Agents
In examples/messaging/commands/deploy.ts around lines 5 to 29, the deployment
function lacks error handling and input validation. Add try-catch blocks around
the deployment logic to catch and log errors gracefully. Validate critical
inputs such as options.rpc, options.privateKey, options.gateway, and
options.router before using them, ensuring they are present and correctly
formatted (e.g., validate Ethereum addresses). This will make the deployment
process more robust and safer for production use.
if (options.values && options.types) { | ||
const valuesArray = options.values.map((value: any, index: any) => { | ||
const type = options.types[index]; | ||
|
||
if (type === "bool") { | ||
try { | ||
return JSON.parse(value.toLowerCase()); | ||
} catch (e) { | ||
throw new Error(`Invalid boolean value: ${value}`); | ||
} | ||
} else if (type.startsWith("uint") || type.startsWith("int")) { | ||
return ethers.BigNumber.from(value); | ||
} else { | ||
return value; | ||
} | ||
}); | ||
|
||
const encodedParameters = ethers.utils.defaultAbiCoder.encode( | ||
options.types, | ||
valuesArray | ||
); | ||
|
||
if (options.isArbitraryCall && options.function) { | ||
const functionSignature = ethers.utils.id(options.function).slice(0, 10); | ||
message = ethers.utils.hexlify( | ||
ethers.utils.concat([functionSignature, encodedParameters]) | ||
); | ||
} else { | ||
message = encodedParameters; | ||
} | ||
} else { | ||
// If no values/types provided, use the message directly | ||
message = options.message || "0x"; | ||
} |
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.
🛠️ Refactor suggestion
Extract parameter parsing to utility function.
The parameter parsing logic is complex and should be extracted for reusability and better testing:
Create a new function in common.ts
:
export const parseAndEncodeParameters = (types: string[], values: string[]) => {
if (types.length !== values.length) {
throw new Error("Types and values arrays must have the same length");
}
const parsedValues = values.map((value, index) => {
const type = types[index];
if (type === "bool") {
const normalizedValue = value.toLowerCase();
if (!["true", "false"].includes(normalizedValue)) {
throw new Error(`Invalid boolean value: ${value}`);
}
return normalizedValue === "true";
} else if (type.startsWith("uint") || type.startsWith("int")) {
try {
return ethers.BigNumber.from(value);
} catch (error) {
throw new Error(`Invalid numeric value for type ${type}: ${value}`);
}
}
return value;
});
return ethers.utils.defaultAbiCoder.encode(types, parsedValues);
};
🤖 Prompt for AI Agents
In examples/messaging/commands/message.ts around lines 23 to 56, the parameter
parsing and encoding logic is complex and embedded directly in the code. Extract
this logic into a new utility function named parseAndEncodeParameters in
common.ts that takes types and values arrays, validates their lengths, parses
booleans and numeric types properly with error handling, and returns the encoded
parameters using ethers.utils.defaultAbiCoder.encode. Replace the existing
inline parsing and encoding code with a call to this new utility function to
improve reusability and testability.
if (options.erc20) { | ||
const erc20 = new ethers.Contract( | ||
options.erc20, | ||
[ | ||
"function approve(address spender, uint256 amount) returns (bool)", | ||
"function transferFrom(address from, address to, uint256 amount) returns (bool)", | ||
], | ||
signer | ||
); | ||
|
||
const amount = ethers.utils.parseUnits(options.amount, 18); | ||
const approveTx = await erc20.approve(options.contract, amount); | ||
await approveTx.wait(); | ||
|
||
// Use the ERC20 version of sendMessage | ||
tx = await contract.functions[ | ||
"sendMessage(bytes,address,uint256,address,bytes,uint256,(address,bool,address,bytes,uint256))" | ||
]( | ||
options.targetContract, | ||
options.targetToken, | ||
amount, | ||
options.erc20, | ||
message, | ||
options.callGasLimit, | ||
revertOptions, | ||
txOptions | ||
); |
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.
Fix hard-coded token decimals assumption.
The code assumes all ERC20 tokens use 18 decimals, which is incorrect for many tokens (USDC uses 6, WBTC uses 8, etc.):
if (options.erc20) {
const erc20 = new ethers.Contract(
options.erc20,
[
+ "function decimals() view returns (uint8)",
"function approve(address spender, uint256 amount) returns (bool)",
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
],
signer
);
- const amount = ethers.utils.parseUnits(options.amount, 18);
+ const decimals = await erc20.decimals();
+ const amount = ethers.utils.parseUnits(options.amount, decimals);
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In examples/messaging/commands/message.ts between lines 70 and 96, the code
incorrectly assumes all ERC20 tokens have 18 decimals when parsing the amount.
To fix this, query the token contract for its decimals using the decimals()
function before parsing the amount, then use the retrieved decimals value in
ethers.utils.parseUnits instead of hardcoding 18.
dotenv.config(); | ||
|
||
const config: HardhatUserConfig = { | ||
...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY || ""] }), |
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.
Address the empty string fallback for PRIVATE_KEY.
Using an empty string as a fallback for PRIVATE_KEY
may cause runtime errors during deployment operations. Consider implementing proper validation or using a more explicit default value.
- ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY || ""] }),
+ ...getHardhatConfig({
+ accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
+ }),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY || ""] }), | |
...getHardhatConfig({ | |
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] | |
}), |
🤖 Prompt for AI Agents
In examples/messaging/hardhat.config.ts at line 13, avoid using an empty string
as a fallback for process.env.PRIVATE_KEY because it can cause runtime errors.
Instead, add validation to check if PRIVATE_KEY is defined and throw an error or
provide a clear default value if it is missing. This ensures the deployment
process does not proceed with an invalid or empty private key.
"name": "nft", | ||
"version": "1.0.0", | ||
"description": "Universal NFT enables non-fungible ERC-721 tokens to minted and transferred between connected chains.", |
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.
Fix inconsistent package metadata.
The package name "nft" and description reference Universal NFT functionality, but this is a messaging example project. This inconsistency could confuse developers about the project's actual purpose.
- "name": "nft",
- "version": "1.0.0",
- "description": "Universal NFT enables non-fungible ERC-721 tokens to minted and transferred between connected chains.",
+ "name": "messaging-example",
+ "version": "1.0.0",
+ "description": "Cross-chain messaging example enabling smart contracts to communicate across different blockchain networks.",
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"name": "nft", | |
"version": "1.0.0", | |
"description": "Universal NFT enables non-fungible ERC-721 tokens to minted and transferred between connected chains.", | |
"name": "messaging-example", | |
"version": "1.0.0", | |
"description": "Cross-chain messaging example enabling smart contracts to communicate across different blockchain networks.", |
🤖 Prompt for AI Agents
In examples/messaging/package.json around lines 2 to 4, the package name and
description incorrectly reference NFT functionality, which does not match the
messaging example project. Update the "name" field to reflect the messaging
example, such as "messaging", and revise the "description" to accurately
describe the messaging project's purpose to avoid confusion.
examples/messaging/package.json
Outdated
"@coral-xyz/anchor": "0.30.0", | ||
"@openzeppelin/hardhat-upgrades": "1.28.0", | ||
"@solana-developers/helpers": "^2.4.0", | ||
"@solana/spl-memo": "^0.2.5", | ||
"@solana/web3.js": "^1.95.8", | ||
"@zetachain/protocol-contracts": "13.0.0", | ||
"@zetachain/standard-contracts": "3.0.0-rc2", | ||
"@zetachain/toolkit": "^16.0.0", | ||
"commander": "^13.1.0", | ||
"validator": "^13.12.0", | ||
"zetachain": "^3.0.0" |
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.
💡 Verification agent
🧩 Analysis chain
Review dependency selection and versions.
Several dependency concerns:
@zetachain/standard-contracts
uses RC version (3.0.0-rc2) which may not be production-ready- Solana-related packages appear unrelated to this Ethereum-based messaging example
- Using ethers v5 instead of the more recent v6 with improved TypeScript support
🏁 Script executed:
#!/bin/bash
# Check for latest stable versions of key dependencies
echo "Checking ZetaChain standard contracts versions:"
npm view @zetachain/standard-contracts versions --json | jq '.[-10:]'
echo "Checking ethers versions:"
npm view ethers versions --json | jq '.[-5:]'
Length of output: 493
🏁 Script executed:
#!/bin/bash
echo "Solana usage in examples/messaging:"
grep -R "import.*solana" -n examples/messaging || grep -R "require.*solana" -n examples/messaging || echo "No Solana references found"
echo ""
echo "Ethers usage in examples/messaging:"
grep -R "import.*ethers" -n examples/messaging || grep -R "require.*ethers" -n examples/messaging || echo "No ethers references found"
Length of output: 679
Ensure production-ready dependencies in examples/messaging/package.json
The messaging example currently includes an RC release and unused packages. Please update as follows:
• Replace @zetachain/standard-contracts
at 3.0.0-rc2 with the latest stable (e.g., 2.0.1) or wait for a non-RC 3.x release.
• Remove Solana-related dependencies (@solana-developers/helpers
, @solana/spl-memo
, @solana/web3.js
)—no Solana imports are used in examples/messaging
.
• The code relies on ethers v5 via Hardhat’s plugin but doesn’t list ethers
explicitly. If you wish to adopt ethers v6 (latest 6.15.0), add it to dependencies
and adjust any v6 breaking changes; otherwise, pin v5 explicitly for clarity.
Example diff:
--- a/examples/messaging/package.json
+++ b/examples/messaging/package.json
@@ dependencies {
- "@solana-developers/helpers": "^2.4.0",
- "@solana/spl-memo": "^0.2.5",
- "@solana/web3.js": "^1.95.8",
- "@zetachain/standard-contracts": "3.0.0-rc2",
+ "@zetachain/standard-contracts": "2.0.1",
+ "ethers": "^6.15.0", // or "^5.7.2" if staying on v5
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"@coral-xyz/anchor": "0.30.0", | |
"@openzeppelin/hardhat-upgrades": "1.28.0", | |
"@solana-developers/helpers": "^2.4.0", | |
"@solana/spl-memo": "^0.2.5", | |
"@solana/web3.js": "^1.95.8", | |
"@zetachain/protocol-contracts": "13.0.0", | |
"@zetachain/standard-contracts": "3.0.0-rc2", | |
"@zetachain/toolkit": "^16.0.0", | |
"commander": "^13.1.0", | |
"validator": "^13.12.0", | |
"zetachain": "^3.0.0" | |
"@coral-xyz/anchor": "0.30.0", | |
"@openzeppelin/hardhat-upgrades": "1.28.0", | |
"@zetachain/protocol-contracts": "13.0.0", | |
"@zetachain/standard-contracts": "2.0.1", | |
"ethers": "^6.15.0", | |
"@zetachain/toolkit": "^16.0.0", | |
"commander": "^13.1.0", | |
"validator": "^13.12.0", | |
"zetachain": "^3.0.0" |
🤖 Prompt for AI Agents
In examples/messaging/package.json lines 55 to 65, replace the
@zetachain/standard-contracts version 3.0.0-rc2 with the latest stable version
such as 2.0.1, remove the Solana-related dependencies
@solana-developers/helpers, @solana/spl-memo, and @solana/web3.js since they are
unused, and explicitly add ethers to dependencies either pinned to v5 for
compatibility or upgraded to v6 with necessary code adjustments.
@@ -0,0 +1,3 @@ | |||
# Universal NFT |
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.
Title misalignment with project functionality.
The title "Universal NFT" does not align with the cross-chain messaging functionality described in the PR objectives. This could mislead users about the project's purpose.
Consider updating to reflect the actual functionality:
-# Universal NFT
+# Cross-Chain Messaging
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Universal NFT | |
# Cross-Chain Messaging |
🤖 Prompt for AI Agents
In examples/messaging/README.md at line 1, the title "Universal NFT" does not
reflect the cross-chain messaging functionality of the project. Update the title
to clearly describe the messaging or cross-chain communication features to align
with the project's purpose and avoid user confusion.
@@ -0,0 +1,3 @@ | |||
# Universal NFT | |||
|
|||
Learn more: https://www.zetachain.com/docs/developers/standards/nft |
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.
Format URL as proper markdown link and align content with project scope.
The bare URL should be formatted as a proper markdown link, and the content should reflect the cross-chain messaging functionality rather than NFT standards.
Apply this correction:
-Learn more: https://www.zetachain.com/docs/developers/standards/nft
+Learn more: [ZetaChain Cross-Chain Messaging](https://www.zetachain.com/docs/developers/cross-chain/messaging)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
Learn more: https://www.zetachain.com/docs/developers/standards/nft | |
Learn more: [ZetaChain Cross-Chain Messaging](https://www.zetachain.com/docs/developers/cross-chain/messaging) |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
3-3: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents
In examples/messaging/README.md at line 3, replace the bare URL with a proper
markdown link and update the link text to reflect cross-chain messaging
functionality instead of NFT standards. Format the line as a markdown link with
descriptive text relevant to the messaging example.
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.
Actionable comments posted: 0
♻️ Duplicate comments (2)
examples/messaging/commands/message.ts (2)
20-46
: Extract parameter parsing to utility function.This complex parameter parsing logic should be extracted into a reusable utility function as previously suggested. The current implementation is embedded directly in the main function and lacks proper validation.
60-79
: Fix hard-coded token decimals assumption.The code incorrectly assumes all ERC20 tokens use 18 decimals when parsing the amount on line 63. This will cause incorrect amounts for tokens with different decimal places (USDC uses 6, WBTC uses 8, etc.).
🧹 Nitpick comments (2)
examples/messaging/commands/message.ts (2)
80-95
: Consider making native token decimals configurable.Similar to the ERC20 path, the native token amount parsing hard-codes 18 decimals. While this is correct for most native tokens (ETH, BNB), consider making this configurable for consistency and flexibility.
112-140
: Command definition is comprehensive and well-structured.The CLI interface covers all necessary parameters for cross-chain messaging with appropriate defaults and clear option names.
Consider adding validation to ensure the
amount
option is required whenerc20
is specified, as the current logic expects both to be present together.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/messaging/commands/message.ts
(1 hunks)
🧬 Code Graph Analysis (1)
examples/messaging/commands/message.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
🧰 Additional context used
🧬 Code Graph Analysis (1)
examples/messaging/commands/message.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/hello)
- GitHub Check: slither (examples/call, call.sarif)
- GitHub Check: slither (examples/hello, hello.sarif)
- GitHub Check: slither (examples/swap, swap.sarif)
🔇 Additional comments (3)
examples/messaging/commands/message.ts (3)
1-11
: Setup and imports are well-structured.The imports, provider initialization, and contract artifact loading follow established patterns and best practices.
48-56
: Revert options configuration is properly implemented.The revert options are correctly structured and the UTF-8 message encoding using ethers utilities is appropriate.
97-110
: Transaction execution and output formatting are well-implemented.The transaction confirmation handling and structured JSON output provide appropriate feedback for cross-chain messaging operations.
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.
Actionable comments posted: 2
♻️ Duplicate comments (1)
examples/messaging/commands/message.ts (1)
42-61
: Address hard-coded decimal assumption for ERC20 tokens.The code incorrectly assumes all ERC20 tokens use 18 decimals, which will cause incorrect amount calculations for tokens with different decimal places (USDC uses 6, WBTC uses 8, etc.).
Apply this fix to query the token's actual decimals:
if (options.erc20) { - const erc20 = new ethers.Contract(options.erc20, ERC20_ABI.abi, signer); + const erc20 = new ethers.Contract( + options.erc20, + [...ERC20_ABI.abi, "function decimals() view returns (uint8)"], + signer + ); - const amount = ethers.utils.parseUnits(options.amount, 18); + const decimals = await erc20.decimals(); + const amount = ethers.utils.parseUnits(options.amount, decimals);
🧹 Nitpick comments (1)
examples/messaging/commands/message.ts (1)
62-77
: Consider parameterizing native token decimals for cross-chain compatibility.While ETH uses 18 decimals, this implementation may be used across different chains with varying native token decimal configurations. For consistency and cross-chain compatibility, consider making decimals configurable.
- const amount = ethers.utils.parseUnits(options.amount, 18); + const decimals = options.nativeDecimals || 18; + const amount = ethers.utils.parseUnits(options.amount, decimals);Add a corresponding CLI option:
+ .option("-d, --native-decimals <decimals>", "Native token decimals", "18")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/messaging/commands/message.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
examples/messaging/commands/message.ts (1)
examples/messaging/commands/common.ts (1)
loadContractArtifacts
(4-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/call)
- GitHub Check: slither (examples/swap, swap.sarif)
- GitHub Check: slither (examples/call, call.sarif)
- GitHub Check: slither (examples/hello, hello.sarif)
🔇 Additional comments (2)
examples/messaging/commands/message.ts (2)
1-18
: Well-structured imports and setup.The imports are appropriate, and the provider/signer initialization follows ethers.js best practices. The use of the utility function for loading contract artifacts promotes code reusability.
19-28
: Efficient message encoding implementation.The conditional message encoding logic is well-implemented. Using
parseAbiValues
from the ZetaChain toolkit appropriately addresses the parameter parsing complexity mentioned in previous reviews, providing a cleaner and more reliable solution.
const main = async (options: any) => { | ||
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | ||
const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
||
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | ||
const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
||
const txOptions = { | ||
gasPrice: options.gasPrice, | ||
gasLimit: options.gasLimit, | ||
}; | ||
|
||
let message; | ||
|
||
if (options.values && options.types) { | ||
message = ethers.utils.defaultAbiCoder.encode( | ||
options.types, | ||
parseAbiValues(JSON.stringify(options.types), options.values) | ||
); | ||
} else { | ||
message = options.message; | ||
} | ||
|
||
const revertOptions = { | ||
abortAddress: options.abortAddress, | ||
callOnRevert: options.callOnRevert, | ||
onRevertGasLimit: options.onRevertGasLimit, | ||
revertAddress: options.revertAddress, | ||
revertMessage: ethers.utils.hexlify( | ||
ethers.utils.toUtf8Bytes(options.revertMessage) | ||
), | ||
}; | ||
|
||
let tx; | ||
|
||
if (options.erc20) { | ||
const erc20 = new ethers.Contract(options.erc20, ERC20_ABI.abi, signer); | ||
|
||
const amount = ethers.utils.parseUnits(options.amount, 18); | ||
const approveTx = await erc20.approve(options.contract, amount); | ||
await approveTx.wait(); | ||
|
||
// Use the ERC20 version of sendMessage | ||
tx = await contract.functions[ | ||
"sendMessage(bytes,address,uint256,address,bytes,uint256,(address,bool,address,bytes,uint256))" | ||
]( | ||
options.targetContract, | ||
options.targetToken, | ||
amount, | ||
options.erc20, | ||
message, | ||
options.callGasLimit, | ||
revertOptions, | ||
txOptions | ||
); | ||
} else { | ||
const amount = ethers.utils.parseUnits(options.amount, 18); | ||
tx = await contract.functions[ | ||
"sendMessage(bytes,address,bytes,uint256,(address,bool,address,bytes,uint256))" | ||
]( | ||
options.targetContract, | ||
options.targetToken, | ||
message, | ||
options.callGasLimit, | ||
revertOptions, | ||
{ | ||
...txOptions, | ||
value: amount, | ||
} | ||
); | ||
} | ||
|
||
await tx.wait(); | ||
|
||
console.log( | ||
JSON.stringify({ | ||
contractAddress: options.contract, | ||
targetContract: options.targetContract, | ||
targetToken: options.targetToken, | ||
message: message, | ||
transactionHash: tx.hash, | ||
amount: options.amount, | ||
erc20: options.erc20, | ||
}) | ||
); | ||
}; |
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.
🛠️ Refactor suggestion
Implement comprehensive error handling and input validation.
The function lacks robust error handling for network failures, invalid inputs, and transaction failures, which could result in unclear error messages for users.
Wrap the main logic in try-catch and add input validation:
const main = async (options: any) => {
+ try {
+ // Validate addresses
+ if (!ethers.utils.isAddress(options.contract)) {
+ throw new Error(`Invalid contract address: ${options.contract}`);
+ }
+ if (!ethers.utils.isAddress(options.targetContract)) {
+ throw new Error(`Invalid target contract address: ${options.targetContract}`);
+ }
+ if (options.erc20 && !ethers.utils.isAddress(options.erc20)) {
+ throw new Error(`Invalid ERC20 address: ${options.erc20}`);
+ }
const provider = new ethers.providers.JsonRpcProvider(options.rpc);
const signer = new ethers.Wallet(options.privateKey, provider);
// ... existing code ...
- await tx.wait();
+ const receipt = await tx.wait();
+ if (receipt.status === 0) {
+ throw new Error("Transaction failed");
+ }
console.log(
JSON.stringify({
contractAddress: options.contract,
targetContract: options.targetContract,
targetToken: options.targetToken,
message: message,
transactionHash: tx.hash,
amount: options.amount,
erc20: options.erc20,
+ blockNumber: receipt.blockNumber,
+ gasUsed: receipt.gasUsed.toString(),
})
);
+ } catch (error) {
+ console.error("Error sending message:", error.message);
+ process.exit(1);
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const main = async (options: any) => { | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contract = new ethers.Contract(options.contract, abi, signer); | |
const txOptions = { | |
gasPrice: options.gasPrice, | |
gasLimit: options.gasLimit, | |
}; | |
let message; | |
if (options.values && options.types) { | |
message = ethers.utils.defaultAbiCoder.encode( | |
options.types, | |
parseAbiValues(JSON.stringify(options.types), options.values) | |
); | |
} else { | |
message = options.message; | |
} | |
const revertOptions = { | |
abortAddress: options.abortAddress, | |
callOnRevert: options.callOnRevert, | |
onRevertGasLimit: options.onRevertGasLimit, | |
revertAddress: options.revertAddress, | |
revertMessage: ethers.utils.hexlify( | |
ethers.utils.toUtf8Bytes(options.revertMessage) | |
), | |
}; | |
let tx; | |
if (options.erc20) { | |
const erc20 = new ethers.Contract(options.erc20, ERC20_ABI.abi, signer); | |
const amount = ethers.utils.parseUnits(options.amount, 18); | |
const approveTx = await erc20.approve(options.contract, amount); | |
await approveTx.wait(); | |
// Use the ERC20 version of sendMessage | |
tx = await contract.functions[ | |
"sendMessage(bytes,address,uint256,address,bytes,uint256,(address,bool,address,bytes,uint256))" | |
]( | |
options.targetContract, | |
options.targetToken, | |
amount, | |
options.erc20, | |
message, | |
options.callGasLimit, | |
revertOptions, | |
txOptions | |
); | |
} else { | |
const amount = ethers.utils.parseUnits(options.amount, 18); | |
tx = await contract.functions[ | |
"sendMessage(bytes,address,bytes,uint256,(address,bool,address,bytes,uint256))" | |
]( | |
options.targetContract, | |
options.targetToken, | |
message, | |
options.callGasLimit, | |
revertOptions, | |
{ | |
...txOptions, | |
value: amount, | |
} | |
); | |
} | |
await tx.wait(); | |
console.log( | |
JSON.stringify({ | |
contractAddress: options.contract, | |
targetContract: options.targetContract, | |
targetToken: options.targetToken, | |
message: message, | |
transactionHash: tx.hash, | |
amount: options.amount, | |
erc20: options.erc20, | |
}) | |
); | |
}; | |
const main = async (options: any) => { | |
try { | |
// Validate addresses | |
if (!ethers.utils.isAddress(options.contract)) { | |
throw new Error(`Invalid contract address: ${options.contract}`); | |
} | |
if (!ethers.utils.isAddress(options.targetContract)) { | |
throw new Error(`Invalid target contract address: ${options.targetContract}`); | |
} | |
if (options.erc20 && !ethers.utils.isAddress(options.erc20)) { | |
throw new Error(`Invalid ERC20 address: ${options.erc20}`); | |
} | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
const signer = new ethers.Wallet(options.privateKey, provider); | |
const { abi } = loadContractArtifacts("Example", "Messaging.sol"); | |
const contract = new ethers.Contract(options.contract, abi, signer); | |
const txOptions = { | |
gasPrice: options.gasPrice, | |
gasLimit: options.gasLimit, | |
}; | |
let message; | |
if (options.values && options.types) { | |
message = ethers.utils.defaultAbiCoder.encode( | |
options.types, | |
parseAbiValues(JSON.stringify(options.types), options.values) | |
); | |
} else { | |
message = options.message; | |
} | |
const revertOptions = { | |
abortAddress: options.abortAddress, | |
callOnRevert: options.callOnRevert, | |
onRevertGasLimit: options.onRevertGasLimit, | |
revertAddress: options.revertAddress, | |
revertMessage: ethers.utils.hexlify( | |
ethers.utils.toUtf8Bytes(options.revertMessage) | |
), | |
}; | |
let tx; | |
if (options.erc20) { | |
const erc20 = new ethers.Contract(options.erc20, ERC20_ABI.abi, signer); | |
const amount = ethers.utils.parseUnits(options.amount, 18); | |
const approveTx = await erc20.approve(options.contract, amount); | |
await approveTx.wait(); | |
// Use the ERC20 version of sendMessage | |
tx = await contract.functions[ | |
"sendMessage(bytes,address,uint256,address,bytes,uint256,(address,bool,address,bytes,uint256))" | |
]( | |
options.targetContract, | |
options.targetToken, | |
amount, | |
options.erc20, | |
message, | |
options.callGasLimit, | |
revertOptions, | |
txOptions | |
); | |
} else { | |
const amount = ethers.utils.parseUnits(options.amount, 18); | |
tx = await contract.functions[ | |
"sendMessage(bytes,address,bytes,uint256,(address,bool,address,bytes,uint256))" | |
]( | |
options.targetContract, | |
options.targetToken, | |
message, | |
options.callGasLimit, | |
revertOptions, | |
{ | |
...txOptions, | |
value: amount, | |
} | |
); | |
} | |
const receipt = await tx.wait(); | |
if (receipt.status === 0) { | |
throw new Error("Transaction failed"); | |
} | |
console.log( | |
JSON.stringify({ | |
contractAddress: options.contract, | |
targetContract: options.targetContract, | |
targetToken: options.targetToken, | |
message: message, | |
transactionHash: tx.hash, | |
amount: options.amount, | |
erc20: options.erc20, | |
blockNumber: receipt.blockNumber, | |
gasUsed: receipt.gasUsed.toString(), | |
}) | |
); | |
} catch (error: any) { | |
console.error("Error sending message:", error.message); | |
process.exit(1); | |
} | |
}; |
🤖 Prompt for AI Agents
In examples/messaging/commands/message.ts around lines 7 to 92, the main
function lacks error handling and input validation, which can cause unclear
errors on network or transaction failures. Wrap the entire main function logic
in a try-catch block to catch and log errors clearly. Add input validation
checks at the start of the function to verify required options like rpc,
privateKey, contract, amount, and message or values/types are present and valid,
throwing descriptive errors if not. This will improve robustness and user
feedback.
export const message = new Command("message") | ||
.description("Send a cross-chain message") | ||
.requiredOption("-r, --rpc <url>", "RPC URL") | ||
.requiredOption("-k, --private-key <key>", "Private key") | ||
.requiredOption("-c, --contract <address>", "Contract address") | ||
.requiredOption("-t, --target-contract <address>", "Target contract address") | ||
.requiredOption("-g, --target-token <address>", "Target token address") | ||
.option("-m, --message <message>", "Message to send (hex format)") | ||
.option("-e, --erc20 <address>", "ERC20 token address for token transfer") | ||
.option("-n, --amount <amount>", "Amount of ERC20 tokens to transfer") | ||
.option("-l, --call-gas-limit <limit>", "Gas limit for the call", "1000000") | ||
.option("-p, --gas-price <price>", "Gas price", "10000000000") | ||
.option("-i, --gas-limit <limit>", "Gas limit", "10000000") | ||
.option("-y, --types <types...>", "Parameter types") | ||
.option("-v, --values <values...>", "Parameter values") | ||
.option( | ||
"-b, --abort-address <address>", | ||
"Abort address", | ||
"0x0000000000000000000000000000000000000000" | ||
) | ||
.option( | ||
"-o, --revert-address <address>", | ||
"Revert address", | ||
"0x0000000000000000000000000000000000000000" | ||
) | ||
.option("-s, --revert-message <message>", "Revert message", "") | ||
.option("-u, --on-revert-gas-limit <limit>", "On revert gas limit", "1000000") | ||
.option("--call-on-revert", "Whether to call on revert", false) | ||
.action(main); |
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.
🛠️ Refactor suggestion
Add validation for interdependent CLI options.
The command configuration is comprehensive, but lacks validation for interdependent options that could lead to runtime errors or unexpected behavior.
Add validation logic in the main function:
const main = async (options: any) => {
+ // Validate interdependent options
+ if (options.erc20 && !options.amount) {
+ throw new Error("--amount is required when --erc20 is specified");
+ }
+
+ if ((options.types && !options.values) || (!options.types && options.values)) {
+ throw new Error("--types and --values must be provided together");
+ }
+
+ if (options.types && options.values && options.types.length !== options.values.length) {
+ throw new Error("--types and --values arrays must have the same length");
+ }
const provider = new ethers.providers.JsonRpcProvider(options.rpc);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const message = new Command("message") | |
.description("Send a cross-chain message") | |
.requiredOption("-r, --rpc <url>", "RPC URL") | |
.requiredOption("-k, --private-key <key>", "Private key") | |
.requiredOption("-c, --contract <address>", "Contract address") | |
.requiredOption("-t, --target-contract <address>", "Target contract address") | |
.requiredOption("-g, --target-token <address>", "Target token address") | |
.option("-m, --message <message>", "Message to send (hex format)") | |
.option("-e, --erc20 <address>", "ERC20 token address for token transfer") | |
.option("-n, --amount <amount>", "Amount of ERC20 tokens to transfer") | |
.option("-l, --call-gas-limit <limit>", "Gas limit for the call", "1000000") | |
.option("-p, --gas-price <price>", "Gas price", "10000000000") | |
.option("-i, --gas-limit <limit>", "Gas limit", "10000000") | |
.option("-y, --types <types...>", "Parameter types") | |
.option("-v, --values <values...>", "Parameter values") | |
.option( | |
"-b, --abort-address <address>", | |
"Abort address", | |
"0x0000000000000000000000000000000000000000" | |
) | |
.option( | |
"-o, --revert-address <address>", | |
"Revert address", | |
"0x0000000000000000000000000000000000000000" | |
) | |
.option("-s, --revert-message <message>", "Revert message", "") | |
.option("-u, --on-revert-gas-limit <limit>", "On revert gas limit", "1000000") | |
.option("--call-on-revert", "Whether to call on revert", false) | |
.action(main); | |
const main = async (options: any) => { | |
// Validate interdependent options | |
if (options.erc20 && !options.amount) { | |
throw new Error("--amount is required when --erc20 is specified"); | |
} | |
if ((options.types && !options.values) || (!options.types && options.values)) { | |
throw new Error("--types and --values must be provided together"); | |
} | |
if (options.types && options.values && options.types.length !== options.values.length) { | |
throw new Error("--types and --values arrays must have the same length"); | |
} | |
const provider = new ethers.providers.JsonRpcProvider(options.rpc); | |
// …rest of implementation… | |
}; |
🤖 Prompt for AI Agents
In examples/messaging/commands/message.ts around lines 94 to 122, the CLI
options lack validation for interdependent parameters, which may cause runtime
errors. Add validation logic in the main function to check for required
combinations, such as ensuring if --erc20 is provided then --amount must also be
provided, and if --types is specified then --values must be provided with
matching lengths. Implement appropriate error messages and exit the process if
validations fail to prevent unexpected behavior.
Deploy
Connect
Tell contracts on different chains about each other so they know which senders to trust.
Send a Message
Uses: zeta-chain/standard-contracts#38
Summary by CodeRabbit
Summary by CodeRabbit