Skip to content

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

Merged
merged 14 commits into from
Jul 31, 2025
Merged

feat: cross-chain messaging #275

merged 14 commits into from
Jul 31, 2025

Conversation

fadeev
Copy link
Member

@fadeev fadeev commented Jul 21, 2025

Deploy

CONTRACT_ETHEREUM=$(./commands/index.ts deploy --rpc https://sepolia.drpc.org --private-key $PRIVATE_KEY | jq -r .contractAddress)
CONTRACT_BASE=$(./commands/index.ts deploy --rpc https://sepolia.base.org --private-key $PRIVATE_KEY | jq -r .contractAddress)

Connect

Tell contracts on different chains about each other so they know which senders to trust.

./commands/index.ts connect \
  --rpc https://sepolia.drpc.org \
  --private-key $PRIVATE_KEY \
  --contract "$CONTRACT_ETHEREUM" \
  --target-contract "$CONTRACT_BASE" \
  --target-chain-id 84532
./commands/index.ts connect \
  --rpc https://sepolia.base.org \
  --private-key $PRIVATE_KEY \
  --contract "$CONTRACT_BASE" \
  --target-contract "$CONTRACT_ETHEREUM" \
  --target-chain-id 11155111

Send a Message

./commands/index.ts message \
  --rpc https://sepolia.base.org \
  --private-key $PRIVATE_KEY \
  --contract "$CONTRACT_BASE" \
  --target-contract "$CONTRACT_ETHEREUM" \
  --target-token 0x05BA149A7bd6dC1F937fA9046A9e05C05f3b18b0 \
  --amount 0.0001 \
  --types string \
  --values hello | jq -r .transactionHash

Uses: zeta-chain/standard-contracts#38

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced a messaging example project demonstrating cross-chain message sending and smart contract deployment.
    • Added command-line tools for deploying contracts, connecting contracts across chains, and sending cross-chain messages.
    • Provided a sample Solidity contract with cross-chain messaging and revert handling capabilities.
    • Included project documentation, configuration files, and licensing information.
  • Chores
    • Added configuration files for ESLint, Hardhat, Foundry, TypeScript, and Git.
    • Updated package management files and dependency remappings.

Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

📝 Walkthrough

Walkthrough

This 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

File(s) Change Summary
Ignore, Lint, License, and Documentation
examples/messaging/.eslintignore, .eslintrc.js, .gitignore, LICENSE, README.md
Added standard ignore files for ESLint and Git, ESLint configuration with TypeScript and Prettier rules, MIT license file, and a minimal README referencing NFT standards.
Project Configuration
examples/messaging/foundry.toml, hardhat.config.ts, package.json, remappings.txt, tsconfig.json
Introduced project setup files for Foundry and Hardhat environments, TypeScript configuration, dependency remappings, and package metadata with dependencies and scripts.
Solidity Contract
examples/messaging/contracts/Messaging.sol
Added Example contract extending Messaging with event emission on message receipt and revert, secure message sending methods supporting native and ERC20 tokens, and revert handling.
Artifact Loading Utility
examples/messaging/commands/common.ts
Added utility function to synchronously load contract ABI and bytecode artifacts from local JSON files.
CLI Commands
examples/messaging/commands/connect.ts, deploy.ts, message.ts, index.ts
Added CLI commands using commander for connecting contracts, deploying the messaging contract, sending cross-chain messages with token support, and an index file aggregating these commands.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~45 minutes


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9aea8 and 7f6650f.

⛔ Files ignored due to path filters (1)
  • examples/messaging/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • examples/messaging/commands/common.ts (1 hunks)
  • examples/messaging/commands/deploy.ts (1 hunks)
  • examples/messaging/commands/message.ts (1 hunks)
  • examples/messaging/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • examples/messaging/commands/common.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/messaging/package.json
  • examples/messaging/commands/message.ts
  • examples/messaging/commands/deploy.ts
⏰ 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)
  • GitHub Check: slither (examples/hello, hello.sarif)
  • GitHub Check: slither (examples/call, call.sarif)
  • GitHub Check: slither (examples/swap, swap.sarif)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch messaging

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@fadeev fadeev marked this pull request as ready for review July 21, 2025 17:13
@fadeev fadeev requested a review from a team as a code owner July 21, 2025 17:13
Copy link

@cursor cursor bot left a 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

// Handle both gasAmount and amount parameters
const gasAmount = ethers.utils.parseUnits(options.amount, 18);

Fix in CursorFix in Web


Comment bugbot run to trigger another review on this PR
Was this report helpful? Give feedback by reacting with 👍 or 👎

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between a8de3b1 and eca386e.

⛔ 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 dependency

Both 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:

  1. https://github.com/foundry-rs/forge-std/releases?utm_source=openai
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.

Comment on lines +8 to +9
const fs = require("fs");
const path = require("path");
Copy link
Contributor

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.

Comment on lines +5 to +26
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,
})
);
};
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +5 to +29
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,
})
);
};
Copy link
Contributor

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.

Suggested change
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.

Comment on lines 23 to 56
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";
}
Copy link
Contributor

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.

Comment on lines 70 to 96
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
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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 || ""] }),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
...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.

Comment on lines +2 to +4
"name": "nft",
"version": "1.0.0",
"description": "Universal NFT enables non-fungible ERC-721 tokens to minted and transferred between connected chains.",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
"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.

Comment on lines 55 to 65
"@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"
Copy link
Contributor

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:

  1. @zetachain/standard-contracts uses RC version (3.0.0-rc2) which may not be production-ready
  2. Solana-related packages appear unrelated to this Ethereum-based messaging example
  3. 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.

Suggested change
"@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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 when erc20 is specified, as the current logic expects both to be present together.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c397713 and e170930.

📒 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between e170930 and 1c9aea8.

📒 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.

Comment on lines 7 to 92
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,
})
);
};
Copy link
Contributor

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.

Suggested change
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.

Comment on lines 94 to 122
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);
Copy link
Contributor

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.

Suggested change
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.

@fadeev fadeev merged commit 60c59d8 into main Jul 31, 2025
20 checks passed
@fadeev fadeev deleted the messaging branch July 31, 2025 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants