Zetachain 🛠️ Building an Omnichain Swap Contract
ETHGlobal·Wed, Oct 9, 2024, 12:00 AM
In this tutorial you will learn how to create an omnichain smart contract for swapping tokens between blockchains. Users will be able to call this contract from any connected chain and do cross-chains swaps, for example, swap BTC for native ETH. Hack with some of the most skilled web3 developers, designers and product builders from all around the globe for a weekend-long adventure to advance the Ethereum ecosystem at ETHIndia 2023! Find more about Zetachain's participation at ETHIndia: https://ethglobal.com/events/ethindia2023/prizes#zetachain ----------------- Follow us: https://x.com/ethglobal​ Check us out: https://ethglobal.co​m Email us at: hello@ethglobal.com
Transcript
[Music] hello in this Workshop we're going to be creating an omni chain swap contract that will let users swap tokens between connected chains so it will allow users for example to swap eth or your C20 tokens for BTC or Matic so let's get started first of all we're going to be create Co copying uh clone actually a GitHub um a template for Zeta chain that is hosted on on GitHub so if you type git clone GitHub Zeta chain SL template and clo in template you will get a new directory called template let's check it out it's pretty empty just a bunch of config files but if you type yarn or you can use npm it will install all dependent es and this is a hard hat based template so you will get all the tooling installed automatically now if you type npx hard hat you will see all the standard commands as well as additional commands that were imported from Zeta chains toolkit like balances cctx and faucet so before we start it's um it's useful to create an account I already have one in this template but if you don't type npx hard hat account it will create a new account for you and if you want to save it just type save and it will create a new account and store it in a n file so you can uh so heart hat has access to it so once you did that uh you want might might want to check your balances so um balances command will query all the balances on all the connected chains and show it to you in a table make sure that you have tokens on Zeta chain as well as on at least one of the connected chains for example girly um in our documentation we have links to faucets you can use okay um to get uh to tokens on Zeta chain you can use the faucet command that we have here so it will ask you to confirm on GitHub not going to do that right now but once you do tokens will be sent to you okay cool so now we want to create an omni chain contract so what is an omni chain contract a a contract is considered to be Omni chain if it's deployed on Zeta chain and it inherits from uh Z contract and it implements a function called on on Cross chain call and this function gets called whenever uh users send tokens to a TSS address on one of the connected chains so basically it's a regular solidity contract that implements a certain function that can be remotely called from other chains so to create a contract we can use the tools we we have from the toolkit and the command is called Omni chain so it just accepts a name and arguments and the arguments are the values that you pass from a connected chain to your Omni chain contract so for example if you want to swap tokens uh you need to pass the token address that you want to get and the recipient right that's the only information you need everything else is um is accessible U aut automatically right so if you if we want to create a swap contract we can just pass two arguments one is going to be the target token address and the other one is going to be recipient so let's do that right now the first argument is the contract name then we're going to pass Target token uh by default arguments are strings but we know that the target token uh will be an address so we can pass an address and recipient we're going to leave it as is uh even though for evm chains recipient is an address but Omni chain contracts also support Bitcoin so we're going to see in a minute why we want to keep it as string um we're going to change this in in the source okay so when you run this command you will see that two three new files are created uh two tasks for deploying a contract one for interacting with a contract and the contract itself the contract is pretty straightforward it it's boilerplate code um But it includes some some some useful um Logic for decoding um uh message so you can see um that it's just basically one function called on questioning call and it accepts four arguments one is context which is useful because it contains chain id address is the zc20 address of the token that you're sending to the TSS address effectively depositing it into uh to Zeta chain amount is the amount of tokens and the message is the data that you pass from a connected chain to your contract as arguments so from evm chains that would be in the data field of a transaction from Bitcoin that would be in a memo okay so first of all let's dive into the on Cross chain call function and see what we need to do um we're going to have two values right that are we need to extract from the message so we're going to have Target token so Target token address and it's going to be of type address we're also going to have a recipient address recipient address for evm chains is of type of address but we also uh need to make sure that our contract is capable of swapping to bitcoin and for Bitcoin that is is a bch 32 address so we can't store it in in address variable so let's use byes memory for that now we need to take into account that a transaction might be coming from either an evm chain or from Bitcoin right so right now as of this moment Zeta chain supports three VM chains ethereum polygon and BC and one uh nonm chain Bitcoin so let's execute different logic based on the on the chain that this transaction is coming from so we're going to be using the context U argument and the chain ID property which returns an integer uh chain ID so if it's Bitcoin and let's define this as a constant so it's a bit easier to to see right so it's going to be Bitcoin and the chain ad that Z chains uses for Bitcoin testnet is 18332 and it's an integer okay this makes it a bit easier to read so if the if the message I mean if the transaction is coming from bit coin then we need to uh process it and extract value we can't use ABI decode because we want to make sure that uh the the the data coming from Bitcoin is as compact as possible and we format it manually so the way we do this is we use the first 20 bytes of the of the message for the token address right so we're going to be we're going to have a memo coming from Bitcoin with first 20 bytes as the target token addess and the following 20 bytes as recipient addess so for evm addresses 20 bytes is sufficient uh so that's good right so we need to manually decode thankfully we have some helpers we can use so we have this uh Library called bytes uh helper helper lib and this lib contains a function I mean you can check it out let's let's actually look where it is so in Zeta chains GitHub you can open a toolkit repo so let's just go to toolkit repo and this is installed automatically so you don't in don't need to install it but it has a contracts directory with bytes helper lib and it has some useful functions that we can use right and one of them is bites to address so we're going to use that to extract um value from the message and the the the argument is an offset we don't need an offset because it's the first value right uh let's import bytes helper lib from data chain toolkit it's in contracts and bytes might helper. okay so what this will do is it will parse the first 20 bytes and convert it into an address let's store this value in the Target token address so now we have our Target token address next we need to do the same for the for the recipient address so we're also going to use bytes helper lib bytes to address message but we're using 20 as an offset because we already parsed the first 20 bytes right so it's the same uh it's the same function but we're using it 20 as an offset and it will return an address what we need instead is actually um a b by B array so we can use ABI en code packed to convert this address into bytes and store that in the recipient address variable so this is going to be just bytes in the recipient address cool so we handled this process for Bitcoin and again when a user wants to call this contract from Bitcoin what they do is they send a native Bitcoin transaction on bitcoin to a special address called TSS address and they include the necessary arguments in the memo in our case that would be a Target token address and recipient address for example if you want to swap Bitcoin for uh BTC for eth you would um uh look up the eth zc20 address and use that as the target token address so it's the token you want to swap for okay we handled the the the Bitcoin uh scenario now let's use uh now let's process transactions coming from evm chains and they're all the same so we're just going to be using decode um address and then bytes again we're going to be passing these as as bytes so we need to to um specify the the type correctly here so the first is going to be of type address and let's say Target token and the second one is bytes memory recipient okay we've got these temporary variables now let's assign these um to to the variables we have in the in the um up here right um so Target token is going to be uh Target token address is going to be Target token um and recipient address it's going to be recipient so at this point we should have handled both cases right so if it's coming from Bitcoin we execute this logic uh otherwise we execute this logic um okay some formatting here cool now what we want to do is we want to actually implement the swapping logic so at this point we already have all the information we need from the message so we're we're going to be using this swap helper lib which again contains some useful functions and the way it works is on Zeta chain we have internal liquidity pools running on instances of of Unis swap so every supported token has a pair with Zeta and we can use these pools to swap between uh between zc20 versions of different tokens and that's exactly what we going to be doing right now so let's use swap helper lib uh uh do swap helper function so swap helper lib do Swap and we need to import it from uh the toolkit swap helper lip Okay so do swap accepts these arguments and we're just going to pass them um so first argument is the token address of Zeta on Zeta chain we have a contract called system contract that contains all these value uh variables that we need so if you type system contract we can use W Zeta contract address uh the second argument is unis swap Factory so system contract contains that as well Unis swap V2 Factory address uh the next one is the router okay so it's already pre-filling that then we need the zc20 token with which in our case is going to be in the in the argument here so we can just pass that and it will uh pass the address of the token that you are depositing that you want to swap next is amount we already have an amount as an argument for crossing call uh next is the target token address and this is the token address we have here and the next ismin amount we're just going to use zero for now okay so this function will do the actual swapping and it will output the amount of tokens that we got let's store it in output amount and that's going to be of Type U okay so now we've swapped the tokens next we need to withdraw them to the chain that they originate from so if we swapped BTC for eth we need to withdraw eth to ethereum depositing tokens right now is free but withdrawing uh will charge you a fee because for a TSS address to send tokens to the recipient address they need to pay um essentially valuators the protocol needs to pay a fee to transfer these tokens so that's why why Zeta chain is charging you a cross chain fee when you want to withdraw something so before we proceed by the way if you're interested to learn more about system contract you can find more information in the docs but also if you go to the protocol contracts repo you can find the system contract in the zvm directory here and learn more about what kind of data it contains and what it can do for you okay going back um we need to calculate this withraw fee and we have this interface ic20 uh that contains a that has a function called withdraw gas fee and it will return the fee the cross chain fee that you you will be charged and it needs Target token address so this returns two arguments one is uh gas zc 20 of type address right so it's the address of the token and the next is the actual gas fee okay um next we we need to actually do the withdrawal so we also use ic20 uh Target token address and we'll going to call withdraw but before we withdraw we actually need to approve the transfer of these tokens of these zc20 uh tokens for gas um on Zeta chain so we're going to do the same but with approv and gas fee so we need to approve the gas fee the transfer of gas fee on Zeta chain and once we do that uh we can actually use the withdraw so withdraw is a function of the zc20 contract so you can call that as a holder of zc20 you can call withdraw and withdraw tokens from Zeta chain to a connected chain so it accepts two arguments one is recipient address in bytes and it's very convenient that we already have it in bytes right it doesn't matter which chain you're withdrawing to evm bitcoin um it's going to work both if you pass if you store it in bytes and the second parameter is the amount of tokens which is going to be the output amount minus the gas fee so this way we will be um withdrawing the amount of tokens um but minus the the gas fee cool um okay it seems like we have a typo here and okay we made a small mistake because approve you can actually check it out in the protocol contracts as well so in zc20 do soul you can um see the approve and it actually needs two parameters one one is the spender and the actual and the second one is the amount so for uh for the first argument we're going to use Target token address and yeah this is just unused argument cool so this should do the trick let's check it out in the terminal uh and compile the cont contract so uh we're going to use an apx hard hat compile to compile the contract seems like it was compiled successfully and we're almost ready to deploy it to Zeta chain uh except one we we need to do one more thing so the inct task by default uh right now um assumes that you're going to be dealing with evm chains so to make this uh to to to change to modify this task to be compatible with Bitcoin uh we need to make some changes and the changes revolve around the recipient as you remember uh we're not passing it as a string or an address but rather as bytes and for for Bitcoin that would be um we need to take the the uh bch 32 address and convert it into hex right so we're going to have a recipient that is different from just the value that we pass on the CLI um so first of all we we again need to kind of execute a similar logic like if if it's Bitcoin then we do um one thing if it's not Bitcoin then we do something else right so to determine if the recipient address that we're passing is a bch 32 address we're going to be using bch 32 Library so import bch 32 from from bch 32 okay and in the in the condition we're going to check if bch 32 can decode the re recipient that we're passing on the CLI if it can decode then it's a valid uh Bitcoin address so we're going to be using that in as our recipient right but we need to take this B 32 formatted string and convert it into bytes so for this we're going to be using utils package of uh utils uh of The Ether package and solidity solidity pack function we need to convert it into bytes and to do that we're also going to use utils two UTF bytes and recipient so this will convert a recipient to bytes and pack it as a proper AR argument if it's not Bitcoin then we're going to use it as is well first of all let's import ills from ethers uh this decoding process will throw an error so we need to uh rra it in try catch block so if if it's an error then it's not not a Bitcoin address so we're just going to use recipient as is because we're assuming it's it's um uh it's an evm uh compatible address okay so once we have that we've updated bytes here uh this should be ready to go so make sure that you have Zeta tokens on Zeta chain ready and once you do have all the right tokens let's compile the contract again don't need to but let's do it anyway compiles and now we can deploy so we're going to use the deploy task not going to modify it at all just specify Network as Zeta test net so Omni chain contracts you only need to deploy once on Zeta chain don't need to deploy it on connected chains execute the deploy task once it's finished you should have a contract deployed on Zeta chain okay you can see that and now we can actually interact with this contract well first of all let's actually see it on block Scout if you go to blog scout.com uh you can paste the address to your contract here and see that it's successfully deployed which is good now we can actually interact with it so let's try swapping from um gly testet to BTC and for that we'll be using the inact task that we just worked with so the interact task accept set of arguments first of all is the amount of input tokens let's just put a small amount the second one is the contract we can copy and paste it from from the output recipient is going to be a Bitcoin address so to get our Bitcoin address we can again use the balances command it will show you all the balances and also uh the address so it's very handy you can just copy and paste it from here so this is your address on bitcoin you can pass paste this as recipient so it should decode it correctly and we're specifying the network that we are broadcasting this transaction to in our case that's ethereum test net and one last argument that we need is the target token so for this you actually need to go to documentation so Zach chain.com dooc you go into the test net and look up the zc20 token address of the token you want to swap to of course in a front end you can do this you should do this automatically but for for now we can just copy uh the Bitcoin uh zc20 address from here and paste it here okay it seems like we have a an error let's check it out what happened in the interact task let's check that everything's working correctly so if it's a Bitcoin address we decode it and yeah we forgot to store it in recipient so decoded but didn't know what to do with it okay let's try again so right now what is happening is a user sending a transaction on gly with um with values in the data field to a TSS address and we can check it um on the Explorer right now but before we do um the the the toolkit has a very useful command called cctx and you can pass either a transaction on a connected chain or a cross chain transaction and it's just going to start looking for this transaction on Zeta chain while it's looking let's go to gly and look up this transaction that we broadcasted uh you can see it was trans broadcasted 30 seconds ago and this is the the value right so the first 20 bytes of the data is the Omni chain contract address so it should match the contract that you deployed to here we go right it's you can see it here and the rest are the arguments that we're passing uh to our uh contract right so this is the content of the message and the first well here it's not 20 bytes because we're using AB code but the first value here is the is the Bitcoin uh zc20 address and the rest is the destination Bitcoin address formatted as hex okay um let's wait for this transaction to be processed and we'll see in real time when when it is it will show up so let's wait for okay we can see that we have two new cross chain transactions one is coming from ethereum testet to Z chain it was already mined right so this is a transaction uh that called the on uh the on Cross chain call function and the posited tokens and we have a transaction pending outbound from Zeta chain to bitcoin so we can use the Explorer Zeta chain Explorer to uh track these transactions so you can get more information about it you can see it it's coming from ethereum test net to Zeta chain the amounts the the message and um you can also look up the outbound cross transaction so this transaction is still pending but we can see it's coming from data chain to bitcoin um and you can also verify that it decoded the rece receiver correctly so it's the same exact address that we have in our balances so let's qu for balances again so 25 TX 25 TX so it decoded the Bitcoin address correctly from the message uh this might take a bit of time but once that's done you will see on outbound mind outbound mind status and you can check your um Bitcoin balance you'll see it increased because uh the swap was correct corly processed and now you have now you've swapped um one token uh to another so let's review what we did so far so we cloned a template from Zeta chain GitHub repository we used npx hard hat Omni chain command to create a new Omni chain contract we modified the logic of the contract to decode values correctly if it's coming from Bitcoin or if it's coming from an evm chain we used a swap helper to swap tokens from one from from one token to another and we withdraw tokens correctly calculating gas so if you want to learn more about how to do this and review the um the code you can go to Zeta Zeta chain docs and go to how many chain contracts section and click Swap and review the documentation here so it also includes information how to interact with the task thank you so much for watching and um see you in the next one
Automatic transcript — names and jargon may be misspelled.