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()
.
Withdraw ZRC-20 Tokens
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.
Withdraw ZRC-20 Tokens and Call a Contract on a Connected Chain
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.
Call a Contract on a Connected Chain
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.
Call Options
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.
Format of the message
Parameter
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 tohello(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).
Revert Transactions
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.
- A contract on a connected chain calls
depositAndCall
orcall
on the Gateway. - The Gateway forwards the call to a universal contract on ZetaChain.
- 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
istrue
, the Gateway invokes theonRevert
function.
-
If the
amount
is insufficient or zero (for example, when it's a no-assetcall
) the Gateway callsonAbort
on theabortAddress
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:
- A universal contract on ZetaChain initiates a call to a connected chain via
withdrawAndCall
orcall
. - The Gateway forwards the message and/or tokens to the target contract on the connected chain.
- If the target contract reverts, ZetaChain initiates the revert process.
Revert Behavior:
-
If
callOnRevert
istrue
:- The Gateway invokes the
onRevert
function on therevertAddress
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 callsonAbort
on theabortAddress
on ZetaChain.
- The Gateway invokes the
-
If
callOnRevert
isfalse
:- The Gateway transfers tokens to the
revertAddress
without invoking any function.
- The Gateway transfers tokens to the
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 callonRevert
.abortAddress
: Address to call ifonCall
reverts (for a no-asset call) or reverting fails (for an asset call)revertMessage
: Message passed toonRevert
andonAbort
.onRevertGasLimit
: Max gas allowed foronRevert
. 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
Direction | Trigger | Revert Path | Fallback if Revert Fails |
---|---|---|---|
Connected → ZetaChain | onCall() in universal contract fails | onRevert() on connected chain (if funded) | onAbort() on ZetaChain |
ZetaChain → Connected | Target contract on connected chain fails | onRevert() on ZetaChain (if callOnRevert ) | onAbort() on ZetaChain |