diff --git a/docs/developers/cross-chain-messaging/examples/multi-chain-value.md b/docs/developers/cross-chain-messaging/examples/multi-chain-value.md index df970d78..aca4108b 100644 --- a/docs/developers/cross-chain-messaging/examples/multi-chain-value.md +++ b/docs/developers/cross-chain-messaging/examples/multi-chain-value.md @@ -8,59 +8,127 @@ title: Multichain Value Transfer # Multichain Value Transfer -This is an example contract that shows how you can send value across chains via -the ZetaChain API. +In this tutorial you will learn how to send ZETA tokens between connected +blockchains using ZetaChain. -From this rudimentary example, you could easily extend it to support arbitrary -asset exchanges via a swap to/from ZETA on source and destination. - -# Multichain Value Transfer - -In this tutorial we will create a contract that allows sending value from one -chain to another using the -[Connector API](/developers/cross-chain-messaging/connector/). +In this example you will only be sending ZETA tokens without any associated +message. ## Set up your environment ``` git clone https://github.com/zeta-chain/template -``` -## Create a new contract +cd template -```solidity title="contracts/MultiChainValue.sol" reference -https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/value/contracts/MultiChainValue.sol +yarn ``` -The contract's main functionality is implemented in the `sendValue` function. - -The `send` function first checks if the destination chain ID is valid and if the -Zeta value and gas are not zero. +## Create the Contract -Next, it attempts to approve and transfer the specified amount of Zeta tokens -from the sender's address to the contract's address. +To create a new cross-chain messaging contract you will use the `messaging` +Hardhat task available by default in the template. -Finally, the function calls the "send" function of a connector contract, -providing the necessary inputs such as the destination chain ID, destination -address, gas limit, and other parameters. The function encodes these inputs into -a message and sends it to the connector contract for further processing. +``` +npx hardhat messaging Value --fees zetaRevert +``` -The contract also uses a notion of "available chains". Before calling the send -function and transferring value between chains you need to call the -`addAvailableChainId` function on the source chain and add the destination chain -ID to the list of available chains. In this example this logic is implemented in -the deploy task. +Use the `--fees` flag to specify that you want your contract to accept ZETA +tokens as fees. Since the purpose of this contract is to send ZETA tokens, it +makes sense to also use ZETA tokens as fees. + +## Modify the Contract + +```solidity title="contracts/Value.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.7; + +import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@zetachain/protocol-contracts/contracts/evm/tools/ZetaInteractor.sol"; +import "@zetachain/protocol-contracts/contracts/evm/interfaces/ZetaInterfaces.sol"; +import "@zetachain/protocol-contracts/contracts/evm/Zeta.eth.sol"; + +// highlight-next-line +contract Value is ZetaInteractor { + error ErrorTransferringZeta(); + // remove-start + error InvalidMessageType(); + + event ValueEvent(); + event ValueRevertedEvent(); + + bytes32 public constant VALUE_MESSAGE_TYPE = keccak256("CROSS_CHAIN_VALUE"); + // remove-end + IERC20 internal immutable _zetaToken; + + constructor( + address connectorAddress, + address zetaTokenAddress + ) ZetaInteractor(connectorAddress) { + _zetaToken = IERC20(zetaTokenAddress); + } + + function sendMessage( + uint256 destinationChainId, + uint256 zetaValueAndGas + ) external payable { + if (!_isValidChainId(destinationChainId)) + revert InvalidDestinationChainId(); + + bool success1 = _zetaToken.approve(address(connector), zetaValueAndGas); + bool success2 = _zetaToken.transferFrom( + msg.sender, + address(this), + zetaValueAndGas + ); + if (!(success1 && success2)) revert ErrorTransferringZeta(); + + connector.send( + ZetaInterfaces.SendInput({ + destinationChainId: destinationChainId, + destinationAddress: interactorsByChainId[destinationChainId], + destinationGasLimit: 300000, + // highlight-next-line + message: abi.encode(), + zetaValueAndGas: zetaValueAndGas, + zetaParams: abi.encode("") + }) + ); + } + + // remove-start + function onZetaMessage( + ZetaInterfaces.ZetaMessage calldata zetaMessage + ) external override isValidMessageCall(zetaMessage) { + bytes32 messageType = abi.decode(zetaMessage.message, (bytes32)); + + if (messageType != VALUE_MESSAGE_TYPE) revert InvalidMessageType(); + + emit ValueEvent(); + } + + function onZetaRevert( + ZetaInterfaces.ZetaRevert calldata zetaRevert + ) external override isValidRevertCall(zetaRevert) { + bytes32 messageType = abi.decode(zetaRevert.message, (bytes32)); + + if (messageType != VALUE_MESSAGE_TYPE) revert InvalidMessageType(); + + emit ValueRevertedEvent(); + } + // remove-end +} +``` -## Create a deployment task +Modify the contract so that it only inherits from `ZetaInteractor`. Since the +purpose of the contract is to only send ZETA tokens (and not messages), it +doesn't need to inherit from `ZetaMessageReceiver` and implement the +`onZetaMessage` and `onZetaRevert` functions. -The deploy task is fairly standard. It deploys the contract to two or more -chains and sets the interactors on each chain. Additionally, for this example, -the script also calls the `addAvailableChainId` function on each chain to add -the other chain to the list of available chains. +You can also remove the message type from the `connector.send` call. -```ts title="tasks/deploy.ts" reference -https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/value/tasks/deploy.ts -``` +## Deploy the Contract Clear the cache and artifacts, then compile the contract: @@ -71,34 +139,28 @@ npx hardhat compile --force Run the following command to deploy the contract to two networks: ``` -npx hardhat deploy --networks polygon-mumbai,bsc-testnet +npx hardhat deploy --networks goerli_testnet,mumbai_testnet ``` ## Send a message -Create a new task to send tokens from one chain to another. The task accepts the -following parameters: contract address, recipient address, amount, destination -chain ID, and the source network name. - -Send a message from Polygon Mumbai testnet to BSC testnet (chain ID: 97) using -the contract address (see the output of the `deploy` task). Make sure to submit -enough native tokens with `--amount` to pay for the transaction fees. +Run the following command to send ZETA tokens from Goerli to Mumbai. Please, +note that since the contract expect ZETA tokens as fees, the value of the +`--amount` param is denomited in ZETA tokens. A fraction of the amount will be +deducted as a cross-chain fee, the rest will be sent to the recipient on the +destination chain. ``` -npx hardhat send --contract 0x042AF09ae20f924Ce18Dc3daBFa1866B114aFa89 --address 0xF5a522092F8E4041F038a6d30131192945478Af0 --amount 20 --destination 97 --network polygon-mumbai +npx hardhat interact --contract 0xe6DE62328677C80084b07eF25637EC83A53d69E1 --network goerli_testnet --destination mumbai_testnet --amount 3 🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1 -✅ The transaction has been broadcasted to polygon-mumbai -📝 Transaction hash: 0x2748882492a565627e4726658744f443fb993943f25ba73f93dba42ae314e689 - -Please, refer to ZetaChain's explorer for updates on the progress of the cross-chain transaction. - -🌍 Explorer: https://explorer.zetachain.com/address/0x042AF09ae20f924Ce18Dc3daBFa1866B114aFa89 +✅ The transaction has been broadcasted to goerli_testnet +📝 Transaction hash: 0x4996283c199fafe4c15f33a8ef6d4a41d00545b0736bac0e5a74d72fb342b4c7 ``` ## Source Code You can find the source code for the example in this tutorial here: -https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/value +https://github.com/zeta-chain/example-contracts/tree/main/messaging/value