[Music] Thank you all for coming. This is going to be a slightly different type of workshop. In the past, I've done this as structured slides, but I've thought one, I want to get you guys first to the main content, which is how do I send ERC20s as fast as possible to new networks? and two, how can I actually whiteboard and show you maybe at a more um sidebyside level how everything under the hood is working and operating within the layer zero ecosystem. So with that, uh my timer starts now.
I'm going to try and jump in and get this OFT deployment done in 5 minutes or less. So the repo that you have in front of you right now um is one that I created already, but for the sake of doing this live, I'm going to also create a new one using our layer zero CLI package. The command I just ran was npx create lzo app at latest. Uh but you can actually use this however you want. In this case I'm going to call this one eth global of2.
Um from this drop down I'm going to select. I'm going to use pnpm to install. You could use yarn or some other package managers if you like. Hopefully the internet's nice with me today and we'll be able to install this relatively quickly. But in this repo uh this is essentially a boilerplate sample for getting started with layer zero of our token standard for moving ERC20 tokens between networks.
In this repo you get out of the box uh both foundry and hard hat scripting that you can take advantage of and some example contracts to play around with. Um the main one being here my you can see that uh we did indeed finish installing in our new repo. So, I'm going to go ahead and CD into ETH global 2. Um, and I'm going to also just copy over really really quickly uh from the uh repo one level up our uh env file. Um, hopefully don't flash my private keys either.
Perfect. Um, before we get started, just going to get init and let's walk through what we're actually going to do today. We're going to deploy an OFT on two different networks. Uh the way that this works actually takes advantage of the hard hat framework. In hard hat config, you can see that there's three networks predefined.
We only need two. So we're going to delete one of them. And I'm going to deploy between base and arbitrum since those are the chains that I have funds on. The way that the network setup works is pretty straightforward. Every layer zero chain that's supported has something called an endpoint deployed on it.
And that endpoint has a unique identifier defining that chain within the layer zero ecosystem. So in this case, I'm going to define base v2 mainet as the chain that I'm going to deploy to. I'm also going to define arbitrum v2 mainet as my endpoint IDs. I'm going to quickly just show you how to grab RPCs relatively quickly for this. Um if you're not already familiar, you can go to chain list.
You can search any network that you want and get a full list of public RPCs that are available. Um, I'm going to grab the tenderly ones because they have always treated me kindly in the past. Um, but you uh may want to play around if you have issues with certain endpoints not working more than others. All right, so there's base. Next one we'll grab is Arbitum.
I'm also going to grip for tenderly. There we go. Um, so that's our entire config file. two networks, one with base, a base endpoint ID, one with arbitrum, arbitum endpoint ID. Great.
Next file we're going to edit really quickly is our layer zero config file in this repo. The LZ config file is basically defining the connections between your chains and is more a structured way for you to figure out what parameters do I want to use within the layer zero ecosystem. So again, we're going to do the exact same thing here. Grab base v2 mainet. I'm going to rename this to my base contract.
I'm going to rename this to arbitum contract and uh hopefully spell it correctly. Uh you can see here that same as before uh the contract name that I'm going to be using for the connection is my O of the one that's found here as a demo. Um one small change I'll add just for this demo is showing you how to mint 10 tokens. So it's just like an ERC20 token. OFT inherits the ERC20 standard by Open Zeppelin.
So you can use that directly as you would any other ERC20 token. Um, further down in this config object, you'll find pathways. And pathways are unique because they define basically between these two networks, what are the security parameters that I'm going to use. For example, what layer zero DVNs that you want to use. Uh, what gas limits to enforce on the destination chain.
In this case, we're delivering 80,000 uh gas limit zero message value on the transactions. Um, and then finally just defining the contracts that are getting connected in our mesh. So with that, we have everything in place that we need to actually deploy. I'm going to run npx hard hat lz deploy. It's going to compile my project with the changes I just made.
I'm going to deploy on both arbitrum and base the myft contract. It's going to now go through the RPCs that I configured in hard hat and then deploy those for me using our deploy script found here. You can see that one deployment is wrapping up right now for base. The next one's wrapping up now for arbitrum. And we have two contracts deployed on base and arbitum here.
And last step I'm going to do before we can actually start sending tokens is run loop wire pointing at the layer zero config file we just edited. We can hit wire. It'll present us with a list of transactions that we just have to sign in order to set the configuration and enable message passing. We'll hit yes. It'll go through these 12 transactions, six on each chain relatively quickly.
Probably faster if I use paid RPCs, but that's the struggle of the hackathon. Um once this gets done, what we're going to do is just run our send task and you'll have successfully sent an ERC20 from one chain base to another chain arbitum. Um so last transaction getting submitted here. Perfect. And your OAP is now configured.
So we provide in the repo uh a handy script that you can use to send tokens from either chain. In this case, I'm sending one token in human readable units from base. This is the endpoint ID for bass to arbitrum, the arbitum ID, and then the receiver wallet address that I'm going to be re receiving tokens to. U I'm going to run this. It's going to quote the native gas cost, include it as value for the send transaction, and then successfully send those tokens, which we can check here on layer zero scan.
So, I frontloaded you with the hardest part of this, which is how do I actually get this done in my local IDE. But let's actually explore from the transactions that I just submitted, what actually happened here on chain. Um, if we look at this destination transaction that's now been delivered, you can see that from null address to my wallet receiver address, I received one ear c20 token called my OFT. Um, what's interesting here is that this is just minting tokens under the hood. And we'll actually see if we dive into the code what of is doing.
So under the hood, the OFT contract is simply an ERC20 that has some bridge logic appended to the contract. Right? So the way that it works is very very similar to any ERC20 you've worked with in the past. The only difference is that under the hood, if we go into OFT core, OFTs define a way for you to debit and credit tokens between blockchains. So in this method that we have here called send effectively what we're doing is we are taking a amount of tokens based on some send parameter provided and we are debiting tokens from one user account and crediting them to another.
If we look at what this send param is actually defined, you are defining a destination layer zero endpoint to send a token balance to a receiver for those tokens, an amount of tokens to send in 256 value, and then some optional fields which uh I might dive into in a little bit. But the unique thing about this is that from the receiver side, it's equally as simple. If we go back into our OFT core contract, you can see here that when we call send, this debit function gets invoked, debit then uh either will burn tokens on the source chain or lock tokens depending on the implementation. You can see in OFT.soul, debit calls burn, so we burn them.
Uh and then in the receiving chain, this method LZ receive gets called. LZ receive is just our standard entry point for any type of message packet that gets delivered to a destination app. You can see here that on LZ receive what we tell in the O of standard is to credit tokens to the um message sender that we wanted to deliver it to in the first place. The two address here uh and then the amount of tokens that we're delivering which are packed inside of the message. So what you get to uniquely do with the OFT standard is basically define in debit and credit your logic for what happens on each chain and then in the OFT core logic here you can see actually how are messages encoded into a bytes array.
So zooming out a bit, I think I've thrown a lot of information at you. Um, what actually happened just now and how did we achieve it? So within any implementation of layer zero, whether you're building an OFT or uh what we call an OAP, you are basically doing what we define as pushbased messaging, which means you're pushing some state information from one blockchain to another. The way that this works is that you the app developer will define some data types. So here's our endpoint contract on arbitum.
Let's say what you're doing in this design pattern is you are first saying all right well I know that I want to send an amount to a destination chain and I know I want to debit those tokens so they aren't circulating anymore on the source blockchain. So in order to do that what I have to do is first define some send function here that has two important roles. Um it updates state on the source blockchain. In this case that state update is burning tokens and then similarly we want to encode the amount plus receiver in order to receive those tokens. So encode the amount receiver and then dispatch a layer zero message that contains amount and receiver.
So if we look at the code example that was provided here in OFT under OFT core when we look at the send function to figure out more of what's happening here. Let's go to underscore send and just do function. So in underscore send you can see that first we do the debit method which uh in this case all it does is uh burns tokens right from a sender uh and then returns an amount an amount sent in this case um once of core gets that uh amount out you can see that then we use that amount sent and amount received amounts in this build message and options function. What build message and options actually does under the hood is relatively straightforward. We basically have some library that we call message codec to encode those different data structures, the amount of tokens, the receiver and any other arbitrary params that we're sending into a layer zero packet.
uh in this case of message codec the encoding is just using AI encode packed which encodes the address that we're sending to the amount of tokens that we want to deliver on the destination chain um a address itself and then a optional composed message um in this case we are uh defining it as a composed message only if it has again in the send param that uh optional field that I mentioned previously. So when I say compose message if a token transfer is sent with this param then it is considered composed and uses this line otherwise you use this line. Um but moving back through the uh encoding step what's essentially happening here is that this is just returning some arbitrary encoding of bytes. So going back to our drawing when we say that we're encoding the amount and receiver let's call amount 256 and let's call receiver uh bytes 32. The encoded array or bytes will essentially look like this if it's packed.
It'll have for unit 256 um four 32 byt words plus for bytes 32 one more 32 byt word. Um this is what our our message looks like when it gets sent to layer zero. Uh then on the destination chain on base what we're effectively doing is we're doing the reverse in our contract that's receiving the final call out. We are decoding the message for the params that we need. So what this looks like effectively is taking the message in bytes that we just encoded and decoding it backed out.
When we use um for example AI.enccode, that method actually allows you to provide the type of data structure that you're using in order to pull the data back out into a usable format for your contract. But in this case, if we look back at the O of when we see LZ receive, we have that raw message uh being delivered back into the contract. Uh and then we're splicing specifically out at certain points what we know our data structure to look like. So in this case, the uh message send to um you can see here returns from a specific offset in the byte array where we're going to deliver.
You can see all the definitions up here for where in the bytes array we're essentially splicing the data structure from uh and encoding. Uh so with this you can kind of see how the O of does its magic. We basically have some logic where anytime a user calls send what's happening is we are taking the amount the receiver and any other necessary params encoding them into bytes arrays delivering that bytes array via layer zero to a destination chain and then decoding that message back out in order to do the type of operations that we want to do in in this case LZ receive to a specific receiver address for an amount of tokens. Um, looking a bit more closely at some simpler examples that you can play around with within the layer zero documentation. Um, we can check really quickly on how to push, pull data, or send any other types of tokens.
Um, so if you guys have any questions, I think I'll stop there. Uh, but let's Yeah, go ahead.
So, thank you for the presentation.
First of all, thank you for the presentation.
Um, I have a question. How can we correlate the received transaction and the send transaction onchain using an LZ read?
So LZ read uses the exact same method. We can actually check in here. If you go to omni chain queries from the source page, you can see an example contract. And the way that the contract works is the same exact way. For read data, you're using LZ send the same as the O of does for dispatching.
The only difference is that you have a special function called get command here that formats requests to target blockchains based as a uh specialized strct that we call a call request. So essentially if you look at this git command you can see that it defines one call request to target a specific blockchain by its endpoint ID and then also a target contract address with some call data. I would recommend reading through this example as it defines what that example contract looks like. In this case, the target contract is just some arbitrary contract that has a variable and we're reading the variable in the example. But there are more that you can look at by going to layer zero uh not docs but dev tools.
If you look at the main branch under examples, you can find more advanced use cases of LZ read and their implementations here. for example. So this one is for math for summing two values on a different chain. This one is for um taking certain data types and manipulating them afterwards. So hopefully that answers
well
my question was specifically about um a transaction with a transaction hash on chain A
or the eid or it
mh
um and then using that with an LG read to find if the coins were or the tokens were actually received on the target chain.
Oh definitely. So that's another use case that you can do too. If you want to for example, let's say have a send request, you can let's say we modified our uh send function in the OFT contract for example. You could modify the send so that it did something and apologize for the pseudo code, but do something like this where you know you send tokens and then dispatch uh a custom LZ read call via basically generating again let's say we took the example from docs of getting a command out. we can go through here and define a similar function like what read data does um and then invoke it within the send transaction of the OFT.
So the idea would be send a transaction for the OFT burn the tokens and then request a read but have it request a read let's say in five block confirmations on the target chain. You could do something like that where in the same transaction the user pays to token transfer and pays to do a pull of the data on that chain at a set point in time later in the the sequence.
Hey um so in your example you're creating the ERC can I use an existing token and wrap it? You can use uh in the same example pattern when I ran MPX create LZO app um I selected for example OFT if you want to use oh let's do uh one two three four five you can use O of adapter so OFT adapter is the exact same standard the only difference is that this contract uses uh safe ERC20 to lock tokens in so you can take an existing ERC20 supply the address of it in the constructor and then it'll use that ERC20 via transfer methods. So when you call send, it'll transfer the amount of token into the lock box, emit a layer zero message, and then just have the normal OFT deployed on that chain and it'll mint. So lock and mint or uh burn and unlock is the pattern with OFT adapter.
Can you show an example about calling a function from on another chain? Yeah,
definitely.
Yeah. Um, one example that you could look at is how call data is submitted. So if we take a look at the examples here, you can see that we have one called omniall. The way that omni works um is a little bit more intuitive. So in this case you can define some arbitrary call and a transfer.
The encodings for this for call are coming from this codec library. But a call is just a target uh target address that you're calling on with value and bytes, right? And the way that it works is that this essentially encodes a type call on send. So you can see here that this uh contract omni message codec lib encodes a certain message type with the call and amount of value to transfer and then on lz receive decodes the values out of uh address to transfer from target value call data and then just executes uh a simple call in this case uh target.call directly.
So you can do types of calls like this where if you know the parameters and the message value that you want to deliver on a destination chain, you can use uh those parameters on destination to call a target function in your receive function. Cool. I think we're out of time, but thank you guys so much for coming. If you have more questions, we'll be at the booth happy to answer follow-ups. [Music]
Automatic transcript — names and jargon may be misspelled.