By the end of this tutorial, you will have learned how to:
- Build a universal app that emits an event when called from a connected chain
- Deploy the universal app on localnet
- Use the Gateway on a connected chain to call a universal app
Set Up Your Environment
Start by cloning the example contracts repository and installing the necessary dependencies:
git clone https://github.com/zeta-chain/example-contracts
cd example-contracts/examples/hello
yarn
Universal Contract
A universal app is a contract that inherits from UniversalContract
interface.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol";
contract Universal is UniversalContract {
GatewayZEVM public immutable gateway;
event HelloEvent(string, string);
modifier onlyGateway() {
require(msg.sender == address(gateway), "Caller is not the gateway");
_;
}
constructor(address payable gatewayAddress) {
gateway = GatewayZEVM(gatewayAddress);
}
function onCall(
MessageContext calldata context,
address zrc20,
uint256 amount,
bytes calldata message
) external override onlyGateway {
string memory name = abi.decode(message, (string));
emit HelloEvent("Hello: ", name);
}
}
A universal contract must implement the onCall
function. onCall
is a
function that gets triggered when the contract receives a call from a connected
chain through the Gateway. This function processes the incoming data, which
includes:
context
: AMessageContext
struct containing:chainID
: The integer ID of the connected chain from which the cross-chain call originated.sender
: The address (EOA or contract) that initiated the gateway call on the connected chain.origin
: deprecated.
zrc20
: The address of the ZRC-20 token representing the asset from the source chain.amount
: The number of tokens transferred.message
: The encoded data payload.
In this example, onCall
simply decodes the message into a variable and emits
an event.
onCall
should only be called by the Gateway to ensure that it is only called
as a response to a call on a connected chain and that you can trust the values
of the function parameters.
Options 1: Deploy on Localnet
Localnet is a local development environment, which simulates the behavior of Gateways deployed on multiple EVM blockchains. Start localnet:
npx hardhat localnet
npx hardhat compile --force
Deploy the universal contract and pass ZetaChain's Gateway address to the constructor. You can find the Gateway address in the output of Localnet.
npx hardhat deploy --network localhost --gateway 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
🚀 Successfully deployed "Universal" contract on localhost.
📜 Contract address: 0xc351628EB244ec633d5f21fBD6621e1a683B1181
Make a Call to the Universal App
To call the universal app deployed on ZetaChain from a connected chain, make a
call to the Gateway contract on a connected EVM chain using the evm-call
task:
npx hardhat evm-call \
--network localhost \
--gateway-evm 0x610178dA211FEF7D417bC0e6FeD39F05609AD788 \
--receiver 0xc351628EB244ec633d5f21fBD6621e1a683B1181 \
--types '["string"]' alice
Pass the EVM Gateway Address from the Localnet's table of addresses and the universal contract address as the receiver. A universal app expects to receive a single string in the message, so pass the appropriate type and value to the command.
After the transaction is processed you will see an [ZetaChain]: Event from onCall
message in the terminal where Localnet is running.
Option 2: Deploy on Testnet
Deploy the contract to ZetaChain's testnet using the Gateway address from
Contract Addresses page
.
npx hardhat deploy --network zeta_testnet --gateway 0x6c533f7fe93fae114d0954697069df33c9b74fd7
🔑 Using account: 0x4955a3F38ff86ae92A914445099caa8eA2B9bA32
🚀 Successfully deployed "Universal" contract on zeta_testnet.
📜 Contract address: 0x11998e1A5D2e770753263376ceE78B14c9617f16
Make a transaction to the Gateway on the Base Sepolia testnet to make a cross-chain call to the universal app on ZetaChain. Make sure to use Gateway address on Base Sepolia.
npx hardhat evm-call \
--network base_sepolia \
--gateway-evm 0x0c487a766110c85d301d96e33579c5b317fa4995 \
--receiver 0x11998e1A5D2e770753263376ceE78B14c9617f16 \
--types '["string"]' alice
Transaction hash: 0x3fbed0a3dc48eda00212aff6e930940d84b142f7da316b9186f0c68edd0793b2
You can track the progress of a cross-chain transaction using ZetaChain's API: