Cross-Chain Messaging Fees
A user (wallet, contract) must pay fees in order to send data and value across chains through ZetaChain. A user pays for fees by sending ZETA (and message data) on a connected chain to a Connector contract. This ZETA is used to pay validators/stakers/ecosystem pools, as well as for paying the gas on the destination. To a user, this is all bundled into a single transaction.
When sending a cross-chain message you're paying two types of fees:
- Outbound gas fee: calculated dynamically based on the gas prices for the destination chain, the gas limit supplied by the user and the token prices on the liquidity pools on ZetaChain.
- Protocol fee: currently, a fixed value defined in the ZetaChain source code.
You can use ZetaChain's HTTP API to get the cross-chain messaging fees:
{
"outboundGasInZeta": "19433380636221748",
"protocolFeeInZeta": "2000000000000000000",
"ZetaBlockHeight": "1202557"
}
Both outboundGasInZeta
and protocolFeeInZeta
are in azeta
.
In the example above, a user would have to pay 2.019 ZETA to send a cross-chain message to the BSC testnet.
Different Approaches to Paying the Fees
When you write a smart contract that uses cross chain messages this contract needs to pay fees in ZETA for every cross chain transaction. There are several ways to handle this.
Sending ZETA to the Connector
In your cross-chain messaging contract approve zetaValueAndGas
amount of ZETA
tokens to the connector and then transfer them to the connector contract.
The main disadvantage with this approach is that the user must approve your contract before and they have to have enough ZETA in his wallet.
function sendMessage(uint256 destinationChainId, bytes calldata destinationAddress, uint256 zetaValueAndGas) external {
if (zetaValueAndGas == 0) revert InvalidZetaValueAndGas();
bool success1 = ZetaEth(zetaToken).approve(address(connector), zetaValueAndGas);
bool success2 = ZetaEth(zetaToken).transferFrom(msg.sender, address(this), zetaValueAndGas);
if (!(success1 && success2)) revert ErrorTransferringZeta();
connector.send(
ZetaInterfaces.SendInput({
destinationChainId: destinationChainId,
destinationAddress: destinationAddress,
destinationGasLimit: 300000,
message: abi.encode(),
zetaValueAndGas: zetaValueAndGas,
zetaParams: abi.encode("")
})
);
}
Pay With ZETA From the Contract
You can add ZETA tokens to the contract and the contract will use these tokens when sending cross chain messages.
This is easier for end-users, because they don't have to think about using ZETA tokens, but it's more complex for the contract developer because they have to ensure that the contract has enough ZETA tokens.
function sendMessage(uint256 destinationChainId, bytes calldata destinationAddress) external {
bool success1 = ZetaEth(zetaToken).approve(address(connector), ZETA_GAS);
if (!success1) revert ErrorApprovingZeta();
connector.send(
ZetaInterfaces.SendInput({
destinationChainId: destinationChainId,
destinationAddress: destinationAddress,
destinationGasLimit: 300000,
message: abi.encode(),
zetaValueAndGas: ZETA_GAS,
zetaParams: abi.encode("")
})
);
}
Pay With Any Token and Swap to ZETA
Your contract can accept any token and swap it to ZETA internally.
This approach is more complex, because you need to add a swap logic to your contract and take market price fluctuations into account. But it's more convenient for end-users, because they can use any token to pay for cross chain messages without even knowing that ZETA is being used under the hood.
To make it eaier you can use ZetaConsumer's getZetaFromEth
to swap any token
to ZETA.
function sendMessage(uint256 destinationChainId, bytes calldata destinationAddress) external payable{
uint256 crossChainGas = 2 * (10 ** 18);
uint256 zetaValueAndGas = _zetaConsumer.getZetaFromEth{value: msg.value}(address(this), crossChainGas);
bool success1 = ZetaEth(zetaToken).approve(address(connector), zetaValueAndGas);
if (!success1) revert ErrorApprovingZeta();
connector.send(
ZetaInterfaces.SendInput({
destinationChainId: destinationChainId,
destinationAddress: destinationAddress,
destinationGasLimit: 300000,
message: abi.encode(),
zetaValueAndGas: zetaValueAndGas,
zetaParams: abi.encode("")
})
);
}
ZetaTokenConsumer is an interface with several implementations that handles all the logic you need to swap ZETA from/to another token. Right now we have three implementations (Uniswap V2, Uniswap V3, and Trident) using different DEX. You can include it in your contract and just call the appropriate method.