From 739d9d1b19d982ca20ee4f71799bde628f3cbdc1 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Oct 2025 13:31:38 +0300 Subject: [PATCH 1/5] Update tutorials to use the new streamlined abstract UniversalContract-based examples --- src/pages/developers/tutorials/hello.mdx | 58 +++--------------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/src/pages/developers/tutorials/hello.mdx b/src/pages/developers/tutorials/hello.mdx index 2f3be81c9..92a0ee53e 100644 --- a/src/pages/developers/tutorials/hello.mdx +++ b/src/pages/developers/tutorials/hello.mdx @@ -57,22 +57,10 @@ A Universal App is a contract that implements the `UniversalContract` interface. // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; +import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; contract Universal is UniversalContract { - GatewayZEVM public immutable gateway; - event HelloEvent(string, string); - error Unauthorized(); - - modifier onlyGateway() { - if (msg.sender != address(gateway)) revert Unauthorized(); - _; - } - - constructor(address payable gatewayAddress) { - gateway = GatewayZEVM(gatewayAddress); - } function onCall( MessageContext calldata context, @@ -86,10 +74,6 @@ contract Universal is UniversalContract { } ``` -The constructor takes ZetaChain’s Gateway address and stores it in a state -variable. The Gateway is used for making outbound contract calls and token -withdrawals. - A universal contract must implement the `onCall` function. This function is triggered when the contract receives a call from a connected chain via the Gateway. The function processes incoming data, which includes: @@ -109,7 +93,8 @@ In this example, `onCall` decodes the message into a string 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. +of the function parameters. This is enforced by the `onlyGateway` modifier, +which is inherited from `UniversalContract`. ## Option 1: Deploy on Localnet @@ -160,39 +145,13 @@ The `forge build` command tells Foundry to compile all Solidity smart contracts within your project, ensuring you're working with the latest compiled versions. Successful compilation will generate bytecode for your contracts. -To deploy and interact with contracts on the ZetaChain localnet, you'll need the -address of the ZetaChain Gateway contract. This contract acts as the entry point -for cross-chain interactions on ZetaChain. - -- While your localnet is still running in its dedicated terminal, carefully - examine its output. -- Copy the Gateway contract address from the localnet terminal output. Look for - the row labeled `gateway` under the `ZETACHAIN` section. It will typically - appear in a format similar to this: - -``` -| gateway │ '0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6' | -``` - -Copy only the hexadecimal address (e.g., -`0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6`). Do not include the single quotes -or any other surrounding text. - -Alternatively, run the following command to get the Gateway address -programmatically: - -``` -GATEWAY_ZETACHAIN=$(jq -r '.["31337"].contracts[] | select(.contractType == "gateway") | .address' ~/.zetachain/localnet/registry.json) && echo $GATEWAY_ZETACHAIN -``` - Fetch a private key with pre-funded tokens on the connected chain: ``` PRIVATE_KEY=$(jq -r '.private_keys[0]' ~/.zetachain/localnet/anvil.json) && echo $PRIVATE_KEY ``` -Deploy the universal contract and provide the Gateway address in the -constructor: +Deploy the universal contract: ``` UNIVERSAL=$(forge create Universal \ @@ -200,8 +159,7 @@ UNIVERSAL=$(forge create Universal \ --private-key $PRIVATE_KEY \ --evm-version paris \ --broadcast \ - --json \ - --constructor-args $GATEWAY_ZETACHAIN | jq -r .deployedTo) && echo $UNIVERSAL + --json | jq -r .deployedTo) && echo $UNIVERSAL ``` ### Make a Call to the Universal App @@ -263,16 +221,14 @@ string. ### Deploy the Contract on ZetaChain -Deploy the contract to ZetaChain’s testnet using the Gateway address from the -[Contract Addresses page](/reference/network/contracts/): +Deploy the contract to ZetaChain’s testnet: ``` UNIVERSAL=$(forge create Universal \ --rpc-url https://zetachain-athens-evm.blockpi.network/v1/rpc/public \ --private-key $PRIVATE_KEY \ --broadcast \ - --json \ - --constructor-args 0x6c533f7fe93fae114d0954697069df33c9b74fd7 | jq -r .deployedTo) + --json | jq -r .deployedTo) ``` ### Call a Universal Contract from Base From 6c0ad6c0e1b54cd0589fa6ccbd68e4656b7f3e66 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Oct 2025 13:36:18 +0300 Subject: [PATCH 2/5] swap improvements --- src/pages/developers/tutorials/swap.mdx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pages/developers/tutorials/swap.mdx b/src/pages/developers/tutorials/swap.mdx index 1257e7511..0c59ca25d 100644 --- a/src/pages/developers/tutorials/swap.mdx +++ b/src/pages/developers/tutorials/swap.mdx @@ -32,8 +32,8 @@ The Swap contract performs the following steps: 3. Queries the withdrawal gas fee required to send the target token back to the destination chain. -4. Swaps a portion of the incoming tokens for ZRC-20 gas tokens to cover the - withdrawal fee using Uniswap v2 pools. +4. If withdrawing to a connected chain, swaps a portion of the incoming tokens + for ZRC-20 gas tokens to cover the withdrawal fee using Uniswap v2 pools. 5. Swaps the remaining balance into the target token. 6. Withdraws the swapped tokens to the recipient on the destination chain. @@ -93,7 +93,7 @@ to perform token swaps across blockchains with a single cross-chain call. Tokens are received as ZRC-20s, optionally swapped using Uniswap v2 liquidity, and withdrawn back to a connected chain. -### Universal App entrypoint: on_call +### Universal App entrypoint: onCall The contract is deployed on ZetaChain and implements `UniversalContract`, exposing a single entrypoint. Cross-chain deliveries are executed only via the @@ -145,6 +145,10 @@ gateway.withdraw( ); ``` +If the destination gas token (`gasZRC20`) is the same as the target token, the +contract approves a single combined allowance of `out + gasFee` for the Gateway +instead of two separate approvals. + ### Funding destination execution from the user’s input The app provisions destination gas out of the input, so users don’t need to @@ -260,6 +264,9 @@ SwapHelperLib.swapExactTokensForTokens( ); ``` +Note: If `inputToken == gasZRC20`, the contract uses `gasFee` directly (no swap +for gas is required) and only swaps the remaining balance to the target token. + You’re free to replace uniswapRouter and the helper calls with any DEX interface or custom routing logic—only the ZRC-20 token flow and the Gateway withdraw semantics are assumed by the rest of the contract. From 0b07f334d4b6da81bde6cf4876506bfae9a84f1c Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Oct 2025 13:51:46 +0300 Subject: [PATCH 3/5] nft --- src/pages/developers/standards/nft.mdx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/developers/standards/nft.mdx b/src/pages/developers/standards/nft.mdx index f81988f38..1e36cf65d 100644 --- a/src/pages/developers/standards/nft.mdx +++ b/src/pages/developers/standards/nft.mdx @@ -59,12 +59,11 @@ RPC_BASE=$(zetachain q chains show --chain-id 84532 -f rpc) RPC_ZETACHAIN=$(zetachain q chains show --chain-id 7001 -f rpc) ZRC20_ETHEREUM=$(zetachain q tokens show -s ETH.ETHSEP -f zrc20) -ZRC20_BASE=$(zetachain q tokens show -s ETH.BASESEP -f zrc20) +ZRC20_BASE=$(zetachain q tokens show -s ETH.BASESEP -f zrc20) GATEWAY_ETHEREUM=0x0c487a766110c85d301d96e33579c5b317fa4995 GATEWAY_BASE=0x0c487a766110c85d301d96e33579c5b317fa4995 GATEWAY_ZETACHAIN=0x6c533f7fe93fae114d0954697069df33c9b74fd7 -UNISWAP_ROUTER=0x2ca7d64A7EFE2D62A725E2B35Cf7230D6677FfEe GAS_LIMIT=1000000 ``` @@ -80,8 +79,6 @@ NFT_ZETACHAIN=$(npx tsx commands deploy \ --rpc $RPC_ZETACHAIN \ --private-key $PRIVATE_KEY \ --name ZetaChainUniversalNFT \ - --uniswap-router $UNISWAP_ROUTER \ - --gateway $GATEWAY_ZETACHAIN \ --gas-limit $GAS_LIMIT | jq -r .contractAddress) && echo $NFT_ZETACHAIN ``` From 90bc5138ab38e5a59ef6798b6affe02a708d7ded Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Oct 2025 13:53:45 +0300 Subject: [PATCH 4/5] token --- src/pages/developers/standards/token.mdx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/developers/standards/token.mdx b/src/pages/developers/standards/token.mdx index 65cf62f4f..e55223e38 100644 --- a/src/pages/developers/standards/token.mdx +++ b/src/pages/developers/standards/token.mdx @@ -58,12 +58,11 @@ RPC_BASE=$(zetachain q chains show --chain-id 84532 -f rpc) RPC_ZETACHAIN=$(zetachain q chains show --chain-id 7001 -f rpc) ZRC20_ETHEREUM=$(zetachain q tokens show -s ETH.ETHSEP -f zrc20) -ZRC20_BASE=$(zetachain q tokens show -s ETH.BASESEP -f zrc20) +ZRC20_BASE=$(zetachain q tokens show -s ETH.BASESEP -f zrc20) GATEWAY_ETHEREUM=0x0c487a766110c85d301d96e33579c5b317fa4995 GATEWAY_BASE=0x0c487a766110c85d301d96e33579c5b317fa4995 GATEWAY_ZETACHAIN=0x6c533f7fe93fae114d0954697069df33c9b74fd7 -UNISWAP_ROUTER=0x2ca7d64A7EFE2D62A725E2B35Cf7230D6677FfEe GAS_LIMIT=1000000 ``` @@ -79,8 +78,6 @@ ZETACHAIN_TOKEN=$(npx tsx commands deploy \ --rpc $RPC_ZETACHAIN \ --private-key $PRIVATE_KEY \ --name ZetaChainUniversalToken \ - --uniswap-router $UNISWAP_ROUTER \ - --gateway $GATEWAY_ZETACHAIN \ --gas-limit $GAS_LIMIT | jq -r .contractAddress) && echo $ZETACHAIN_TOKEN ``` From dbf4f18c24d56fb3d7d8ac0637fbbae964fe953a Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Oct 2025 14:04:09 +0300 Subject: [PATCH 5/5] call --- src/pages/developers/tutorials/call.mdx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/developers/tutorials/call.mdx b/src/pages/developers/tutorials/call.mdx index 1086df8ca..c861a299b 100644 --- a/src/pages/developers/tutorials/call.mdx +++ b/src/pages/developers/tutorials/call.mdx @@ -88,10 +88,13 @@ function onCall( ### Making outgoing calls -To call a contract on a connected chain from your Universal App, you must first -approve the Gateway to spend the gas token: +To call a contract on a connected chain from your Universal App, first quote the +destination gas fee for your intended gas limit, pull that fee from the caller, +and approve the Gateway to spend it: ```solidity +(, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit(callOptions.gasLimit); +IZRC20(zrc20).transferFrom(msg.sender, address(this), gasFee); IZRC20(zrc20).approve(address(gateway), gasFee); ``` @@ -267,7 +270,6 @@ your connected EVM testnet (for example, Base Sepolia) and the Gateway addresses for each chain. ```bash -GATEWAY_ZETACHAIN=0x6c533f7fe93fae114d0954697069df33c9b74fd7 GATEWAY_BASE=0x0c487a766110c85d301d96e33579c5b317fa4995 RPC_ZETACHAIN=https://zetachain-athens-evm.blockpi.network/v1/rpc/public @@ -281,8 +283,7 @@ UNIVERSAL=$(forge create Universal \ --rpc-url $RPC_ZETACHAIN \ --private-key $PRIVATE_KEY \ --broadcast \ - --json \ - --constructor-args $GATEWAY_ZETACHAIN | jq -r .deployedTo) && echo $UNIVERSAL + --json | jq -r .deployedTo) && echo $UNIVERSAL ``` ### Deploy Connected to Base Sepolia @@ -451,10 +452,9 @@ from the localnet registry: ```bash RPC=http://localhost:8545 -ZRC20_ETHEREUM=$(jq -r '."11155112".chainInfo.gasZRC20' ~/.zetachain/localnet/registry.json) && echo $ZRC20_ETHEREUM +ZRC20_ETHEREUM=$(jq -r '."11155112".zrc20Tokens[] | select(.coinType == "gas" and .originChainId == "11155112") | .address' ~/.zetachain/localnet/registry.json) && echo $ZRC20_ETHEREUM PRIVATE_KEY=$(jq -r '.private_keys[0]' ~/.zetachain/localnet/anvil.json) && echo $PRIVATE_KEY GATEWAY_ETHEREUM=$(jq -r '.["11155112"].contracts[] | select(.contractType == "gateway") | .address' ~/.zetachain/localnet/registry.json) && echo $GATEWAY_ETHEREUM -GATEWAY_ZETACHAIN=$(jq -r '.["31337"].contracts[] | select(.contractType == "gateway") | .address' ~/.zetachain/localnet/registry.json) && echo $GATEWAY_ZETACHAIN ``` Deploy the Universal App: @@ -464,8 +464,7 @@ UNIVERSAL=$(forge create Universal \ --rpc-url $RPC \ --private-key $PRIVATE_KEY \ --broadcast \ - --json \ - --constructor-args $GATEWAY_ZETACHAIN | jq -r .deployedTo) && echo $UNIVERSAL + --json | jq -r .deployedTo) && echo $UNIVERSAL ``` Deploy the Connected contract: