Build
Connected Chains
ZetaChain

To make a call from a universal app to a contract on a connected chain or withdraw tokens, use the ZetaChain gateway.

The ZetaChain gateway supports:

  • Withdrawing ZRC-20 tokens as native gas or ERC-20 tokens to connected chains.
  • Withdrawing tokens and making a contract call on connected chains.
  • Calling contracts on connected chains.

Note: Withdrawing ZETA tokens is currently not supported and will revert with ZETANotSupported().

To withdraw ZRC-20 tokens to an EOA or a contract on a connected chain, use the withdraw function of the gateway contract:

function withdraw(bytes memory receiver, uint256 amount, address zrc20, RevertOptions calldata revertOptions) external;

The receiver can be either an externally-owned account (EOA) or a contract on a connected chain. Even if the receiver is a smart contract with a standard receive function, the withdraw function will not trigger a contract call. If you need to withdraw and call a contract on a connected chain, use the withdrawAndCall function instead.

The receiver is of type bytes to accommodate different address formats used by various chains (e.g., Bech32 for Bitcoin). This type ensures the receiver address is chain-agnostic. When withdrawing to an EVM chain, ensure you convert address to bytes.

When withdrawing to non-EVM chains make sure to encode the receiver address to bytes as string, meaning you take an address as a string of characters and convert it into bytes.

For example, if the receiver address on Solana is:

GBwCxLUt5qn12aCD4uVKMWnoXPn2DoH126p8FrFmGNUy

The receiver bytes should be:

0x47427743784c557435716e31326143443475564b4d576e6f58506e32446f4831323670384672466d474e5579

The amount specifies the quantity to withdraw, and zrc20 is the ZRC-20 address of the token being withdrawn.

The revertOptions.revertMessage must not exceed 1024 bytes in length.

You don't need to specify the destination chain since each ZRC-20 token is tied to the chain from which it was deposited. A ZRC-20 token can only be withdrawn to its originating chain. For example, to withdraw ZRC-20 USDC.ETH to the BNB chain, you must first swap it to ZRC-20 USDC.BNB.

To withdraw ZRC-20 tokens and call a contract on a connected chain, use the withdrawAndCall function:

function withdrawAndCall(bytes memory receiver, uint256 amount, address zrc20, bytes calldata message, CallOptions calldata callOptions, RevertOptions calldata revertOptions) external;

This function withdraws tokens and makes a call to a contract on the connected chain identified by the zrc20 address. For instance, if ZRC-20 ETH is withdrawn, the call is made to a contract on Ethereum.

The combined length of message and revertOptions.revertMessage must not exceed 1024 bytes.

To call a contract on a connected chain without withdrawing tokens, use the call function:

function call(bytes memory receiver, address zrc20, bytes calldata message, CallOptions calldata callOptions, RevertOptions calldata revertOptions) external;

Here, zrc20 represents the ZRC-20 token address of the gas token for the destination chain. This address acts as an identifier for the target chain. For example, to call a contract on Ethereum, use the ZRC-20 ETH token address.

The combined length of message and revertOptions.revertMessage must not exceed 1024 bytes.

The CallOptions parameter specifies details for making calls to contracts on connected chains. It is used in both the call and withdrawAndCall functions:

struct CallOptions {
    uint256 gasLimit;
    bool isArbitraryCall;
}
  • gasLimit: The maximum gas the cross-chain contract call can consume. If the gas usage exceeds this limit, the transaction reverts.
  • isArbitraryCall: Determines whether the call is "arbitrary" (true) or "authenticated" (false).

An arbitrary call invokes any function on a connected chain but does not retain the original caller's identity—within the target contract, msg.sender is the Gateway address, not the originating universal contract. This is suitable for scenarios like token swaps, where the caller's identity is unnecessary.

An authenticated call specifically targets the onCall function of a contract on the connected chain. Authentication is achieved because the onCall function receives the context.sender parameter, referencing the originating universal contract. This allows the target contract to verify and trust the initiating universal app, rejecting unauthorized calls.

For arbitrary calls (when isArbitraryCall is true) the message parameter in the withdrawAndCall and call functions contains the encoded function selector and arguments for the target contract:

  • Function Selector: The first 4 bytes of the Keccak-256 hash of the function signature.
  • Arguments: The remaining bytes, ABI-encoded according to Ethereum's rules.

