Developers
Cross-Chain Messaging
Tutorials
Cross-Chain Counter
Build a cross-chain counter
This is an example app of cross-chain counter using Zeta Connector.
Set up your environment
git clone https://github.com/zeta-chain/template
cd template
yarn
Create a new contract
npx hardhat messaging Counter from:address
from
: address of the sender
contracts/Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@zetachain/protocol-contracts/contracts/evm/tools/ZetaInteractor.sol";
import "@zetachain/protocol-contracts/contracts/evm/interfaces/ZetaInterfaces.sol";
contract Counter is ZetaInteractor, ZetaReceiver {
error InvalidMessageType();
error DecrementOverflow();
event CounterEvent(address);
event CounterRevertedEvent(address);
mapping(address => uint256) public counter;
bytes32 public constant COUNTER_MESSAGE_TYPE =
keccak256("CROSS_CHAIN_COUNTER");
ZetaTokenConsumer private immutable _zetaConsumer;
IERC20 internal immutable _zetaToken;
constructor(
address connectorAddress,
address zetaTokenAddress,
address zetaConsumerAddress
) ZetaInteractor(connectorAddress) {
_zetaToken = IERC20(zetaTokenAddress);
_zetaConsumer = ZetaTokenConsumer(zetaConsumerAddress);
}
function sendMessage(uint256 destinationChainId) external payable {
if (!_isValidChainId(destinationChainId))
revert InvalidDestinationChainId();
uint256 crossChainGas = 2 * (10 ** 18);
uint256 zetaValueAndGas = _zetaConsumer.getZetaFromEth{
value: msg.value
}(address(this), crossChainGas);
_zetaToken.approve(address(connector), zetaValueAndGas);
connector.send(
ZetaInterfaces.SendInput({
destinationChainId: destinationChainId,
destinationAddress: interactorsByChainId[destinationChainId],
destinationGasLimit: 300000,
message: abi.encode(COUNTER_MESSAGE_TYPE, msg.sender),
zetaValueAndGas: zetaValueAndGas,
zetaParams: abi.encode("")
})
);
}
function onZetaMessage(
ZetaInterfaces.ZetaMessage calldata zetaMessage
) external override isValidMessageCall(zetaMessage) {
(bytes32 messageType, address from) = abi.decode(
zetaMessage.message,
(bytes32, address)
);
if (messageType != COUNTER_MESSAGE_TYPE) revert InvalidMessageType();
counter[from]++;
emit CounterEvent(from);
}
function onZetaRevert(
ZetaInterfaces.ZetaRevert calldata zetaRevert
) external override isValidRevertCall(zetaRevert) {
(bytes32 messageType, address from) = abi.decode(
zetaRevert.message,
(bytes32, address)
);
if (messageType != COUNTER_MESSAGE_TYPE) revert InvalidMessageType();
if (counter[from] <= 0) revert DecrementOverflow();
counter[from]--;
emit CounterRevertedEvent(from);
}
}
Create a task to get the counter value
tasks/counter_show.ts
import { task } from "hardhat/config";
import { HardhatRuntimeEnvironment } from "hardhat/types";
const contractName = "CrossChainCounter";
const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
const [signer] = await hre.ethers.getSigners();
console.log(`🔑 Using account: ${signer.address}\n`);
const factory = await hre.ethers.getContractFactory(contractName);
const contract = factory.attach(args.contract);
const counter = await contract.counter(signer.address);
console.log(`🔢 The counter for ${signer.address} is: ${counter.toString()}
`);
};
task(
"counter:show",
"Sends a message from one chain to another.",
main
).addParam("contract", "Contract address");
hardhat.config.ts
import "./tasks/counter_show.ts";
Create a task to increment the counter value
tasks/interact.ts
import { task } from "hardhat/config";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { parseEther } from "@ethersproject/units";
const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
const [signer] = await hre.ethers.getSigners();
const factory = await hre.ethers.getContractFactory("Counter");
const contract = factory.attach(args.contract);
const destination = hre.config.networks[args.destination]?.chainId;
if (destination === undefined) {
throw new Error(`${args.destination} is not a valid destination chain`);
}
const paramFrom = hre.ethers.utils.getAddress(args.from);
const value = parseEther(args.amount);
const tx = await contract
.connect(signer)
.sendMessage(destination, { value });
const receipt = await tx.wait();
if (args.json) {
console.log(JSON.stringify(tx, null, 2));
} else {
console.log(`🔑 Using account: ${signer.address}\n`);
console.log(`✅ The transaction has been broadcasted to ${hre.network.name}
📝 Transaction hash: ${receipt.transactionHash}
`);
}
};
task("interact", "Sends a message from one chain to another.", main)
.addFlag("json", "Output JSON")
.addParam("contract", "Contract address")
.addParam("amount", "Token amount to send")
.addParam("destination", "Destination chain")
.addParam("from", "address");
Deploy the contract
Clear the cache and artifacts, then compile the contract:
npx hardhat compile --force
npx hardhat deploy --networks goerli_testnet,mumbai_testnet
🚀 Successfully deployed contract on mumbai_testnet.
📜 Contract address: 0xbe58130dcD7db27f7b79AE27F91d2D74324c5999
🚀 Successfully deployed contract on goerli_testnet.
📜 Contract address: 0x0e10dF07DCA39Ae5e09bC37897E846b281A68A6C
🔗 Setting interactors for a contract on mumbai_testnet
✅ Interactor address for 5 (goerli_testnet) is set to 0x0e10df07dca39ae5e09bc37897e846b281a68a6c
🔗 Setting interactors for a contract on goerli_testnet
✅ Interactor address for 80001 (mumbai_testnet) is set to 0xbe58130dcd7db27f7b79ae27f91d2d74324c5999
Increment the counter value
Show initial counter value on both chains
npx hardhat counter:show --network goerli_testnet --contract 0x0e10dF07DCA39Ae5e09bC37897E846b281A68A6C
🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1
🔢 The counter for 0x2cD3D070aE1BD365909dD859d29F387AA96911e1 is: 0
npx hardhat counter:show --network mumbai_testnet --contract 0xbe58130dcD7db27f7b79AE27F91d2D74324c5999
🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1
🔢 The counter for 0x2cD3D070aE1BD365909dD859d29F387AA96911e1 is: 0
Increment the counter value
npx hardhat interact --network goerli_testnet --contract 0x0e10dF07DCA39Ae5e09bC37897E846b281A68A6C
--amount 0.3 --destination mumbai_testnet
🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1
✅ The transaction has been broadcasted to goerli_testnet
📝 Transaction hash: 0xd0e5adadda20236fd1f50c2e3290e823744015e3227242fb22c78f27b46a63db
Show the counter value after increment
npx hardhat counter:show --network mumbai_testnet --contract 0xbe58130dcD7db27f7b79AE27F91d2D74324c5999
🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1
🔢 The counter for 0x2cD3D070aE1BD365909dD859d29F387AA96911e1 is: 1
Source Code
You can find the source code for the example in this tutorial here:
https://github.com/zeta-chain/example-contracts/tree/main/messaging/counter