For example:

0xa777d0dc00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005616c696365000000000000000000000000000000000000000000000000000000
  • Function Selector: 0xa777d0dc corresponds to hello(string).
  • Arguments: The remaining data represents the argument alice, encoded in hexadecimal (616c696365).

For authenticated calls the message is just ABI-encoded arguments (no function selector in the beginning, because authenticated calls are routed to a specific onCall function).

When a cross-chain transaction (CCTX) fails, ZetaChain uses the RevertOptions struct to determine how to handle the failure. The behavior depends on the direction of the transaction — whether it's from a connected chain to ZetaChain or from ZetaChain to a connected chain.

Connected Chain → ZetaChain (Incoming)

This scenario happens when a contract on a connected chain sends tokens or a message to ZetaChain using the depositAndCall or call function on the Gateway.

  1. A contract on a connected chain calls depositAndCall or call on the Gateway.
  2. The Gateway forwards the call to a universal contract on ZetaChain.
  3. If the onCall function reverts, the protocol initiates the revert process.

Revert Behavior:

  • If the amount sent with the original call is enough to cover revert gas fees:

    • ZetaChain swaps part of the amount into gas tokens (ZRC-20) for the connected chain.
    • The protocol sends the remaining tokens and revert message to the revertAddress on the connected chain.
    • If callOnRevert is true, the Gateway invokes the onRevert function.
  • If the amount is insufficient or zero (for example, when it's a no-asset call) the Gateway calls onAbort on the abortAddress on ZetaChain.

ZetaChain → Connected Chain (Outgoing)

This scenario occurs when a universal contract on ZetaChain calls withdrawAndCall or call on the Gateway to interact with a contract on a connected chain.

Flow:

  1. A universal contract on ZetaChain initiates a call to a connected chain via withdrawAndCall or call.
  2. The Gateway forwards the message and/or tokens to the target contract on the connected chain.
  3. If the target contract reverts, ZetaChain initiates the revert process.

Revert Behavior:

  • If callOnRevert is true:

    • The Gateway invokes the onRevert function on the revertAddress on ZetaChain.
    • The remaining ZRC-20 tokens are passed along with the revert context.
    • If the onRevert call itself reverts, the Gateway transfers ZRC-20 tokens to and calls onAbort on the abortAddress on ZetaChain.
  • If callOnRevert is false:

    • The Gateway transfers tokens to the revertAddress without invoking any function.

The RevertOptions struct specifies how assets are handled in case of a cross-chain transaction (CCTX) revert.

RevertOptions Struct

struct RevertOptions {
    address revertAddress;
    bool callOnRevert;
    address abortAddress;
    bytes revertMessage;
    uint256 onRevertGasLimit;
}
  • revertAddress: The address that receives tokens or revert logic. If revert address is zero, reverted tokens are transferred to the original sender of the call.
  • callOnRevert: Whether the Gateway should call onRevert.
  • abortAddress: Address to call if onCall reverts (for a no-asset call) or reverting fails (for an asset call)
  • revertMessage: Message passed to onRevert and onAbort.
  • onRevertGasLimit: Max gas allowed for onRevert. Determines the amount of tokens that will be used

onRevert

struct RevertContext {
    address asset;
    uint64 amount;
    bytes revertMessage;
}
 
interface Revertable {
    function onRevert(RevertContext calldata revertContext) external;
}
  • On a connected chain, asset is the ERC-20 originally deposited (or zero address for gas assets).
  • On ZetaChain, asset is the ZRC-20 withdrawn during the original call.

onAbort

struct AbortContext {
    bytes sender;
    address asset;
    uint256 amount;
    bool outgoing;
    uint256 chainID;
    bytes revertMessage;
}
 
interface Abortable {
    function onAbort(AbortContext calldata abortContext) external;
}
  • Called on ZetaChain as a fallback when revert execution fails.
  • Used in both incoming and outgoing transactions.

Summary

DirectionTriggerRevert PathFallback if Revert Fails
Connected → ZetaChainonCall() in universal contract failsonRevert() on connected chain (if funded)onAbort() on ZetaChain
ZetaChain → ConnectedTarget contract on connected chain failsonRevert() on ZetaChain (if callOnRevert)onAbort() on ZetaChain