New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

RIP-7755: Empowering Cross-Chain Interactions by Jack Chuma | Devcon SEA

DevconTue, Oct 7, 2025, 12:00 AM

Speaker

Cross-chain interactions are becoming essential as Ethereum Layer 2 solutions multiply. RIP-7755 changes the game by trustlessly bridging the gap between L2 chains, allowing new use cases that rely solely on Ethereum and its rollups. In this workshop, we’ll explore RIP-7755 by building a cross-chain NFT minting app, focusing on nested storage proof implementation details to eliminate trust assumptions. Speaker(s): Jack Chuma Skill level: Intermediate Track: Layer 2 Keywords: Cross-L2, Rollups Follow us: https://twitter.com/efdevcon, https://twitter.com/ethereum, https://warpcast.com/devcon Learn more about devcon: https://www.devcon.org/ Learn more about ethereum: https://ethereum.org/ Visit the https://archive.devcon.org/ to gain access to the entire library of Devcon talks with the ease of filtering, playlists, personalized suggestions, decentralized access on Swarm, IPFS and more. Devcon is the Ethereum conference for developers, researchers, thinkers, and makers. Devcon SEA was held in Bangkok, Thailand on Nov 12 - Nov 15, 2024. Devcon is organized and presented by the Ethereum Foundation. To find out more, please visit https://ethereum.foundation/

Transcript

[Music] okay I think we're good to get started hello welcome to the workshop for RP 7755 this is a proposed standard for empowering low-level cross-chain calls with minimal um trust assumptions my name is Jack chuma I'm uh a senior software engineer working in R&D on the base team at coinbase and I'm so ex excited to share with you all a little bit about this project so a couple of goals for today um first and foremost I want to promote a deeper understanding of rip 7755 what exactly it is and how it works um second considerations for adding new chain support in the future as I foresee that being one of the uh main opportunities for open source contribution number three one of the main um features of the standard is that it minimizes trust assumptions and that's done via a mechanism called what we're calling nested storage proofs so I'd like to do a deep dive there and uh promote a a deeper understanding um and then lastly integration details so if you're an app developer and you would like to be able to facilitate some kind of cross-chain call between l2s and the ethereum EOS system how would that work um if you're if you're integrating with the standard so to give you some context in a a highle purpose on where we're coming from here I included a screenshot here from L2 beat um sometime last week and what it's showing is activity in ethereum l2s and more specifically how it's surged by over 500% in just the last year Alone um and that's being spread out over many different networks so that's why I chose this screenshot specifically it shows um a handful of networks this is just a small subset of um how many l2s there are already and that's only just going to continue expanding um and this has been great for scaling ethereum but it has caused fragmentation in the ecosystem um where if you're a user and you want to interact with an app that's deployed to a specific chain that you maybe don't have funds on um there are certain Hoops that you need to jump through to get funds to the correct location to be able to um you know interact with that application and that hurts the user experience it's a critical problem that has to be solved um and so to solve that problem uh we believe that there should be a standard between or for communication between chains that uh checks the following three boxes is public and decentralized in the spirit of web 3 relies solely on validation data that is trustless available on chain so minimal trust assumptions and has built-in flexibility to support any arbitrary message these three uh bullet points where the three North stars that uh we kept in mind as as we developed the RP 7755 proposal and uh we'll dive in now so because ethereum l2s uh post some sort of state representation to a shared execution environment they are uniquely positioned to solve this problem with minimal um trust assumptions uh and this is done via a mechanism called nested storage proofs as I previously mentioned um this allows us to prove State about one L2 from another L2 even though they don't have a direct line of communication to understand storage proofs um I think it makes sense to do a quick refresher on Merkel trees uh of course this is nothing new but uh a required prere to understand um storage proofs and how they work under the hood so uh as a quick quick recap here um a Merle tree is basically a tree data structure where each node is a hash of its direct descendants and so if you wanted to represent uh like in this diagram here I don't know if you can see my mouse yeah um we have these four data blocks a b c and d and if you wanted to convert that into a Merkel tree each one gets hashed uh respectively to create a leaf node for the tree you group The the nodes into groups of two hash concatenate them hash them together that creates their parent node and you do that recursively until you reach the root node of the tree and effectively what you've done is generated a unique identifier um for the entire data set that is just a single hash so this has a couple of interesting properties one being that if any of the data blocks changes in any way whatsoever the the root hash is going to change completely um and then this also like this property allows us to efficiently prove inclusion or exclusion of data within the larger data set so in this example if we wanted to prove that data block a it was in this larger data set of ABC and D we first would need a verifier to have trustless access to the the root hash that is uh represented in this root node up here if that's in place all we'd have to supply to the verifier is data block a and then this node of hash of B and then this node hash of HC HD and that data alone is all the verifier would need to recreate the the root hash at the top of the tree so the verifier would then Hash A to create this Hash a node combine Hash A with hash B hash that together to create this hash ha HB node and then do that again for this this final level to recreate the root node and if that is equivalent to this the the stored root node that the verifier already had then that's a successful proof so how does that apply to ethereum storage um basically all of ethereum's state is represented as a modified form of that Merkel tree data structure called a Merkel Patricia Tri and the exact uh details in terms of the differences between the two data structures are out of scope for this talk but um it's it's basically a handful of optimizations for theorem specific use cases all we really need to know or care about about for this application is that that Merkel proof uh Paradigm applies here so for every block in a theum there's a handful of block headers one of which being a state route this is the the root hash for a Merkel Patricia to try for all of ethereum state where the values in that try are ethereum accounts um these accounts could be EAS so like any offchain wallet like coinbase wallet or or metamask or it could be smart contract accounts um the like these accounts as they're represented in the try are represented by a array of four pieces of metadata about the account and that's what I have listed out here so you have the account nons balance storage route and code has for um yeah so for smart contract accounts they're it's highly likely that they're managing some type of state and if they are that state is stored um in that contract storage and that contract storage is also represented as a Merkel Patricia try under the hood and you guessed it the root of that TR is the storage route that is uh stored with the account so if we have access to a state route for a network um we can supply a path down that state try to a specific account within the network and then using that account's storage route which can be extracted from the account's metadata um we can supply another path from the storage route to a specific location in that accounts storage and that's basically the highle concept of how a storage proof works so how does that apply to cross chain messaging though because that's just proving State like within a specific Network so to explain that I have this diagram here it's very simplified obviously but um this meant to represent two rollups in the ethereum ecosystem that are both sharing state with a a shared L1 um so this L1 at the bottom would be like ethereum main net and then chain a and chain B are two L2 networks what this diagram is depicting is bidirectional communication between the two layer two chains and the shared layer one um for this downward Arrow Direction in both CHS uh an ethereum L2 chain wouldn't be an ethereum L2 chain if it wasn't sharing state with L1 in some uh context so that's what this is representing here uh it's requiring that both chains are sharing some representation of their state with what I'm calling a rollup contract on layer one um this could be the state route directly or it could be some other representation of State the only requirement being that it has to be verifiably linkable to its state route at least for the way that um RP 7755 is working thus far and then in the other direction we need trustless access to a layer one state representation within the L2 chains as well so that's what these upward arrows pointing to the beacon Roots Oracle contracts are um this is made possible by an improvement proposal that's live today in many networks called EIP 4788 which trustless exposes the most recent uh 8,191 Beacon routes for um the ethereum consensus client within the L2 execution environment it should be noted a beacon route is not the same as uh execution client state route but it is verifiably linkable to the L1 execution client State Route via very similar process so from chain a if we were trying to prove something about the state of chain B um this diagram represents everything that needs to be true for that to work so if chain a starts with a uh trustless access to a beacon route it can supply uh like a Merle Patricia try based proof um to verify the L1 execution client State Route and then if we have a verified L1 execution client State Route that's that exact storage rout or storage proof process that I just went through applies um so we could then prove anything about the state of layer one from chain a in this context the first step would be to go from the state route to um like a path to an account within that state try and the account here would be chain B rollup contract and then using chain B rollup contract storage route we can supply another path to a specific location in um in that contract what's interesting here is if the value that you're verifying inside of chain B rollup contract is itself a state representation for chain B you can then recursively follow the same steps again to prove something about chain B's State try so then starting from chain B's um State Route you can supply a path to a specific account within chain B and then again a path to from that from that account storage route to a specific storage location and that effectively allows us to prove uh verifiably a location in storage in an account on chain B from chain a even though there's no direct line of communication so that's cool but how does that help us with cross chain calls um this diagram is an overall architecture for how rip 7755 is set up to work um and that should help answer this question here so as you can see we have two chains represented in origin in a destination and then we have both onchain and offchain components here um every supported chain is going to have some sort of inbox and outbox contract that we're calling RP 7755 inbox and outbox but I'll just stick with inbox and outbox for for the rest of the talk um the outbox contract is basically the entry point into the standard so if a user wants to request a cross-chain call they submit a request to the outbox contract if the request settles properly um it'll Emmit an event that some offchain actor that we're calling fulfillers should be listening for and if there's sufficient incentive to respond to that request then the fulfiller will so that brings us back to um a way to incentivize fulfillers to respond to the request another key piece of uh logic that happens here is the user will also lock some kind of reward Bounty um for the fulfiller to respond to the request if the reward Bounty is sufficiently like incentivizes the fulfiller then it'll respond so the fulfiller then assuming it's a sufficient incentive will submit the requested call to the destination chain over here um that routes through an RP 7755 inbox contract which will perform a handful of validation steps mainly confirming that the request is arriving to the correct chain and uh the correct location at the correct chain at that um there's also this custom like optional validation step called a pre-check contract where this could be absolutely anything as long as it aderes to a specific pre-check contract interface that the standard requires um and all this is meant to do is allow the user to encode any kind of arbitrary fulfillment condition um that should be true in order for the Fulfillment to to work out but like I said it again it's totally optional so if it's if it's not being used this step will be skipped if all the validation steps um are like sufficiently check out properly um then the requested calls will be routed from there um so this could be a batch of any low-level arbitrary call that goes to a handful of addresses within coded call data um and any native currency value that may be included if all of those are successfully um are successful the main purpose of this inbox contract is to then store a receipt of successful execution um and this execution receipt gets stored in a deterministic location within this contract storage that is derivable from the origin chain without knowing anything about the state of the destination chain um so that's important and we'll come back to it in a second after the call has been successfully submitted the fulfiller then comes back to the origin chain to say hey I I I did the job now can I have my my payment and what's unique about this standard is that the payment will only be released if the fulfiller can cryptographically prove that they did actually submit the request to the destination chain and that's done via that nested storage proof concept that we just walked through um so from the origin chain the uh origin chain would have to be able to verify a specific storage location in the uh inbox contract on destination chain and because of the fact that the outbox contract can derive exactly where that location is supposed to be then um the outbox contract has everything it needs to be able to verify the successful fulfillment of the call so only if this nested storage proof um checks out will the outbox contract release the reward to the fulfiller and that would close the loop on the full process for how RP 775 is working um yeah so for today's Workshop I have a um example project for us to go through together so if anyone's interested in uh coding along there is a starter repository on my GitHub that I can show in a second um if you're not interested in following along I'll just I'll be doing it up here as well so um we can go from there so I'm going to start by walking through all of the contracts and services that are present in the starter project uh this is going to be a self-contained system that is mocking a multi- rollup uh ecosystem that will run locally on your on your machine so once you once you clone the repository you should have everything needed to run the entire app end to end locally so after a brief walkr of all the services and how they're working together to make that work we'll Implement and test a nested storage proof validation contract as that is where the bulk of the effort is going to have to be applied to add new chains support in the future I also just think it's really cool um and then once that is working properly we'll integrate with an offchain client application so for this demo it's just a simple nft mint application where the nft owner wants to be able to support users who uh don't necessarily have funds on the chain that the nft contract lives on so if they don't it would be an R rip 7755 request to send the cross chain call to still Min the nft so once that is all set up we should be good to run the app end to end before we jump in I'll leave you uh well I guess I'll leave this presentation piece with that it's still very early in the research phase that uh a lot of these details are subject to change but we have proven the concept on live networks of this nested storage proof and how that can be used to trustless verify cross chain calls so this seems to hold a lot of potential it's something I'm very excited about something the base team is very excited about um we do have an open source proof of concept repository on the Bas or GitHub that I fully invite anyone and everyone to contribute to if you have uh interesting ideas so with that uh we should be good to dive into code but as a quick gut check uh are there any questions before doing so and there will be time for questions at the end too and and after this talk okay so I didn't know the best way to share the link but um my GitHub is is my name Jack chuma and I have this Devcon 2024 rip 7755 Workshop project um if anyone's interested in coding along you can clone this repository and uh and follow along with me I already have it cloned so um we can start with a brief walk through here so once you have the project what you'll notice is there's two main directories we have contracts and services so this is onchain and offchain um components from that architecture diagram we can start by going through the contracts um I'll start with the nft contract because it's very very simple um but this is what our demo client is going to be using the main piece here is this mint function the only reason I'm covering it is because this is going to be needed to uh set up the integration when we get there but there's nothing too interesting happening here um next up we have a rollups directory and this is used to mock the multi rollup system locally um I didn't want to have to rely on good internet connection for this to work so uh we got a mock system running locally this rollup contract is what would be deployed to a Mach L1 and this is what will be storing a state representation for the Mach l2s in this context that state representation is a hash of the L2 block Tim stamp and the L2 State Route um and then on the L2 side of things we have this Beacon Oracle contract which is meant to mock the EIP 4788 interface interface to query um one of the beacon routes that are being stored in the L2 execution environment for this example it's a simplified example and this is directly storing the L1 execution client State Route so um we we are cutting out a step of ver verification going from Beacon route to state route but um I think it still gets the message across and then we have all of our RP 7755 contracts so as a brief walk through for what an actual request looks like we have this RP 7755 stru file and this is exactly what a request would look like so um all the fields are we start with a requester so this is the pretty self-explanatory the address submitting the request we have a batch of calls where where each call is a low-level um description of the exact address that you'd like to send the call to encoded call data and then any native currency value that should be included with that call then we have a specified Prov contract um the reason this is here is because there's no standard way for L2 chains to post their state representation to L1 um and because of that the exact implementation details for verifying State about a destination chain will vary depending on what that destination chain is so right now we have the setup with the proving logic abstracted into two separate contracts this very likely will change in the near future if we baked that into the outbox contract um that would require multiple outbox contracts to be deployed to each chain um so it's a trade-off but for right now this is set up to be one outb one outbox one inbox and then uh an array of pro or contracts deployed to each chain that the user would have to specify which one um should be used to verify fulfillment and this contract is what we're going to be implementing in a few minutes then we have destination chain ID which is pretty self-explanatory inbox contract is the address of the RP 7755 inbox contract on the destination chain um L2 Oracle address that is the address of the rollup contract that would get deployed to L1 for the destination chain so this is the user specifying where the approver contract should be looking for the the state representation for the destination chain when it's verifying that the call was submitted um so the user is specifying the address where that should be located as well as the storage key within that address um and then we have a reward address this could be an erc20 address or as specified by ERC 7528 there's a special address value that can be used to um depict native currency then there's reward amount which is the amount of the reward asset that should be locked within the request um should be noted that reward amount should cover all of the value that's included in these calls plus whatever the gas cost would be for submitting the call to the destination chain plus an extra tip for the fulfiller and that extra tip is what acts as the incentive for the fulfiller to respond to the request then we have finality delay seconds this is uh the Gap after which or after the call is successfully fulfilled on the destination chain um that has to pass before the fulfill is allowed to claim their reward this is basically like destination chain reorg protection for the user theot like the higher this delay is uh the more likely the reorg chain or the destination chain won't be reorg um and ensure that the call was actually submitted and will stay submitted and then we have a nons value for ensuring that every request is unique as the way we're identifying these requests is by hashing this entire structure an expir field for uh when the request should expire this is relevant for for um the user being able to reclaim the reward if for whatever reason the call was never submitted to the destination chain um there should be a mechanism for the user to to recover those funds and that's what this xer Tim stamp is if the call is not submitted for the X time stamp then that's when the user can reclaim and then we have these last two fields for the pre-check contract so a pre-check contract address and an arbitrary bytes array of uh encoded data for that pre-check um this is for that optional like arbitrary fulfillment condition that should be true if this is the zero address that is depicting that we're not going to to use it and uh we're not going to be doing a pre-check step in today's demo but um figure it's worth covering that it's it's there anyways so then next up we'll do a quick walkr of the Inbox and outbox contracts so the outbox contract being the entry point to the system um we have one main function that we really care about here request cross chain call that's where the user would submit the request um and where the event would be admitted that the fulfillers are listening for then we have a claim reward function which is where the fulfiller comes to to claim their reward after successfully uh fulfilling the request and that calls into the pro contract here on this line and so this contract is again what we're about to implement uh it expected to revert if the proof fails uh so there there's no return value or anything here and then lastly the cancel request function so this is like after the exper Tim stamp if the reward has not been claimed yet then the user gets to to reclaim it on the destination chain side we have the inbox contract um the main piece of information we care about here is this fulfillment info struct so this is that execution receipt that should be created in storage and uh that will be the target of the nested storage proof validation um the whole point of the the the storage proof there is to prove that this struct exists in storage for the specific request and this is storing the time stamp at which the request was submitted as well as the filler address that should be able to claim the reward back on the um Source chain and that gets created during this fulfill function so we have all our validation steps up here and then uh we we route the calls here and if everything successful we're left with the created fulfillment info struct so this contract is fairly simple um something a little bit more interesting we have this State validator library and if you in the future are working on setting up a new prover contract for a new destination chain that's not currently supported by the proposal um you likely would be utilizing the State validator Library the whole point of this is to abstract a lot of the complexity involved with um storage proofs away from the developer um no need to reinvent the wheel every time um so the way that this this is set up there are two main functions that we care about there's validate State and then there's validate account storage validate state is for if you're starting from the beacon route on uh L1 and you're trying to um verify the like the L1 execution client State Route against that Beacon route um that's what validate state would do and then from there using that the the verified State Route it would then prove uh storage location for an account within that state because our example today is not using Beacon rootes we don't need to use this function but I figured I'd briefly cover it the function we care about is this validate account storage so this takes in an account which is a specific account within the network a state route for the network and then a handful of these account proof parameters and using these account proof parameters which are specified storage key an expected storage value and then an account proof and storage proof the account proof can be thought of as the path down the state try from the state route to the specific account we care about and then the storage proof would be thought of as like a path from that account's storage route to uh the St location that should that exists at storage key and should be storing storage value so it should be noted that all of the the values in um the state TR are keyed by the hash of the ethereum address of that account so that's what we're doing here we're uh deriving the account key and then using that we can do a merry. getet to uh return an encoded account an encoded account is basically an encoded array of the account metadata that I went through in the slides earlier that have the the kns the balance the storage route Etc so from that we can extract the storage route and then using the storage route we can verify that storage location using the storage proof that exists in the account proof progams struct that I just went through so at a high level that's how that's working um there's a directory in here called provs this is what we're about to implement so we'll be coming back to that in a second just real quick before we dive into the implementation details there um I want to do just a quick summary of what offchain services are running here the demo is the app that uh is facilitating the nft mints so we'll be implementing some details in this directory um but then these other two directories are surrounding services that are needed for the full system to run locally the Sinker is in charge of sharing State representations bidirectionally between the mack L1 and the mack l2s um and then the fulfill is the offchain agent that's listening for requests and will uh validate the request and ensure that the incentive uh is is enough to um to compensate them for their time and we'll respond accordingly and submit the request and then we'll be generating a full nested storage proof that gets validated against the contract we're about to implement and if that checks out we'll be able to see the fulfiller claiming its rewards in in real time so with all of that being said um we have enough here to dive into beginning to implement a pro contract um I have a handful of imports just set up in here already just to save the time from typing them out um you'll notice the first import is an iover interface uh so we can start here taking a look and what it defines is a single function and this is all the approver contract needs because this is the the function that the reward claim function from the outbox contract is going to hit so we can start by literally just copying this entire thing into um into our prover contract to initiate the implementation of that function so I will replace this comment with that function declaration and uh add empty curly braces here so oh and then we can extend that interface is I approver to take a quick skim through the comments here that are explaining what validate proof should even be doing um it validates storage proofs and verifies fulfillment okay makes sense it should revert if the storage proof is invalid also makes sense it should revert if fulfillment info is not found at inbox contract storage key on the specified inbox contract um that is kind of interesting here it should be noted that the the storage key like I was saying before is derivable from a network that doesn't actually have cont context of the destination chain and because of that this is being done in the um outbox contract before this function is hit so this is not coming from the offchain fulfiller we can trust this value lastly it should revert if the uh fulfillment info Tim stamp is less than the finality delay seconds amount of time from the current destination chain block Tim stamp so this is that that uh destination chain reor protection that I was mentioning earlier um we need to ensure that the finality delay second is not currently like still in progress then we have a note about that the implementation should vary by destination L2 this is due to the the lack of standardization around how l2s uh post their state representations to L1 like I was saying um and then a quick summary of the input parameters that we have to work with here so inbox contract storage key I just kind of mentioned is the storage location in the inbox contract on the destination chain where we expect the uh execution receipt to be next up is the Fulfillment info um struct so this is that uh exact execution uh receipt that should be existing at inbox contract storage key on the destination chains inbox contract then we have the initial request uh that came from the user and then we have an arbitrarily encoded proof data bytes array this is because of the fact that um like I was saying before the lack of standardization around the state the state posting um there could be subtle differences in the exact data that is needed for the prover to verify that state so there's no enforced structure to this data at the outbox level this is being implemented here within the prover so we'll start to set that up uh in just a moment okay so with all of that gone through we have enough here to start to set this up so we can think about uh what the steps are for validating a nested storage proof for starters we'll want to enforce a structure to proof data so we can decode this into some defined um struct that will we'll Define in storage up here so uh decode proof data next with the decoded proof data uh we can use some supplied data to uh trustless access the beacon route from L1 so I want to query L1 date representation in a real Network this likely would be or if the network is supporting EIP 4788 um this would be a beacon route for today's example it's the state route directly so I'll just make a brief comment explaining that um in real Network likely Beacon route in two days demo State rout okay so then step three um once we have a state representation for L1 um using L1 State Route we'll verify uh storage location on L1 and that storage location will be the destination chains rollup contract so verify storage location in DST chains rollup contract on L1 step four we'll need to verifiably link a destination chain state route to that uh oh yeah let's see using L1 State Route verified storage location this should be DST chain State Rep we'll need to use that verified value to link a state route for the destination chain um so we can do that as its own step here um verifi link to uh chain State Rep after that step we should have a verified State root for the destination chain so then we can essentially repeat step three again so this would be using L2 State root verify execution RIS seat in inbox contract on DST chain and then lastly the only step that we haven't covered is the uh this revert statement that's saying if finality delay seconds is still in progress this function should be reverting so we can check that as our final step whoops step six revert if finality delay seconds in progress okay so if we can successfully set up these six steps then we should be good to go to verify these nested storage proofs if we take a look up here you might have noticed that uh this contract is expecting to be deployed with an address in the Constructor this address is for the beacon Roots Oracle contract in a live Network you wouldn't have to do this um because EIP 4788 uh specif I a deterministic pre-compile address that you could just hard hard code into your contract storage but for this to work in both tests and deployed to our local network um I have it being deployed with the address specified here so as our first step we can store this as an immutable variable in storage so this will be address private immutable and then call it Beacon Roots Oracle something like that beon roots and then assign that within the Constructor and then if we start to think about how these steps are are working um the first step being that we want to decode proof data into some specified structure that we're going to Define we can start by defining a struct for what that structure should be so uh for this context we should call it um this will be a struct rip 7755 proof and the compiler is going to be mad about the struct being empty but we'll come back to that in a second we can set up this first step uh with that in place by decoding the proof data into a local variable that we can call proof that is um adhering to this RP 7755 proof struct so if we copy this paste it here this will be in memory is equal to ai. decode proof data and then pass in the name of the struct is the second argument here and then this will decode the proof data bytes into whatever structure we Define at the top of the file here so for step two we want to then query the L1 state representation um this is going to come from that Beacon Oracle contract from the rollups directory that I covered briefly so if we pull that up to take a look at the storage layout here of how exactly we should query that um this has a fallback function in here and that's to mimic the um the interface that would be used to query a beacon rout from like the real Beacon Roots Oracle contract on a live network uh and all this takes is an encoded block timestamp so that's actually the first piece of data that we need to add into our RP 7755 proof struct is we need to know the L1 block time stamp that we're um going to be using for the proof so we can add that in as a u 256 call it L1 timestamp and then using that we can set up a static call into the beacon Oracle contract again if you remember we have the address stored as the immutable variable here so we can take this copy that and under step two paste that that'll be uh yeah Beacon Roots Oracle you do static call which is like a lowl call but kind of like a view function where it's expected to not um mutate any state in the destination address that you're calling so uh and then we're passing in the encoded block Tim stamp for the L1 chain which we just added to proof so this should be available via ABI do encode with proof. L1 timestamp passed in whoops this static call will return a tuple where the first value is a Boolean so we can call that bu success and the second value is uh bytes array and memory so bytes memory data with any low-l call in an evm chain you need to confirm that success comes back is true because if something weird happens with this address or the static call fails for some unexpected reason um and success comes back false we need to uh ensure that we revert the transaction here so uh for that case we can add a custom error at the top of the file for if um the the static call fails we can call it error Beacon root Oracle call failed copy that and then underne this line we can do if not success revert with that custom error so if we get past this if statement then we have a returned um data bytes array where the data is representing an encoded um version of the state route for L1 so after this if statement we can then decode data into a bytes 32 stay rout so this would be bytes 32 L1 State root is equal to another ai. dcode with uh data passed in and then uh the data type is bytes 32 okay so that should do it for step two so at this point we have an L1 State root that we can then use in a storage proof to verify something about the state of L1 um and that exactly what step three is laying out here so using L1 state route we want to verify the storage location um in the destination chains rollup contract on L1 and this is going to be the state representation for the destination chain so in order to get some more context for how that should work uh let's take a look at the rollup contract because that's where the destination chain will be posting its state representation so that'll be inside of this rollup contract what's kind of interesting here is in a lot of live networks the exact storage location that the state representation is going to exist is not necessarily um known at the time of request um in that case the request just knows the storage slot maybe where the data structure is located so like in this example the mapping storing output routes exist at uh storage slot one so in this context the request specifying the um destination chains storage location for their rollup contract on L1 would just be specifying storage slot one and then we'd have to take that and derive uh the location of the value for the mapping based off of the destination chain L2 block timestamp so that brings us to the next piece of data that we need for this proof it's going to be the the block Tim stamp for the destination chain um so we can add that as another un 256 instead of the proof and this can be L2 block timestamp by the way for anyone following along try to stick to the exact names I have here for the um fulfiller proof to work properly um the logic by itself should should work fine either way but in order for this to be compatible with the the surrounding system we have to we have to use the correct names so if we have the L2 block timestamp here then we have enough to um Direct dve the storage location of the output route associated with that block Tim stamp inside of the rollup contract so how do we do that if we take a look down here so for step three we're going to be using the L1 State Route and this is the first example of an actual storage proof that we're going to use so this is where we would maybe want to take a look at that State validator Library again because this has a bunch of utility functions for um facilitating storage proofs directly be namely being this account proof parameter struct this is going to come in handy um and then that second function I mentioned for validating account storage so um we'll actually be using this function here this takes in an account a state rout and then a proof parameters that should be provided by the fulfiller so we can start to set that up now starting with State validator and then validate account storage my autocomplete added the names of the variable here so for the account what what's the account here this is the rollup contract for the destination chain and if you remember from whoops if you remember from the Str file that I walked through at the beginning one of the fields that is specified by the requester is this L2 Oracle address and this is exactly the account that we care about for this first storage proof so we can pass that in here um this is going to come from request which is being passed in so this will be request.

L2 Oracle for the account the state root is the L1 State rout that we just decoded here so we can use this for State root and then the account proof parameters uh this is this account proof parameter struct inside of the state validator this is going to be supplied by the offchain fulfiller so this is the next piece of information we need in our proof struct so if we copy this add it as a third argument in the proof struct or third field oops um this will be state validator do account proof parameters and we're going to call this one DST L2 State root proof or Rams so then we can copy that variable name and this will be the last argument passed into our validate account storage function so this will be proof Dot and then that copied field name if you take a look at the validate account storage function here you can see that it returns a Boolean value um so we need to ensure that the Boolean value returned is true so we can capture that in a local variable here uh we can call it bu um is valid L1 state is equal to the state validator validate account storage and then for when it's not a valid L1 State we can define a c error up here this will be error invalid L1 State copy that and then if not is valid L1 State we'll revert with that custom error and there we go as uh all right so what we have Happening Here is after this step we should have a verified um storage location in L1 to jog your memory on the prams that are being passed in by the fulfiller there is something kind of fishy happening with the way we currently have it set up these account proof parameters specify an exact storage location and expected value um so if this checks out and returns true it means we've successfully validated that location but what if that location's the wrong location like what if it's not the state representation for the destination chain that would be a problem so um this is where we'd want to override the storage key um we could either derive what it should be and confirm that it's equivalent or we could override it um for this example we we'll override it but uh maybe there's a a subtle gas optimization one way over the other um but for starters we'll want to create a helper function for deriving what the L1 storage key should be so we can create that down here as a private helper function derive L1 storage key this will be a private Pier returns bytes memory because as you see up here the storage key is a by string not a byes 32 or anything so um yeah so this is the storage key of where the state representation should be in the rollup contract on L1 so we can take a look at this rollup contract again to see uh how that storage layout is set up we have a mapping located at the first storage slot which likely is going to be the um inside of the Str file this likely is going to be the L2 Oracle storage key so I would expect this to be the bytes 32 representation of the number one um and then we can take that and hash that with the L2 block Tim stamp which under the hood is what solidity is doing to generate the storage location for the value that should be um keyed by the block timestamp so that's exactly what we can recreate here in order to do that we need a couple of pieces of information here one of them is the L2 Oracle storage key which comes from the request so we can pass in the request here copy this whole thing pass that in and then the other piece of information is the L2 block timestamp which if you'll remember we added to the uh proof struct up here so we can pass that in as well so if we copy this declaration we can paste that here as a second input argument and then so what does this return so this is going to return a derived value for the storage key where um the l2's output route should exist that is going to use so we'll return an ai. encode PA act with the block time stamp passed in so this is going to be proof. L2 block timestamp and then um the storage key location so then it's going to be request. L2 Oracle storage key so this concatenates them together into a single bytes array we now need to Hash this to generate the storage key so I'll wrap that whole thing in a catch 256 hash function and then one final step here because catch 256 returns a byes 32 and we need this to return a bytes memory we just have to wrap this in one more ai. encode and that should be all that we need to derive the storage location for the L1 storage key so now we can close these and then use this function to overwrite the storage key that gets passed into this validation step um so this will be proof.

DST L2 State root proof programs. storage key is equal to derive L1 storage key where we pass in uh the two input parameters of request and proof okay sweet so at this point we now have a verified value in the uh rollup contract for the destination chain on L1 um we've confirmed that it is in the correct location so we can trust that it's the proper state representation and then we can uh move on from there so the next step here is to verifiably link the destination chain State Route the destination chain state representation um if the destination chain is directly posting their state route this is UN necessary um we just need to make sure that we have a verified State Route here um so in this example because we're not ly posting that it's a hash of uh block time stamp and state route we need to recreate what this output route should be so um we can rederive that with a bytes 32 uh for a local variable byes 32 this will be uh derived L2 um output root is equal to KCK 256 and what gets passed in here we're basically just recreating this line over here in the rollup contract so this is going to be an ai. encod packed and inside of the ABI incoda we want to pass in the block timestamp and a state route but at this point we don't have a state route to use so that uh would be the perfect now is the perfect time to add that as the next next field in our RP 7755 proof struct um we would expect the fulfiller in this case to supply what the destination chains State Route is and then we can use that to rederive um the the state representation that was verified using the first storage proof and if they're equivalent then we can trust the past in L2 State Route so this will be a byes 32 L2 State Route and we need to pass pass these arguments in in the same order that they're passed in over here so we'd start with the L2 block time stamp this will be proof. L2 oops L2 block Tim stamp and then we'd want to pass in the state root so proof. L2 State root okay so now at this point we have a rederived uh output route for the destination chain in the case that this doesn't equal the value we just verified we'll create another custom error for that uh to revert in that case so we can call that an error uh invalid L2 State root copy that and then we need to compare this to the storage value that we verified in this step so that'll be um if derived L2 output rout does not equal proof.

DST L2 State root proof prams do storage value then we revert with that custom error and this is yelling at me because the storage value is a a byes string and this is a bytes 32 so uh because we'd be expecting them to be equivalent we can wrap this in a byes 32 for the type safe uh yeah properties of solidity and one other thing I'm noticing here this is kind of just like a personal preference for me but uh the State validator Library up here because we're using it on a specific account for solidi purposes we can actually bind that library to the account to just improve the legibility of this this line make it a little bit more succinct so I'm going to do that but that's totally just a personal preference so we would add a line at the top of the Prov contract that says using state validator for address and then what that allows us to do is to copy this request. L2 Oracle and remove it from the function call and then paste it here instead of state validator and then this is doing the same thing but it's just a little bit shorter okay so at this point we have a verified um destination chain store or state route now we can use that to basically redo step three but now the account we care about is the inbox contract on the destination chain so what that's going to look like is uh aou is valid L2 state is equal to um and then to jog your memory again on the structure of a request we actually have the inbox contract being defined by the user when they submit the request so this address is going to be the address that we're um verifying State against so this will be request do inbox contract Dot verify uh what's it called again oh validate account storage and in here we need to pass in a state root and another instance of the proof programs so the state root here is passed in from the proof struct and at this point we verified that this can be trusted so we'll um use that value so this will be proof. L2 State root and then we need to um add another instance of those proof prams this time for the inbox contract on the destination chain so we can duplicate this line and then instead of calling it DST L2 State root proof prams we can call it DST L2 account proof prams and then copy that field name down here for the second storage proof we can pass that in as the second argument so this looks like proof dot. destination account proof programs um field and then for the case again where is valid L2 State comes back false we need a custom error to to throw as a reversion in that case so we can Define that up here error invalid L2 State copy that and this will be so if not is valid L2 State revert with that custom error cool and then so if you remember from the first storage proof we had an issue trusting the storage key that gets passed in from the fulfiller um you can assume that we have the same issue in the second storage proof and we do luckily it's a little bit easier to solve on this side of things because um like I said at the beginning of the walkth through here the out the outbox contract red derives where that storage key location should be already and that gets passed in so we can just reassign the storage key value with this pass in inbox contract storage key uh we'll do that above step five so this will be proof. DST L2 account proof prams this time.

storage key is equal to inbox contract storage key and what that's going to do is ensure that we're uh verifying against the correct storage location where the execution receipt should exist on the inbox contract on the destination chain so at this point we have a fully verifiable proof um the last step is we just have to confirm that finality delay seconds is not still in progress we have uh the receipt being passed in here so we can use that for this check and for when it is still in progress we can add a custom error at the top this will be our last error so this will be uh error finality delay seconds in progress copy that and then down here at the bottom of this function if fulfillment info. timestamp plus request. finality delay seconds is greater than if you'll remember for uh the proof we defined the L2 block time stamp up here this is exactly what we need to use um for this time stamp based protection so we can do proof. L2 block timestamp if that if the time stamp at which it was submitted plus the uh configured finality delay seconds is greater than the time that we're using for this proof then we can't accept this proof because final finality delay seconds is still in progress so that's what this line is doing so we' revert with that new custom error finality delay seconds and progress and then cool that's um all of our steps but there is one final piece of uh data connection that we're forgetting here and that is stemming because of um well we' confirmed a specific storage location in the inbox contract on the destination chain and then we confirmed a past in receipt uh satisfies that finality delay seconds requirement but we did not confirm that that receipt that we're using for this final check is the same value as the one that we just verified so uh we need to make sure that the storage value for the second storage proof is equal to the encoded execution receipt that we're using for this timestamp um check in the last step and so that represents the final piece that we still have to set up to um secure this thing we can set up the encoding of the Fulfillment info struct as a separate private um helper function so this will be see this will be what do we want to call that encode fulfillment info okay so for encoding fulfillment info we need to pass in the uh struct that we're using for the validation up here and um inside of this it's a simple ai. encode packed so um return ai.

encode packed whoops with fulfillment info. filler and then fulfillment info. timestamp passed in this is because of the struct packing rules and um solidity storage we have to custom encode this struct here um if I show you the definition of the struct in the inbox contract again we see that it has two fields timestamp and filler the time stamp is a u 96 and the filler is an address so this can get packed into a single U 256 slot but the way that it gets packed or the ordering that it gets packed is in the order of the uh defined fields and it uh packs them into the lowest value bits first so the time stamp actually ends up on the right side of the storage slot and then the fillers on the left so in order to recreate that um alignment we have to uh use an ai. incode PCT here instead of uh where we pass in like the the fields in reverse order instead of just doing ai. code and passing in the entire struct we would have it in reverse order in that case so using this function now we can override the storage value for this final storage proof so this will be proof.

DST L2 account proof programs. storage value is equal to encode fulfillment info we pass in excellent so that should be our first pass at a full implementation for validating one of these nested storage proofs um we can now see if this is compiling so if we CD into the contracts directory can run a forge well I like to format it properly first and then we can do a forge build to check if it's compiling it is um so now to check this I have a test file in here with mock data from um from a working system it's commented out just to prevent compiler issues with the initial like structure of the project so we can uncomment this and run a Forge test to make sure that we did Implement that properly it'll recompile recompile this test file and then okay okay cool it's passing so this is using a previously generated proof um that adheres to the structure that we just set up in that R 7755 proof struct and um it uses is being proven against a specific storage route or a specific state route that is being assigned here in this commit Beacon root function in the beacon Oracle contract um so because this is passing it's a it's a good sign that our implementation is working properly so that wraps up the onchain implementation piece um at this point we should be good to take a step into the offchain world um and look at the integration for this nft minting um site so I'll close the contracts directory for now although we'll be back here shortly to reference um different function signatures inside of the services directory uh we care about this demo directory so if we take a look in here a brief walk through it's literally just like a backend like server script to run it's going to just tell you what it's doing um if you yeah it'll it'll display your current nft balance for the um for the nft contract that's going to get deployed we're we're calling it deployed on a mock arbitrum chain and then the user is going to be minting it from moach base and so it'll be displaying your nft balance on mock arbitrum your current eth balance on Mach base what the price is of the nft um which I think I have hardcoded as one e in the deployment script this is using local Anvil nodes so using um easy numbers is pretty easy uh and then lastly it'll prompt you for like once you hit enter it'll trigger minting the nft and then we can watch the fulfiller in action generating its proof after the fact um which gets uh verified again that Pro contract we just set up so there's not too much for us to change here this is just I just wanted to give you a rundown of uh what exactly it is that's happening what we care about is inside of the SRC directory there's a client file called clients. service um this is all in typescript if anyone's familiar there's a function called RP 7755 mint that is completely empty so this is what the uh developer needs here we have to set this up to be an RP P 7755 request um to be able to integrate with the cross chain call and um close the loop for this minting process so we can start here by setting up what this request is going to look like um to do that it would help to have the Str file up here as a reference um so I'll open that up because actually before we dive into that as a refresher in the outbox contract the function that we care about as the entry point is this request cross chain call which accepts one input argument and it's a cross chain request so really what we need to build here is a cross chain request with all the correct Fields um so that's what I I want to show you guys so to start with this implementation we can get a um an outline of what this request is going to look like as well as the the lowle call that we need it to do to facilitate so we can start with uh const calls it's going to be a it's a batch but it's it's just one call but it still needs to be set up as an array because it's uh uh meant to be able to support a batch of calls and then we'll have a const request which will be an empty object we can start by just getting the field names in here um I'm just going to assign them all as empty values to start just to motor through this and then we can uh go through each one individually to make sure that we're setting it up properly and um explain it all as we go so we need to add Prov contracts and again I'm just copying everything to try to prevent some silly typo mistake um so just bear with me for a second pass in the inbox contract L2 Oracle L2 Oracle storage key reward asset reward amount we initialize as zero finality delay seconds initialize a zero we'll come back through this in a second nons is zero xir zero and then the final two pre-check related fields PreCheck contract and pre-check data and then for the individual call that we want to set up here uh this just takes in three parameters there's two data and value so we can take two empty string data and then value okay so we have our structure outlined here now we need to make sure we set up each of these fields um properly and it helps to have an understanding of exactly what each field is which we now all should um in order to help with this I have inside of this common directory there's a constants file that has a couple of um chain configs in here one for Mock arbitrum and one for Mach base so this is going to be very um helpful for where we can pull the addresses from assuming everyone deploys the contracts using the deployment scripts that are provided uh these addresses are I mean they're they're deterministic so they're if they're deployed in the right order then these addresses should all be correct um which is why they're pre-populated in this in this file uh even though the contracts are not currently deployed on our local network so okay um let's start filling these out for calls the destination address here uh again what exactly is it that we're trying to do uh we want to Mint an nft so the two address is going to be the nft address which we have up here as mock arbitrum nft address so at the top of this client service file we can import that so this will be import mock arbitrum nft address from do do commmon constants and then we can copy that into our two field data is going to be the encoded call data for the mint function I'm going to leave that for the last step we'll come back to that in a second value um as you see here this function is being called with an address this is going to be the user address receiving the nft and then the mint price which is the mint price of the nft denominated in way so we can just pass in this mint price as the value directly um yeah so this will be mint price and then now as we start to fill out the request the requester is pretty self-explanatory like I said the address of the of the requester is being passed in so we can use that calls is going to be this calls array prover contract is what we just implemented this will be the ad the contract address on the source chain which in this context is is mock base um of the prover contract that has been implemented specifically to validate requests or validate State about the mock about the destination chain which in this context is mock arbitrum so if we look over here um there's only one defined in each but this is set up in a way that we could Define multiple Prov contracts for each chain um but this is pretty straightforward so we'll start with um well first we have to import the chain configs here so if we go back up to the top of the file we can import that from the same file that we just imported the nft address and then down here in our mint function we can destructure the mock arbitrum and mock base configs from uh that outer object that's containing the the chain configs so this would look like const Curly braces with Mock arbitrum and mock base is equal to chain configs and then now we can access all of these config Fields directly on Mock arbitr and mock base so for the pro contract because this is the base side of of the equation here this will be mock BAS doover contracts prover the destination chain ID is going to be mock arbitrum chain ID so we can go Mach arbitrum dochain ID inbox contract is going to be the address of the RP 7755 inbox contract on mock arbitrum so this will be mock arbitrum do contracts. inbox L2 Oracle is the rollup contract on L1 for mock arbitrum here so this will be moach arbitrum dol2 Oracle same thing with the storage key or the L2 Oracle storage key this is for the L2 Oracle contract for Mark arbitrum so we'll grab that from the same place this be Mach arbitrum L2 Oracle storage key and then for a reward asset for this example we're just going to use native currency because we're um sending native currency for the mint function but it doesn't necessarily have to be one to one like that um we could be passing in some erc20 and then expect the fulfillers to do the necessary conversions offchain to make sure that the rc20 is enough value to account for the native currency that's being passed in here but it's a little bit cleaner for the demo here to just keep it one to one like that um so uh that's what we're going to do the ERC that I mentioned earlier that had a spe specific uh asset that represents native currency is hardcoded here so that's what this like oxe address is and this is another exported constant from this constants file so we can copy that and add it to the import statement and then pass that in for reward address reward amount again to to remind you is meant to cover all of the value from the calls plus whatever the the destination chain gas cost is Plus a tip for the fulfiller um the exact mechanism to calculate what that Surplus should be can be pretty complicated um but for the sake of today's demo doing it in a simplified context if we add an extra 2% to the request that should be uh more than enough for it to be profitable for the fulfiller in our local network so we'll do it that way so this will be uh reward amount is going to be the mint price which is already in way to remind you we'll add a 2% buffer so we can do that with big ints in in typescript by multiplying it by something like 102 n for a big int and then dividing it by 100 this results in the mint price having 2% added to it all right so then for finality delay seconds in a live Network this will likely be something on the order of days to a week to ensure uh protection against reorgs on the destination chain because this is a self-contained system that is going to run locally on your machine we have the flexibility and the freedom to make this really short and because I want this to be a responsive demo and for us to see everything happening uh in real time we'll make it just 10 seconds um so what this is going to result in is like a 10 to 15c delay after the request is submitted before we'll see the fulfiller actually generate the proof and claim the reward nons doesn't matter because that's going to get overwritten in the outbox contract it's just like a canonically incrementing nons for every request expir doesn't really matter too much for this demo um we can if you see over here in the constants file we have a onee constant we can just add one week to the current block time stamp um we just need like whatever this value it is is it has to be greater than um the finality delay seconds in the future from like now um plus some extra cancellation buffer period which is hardcoded as a constant in the outbox contract storage as a full day so um for the demo just to get a valid value in here one week should be more than enough so this will be date. now so in JavaScript if you're not familiar it has a global date object that if you do date.

now will return the Unix Tim stamp but in milliseconds and in solidity it's in seconds so we have to divide that by a th but solidity doesn't like decimals so then we have to floor that to the nearest integer and then from here we can add the onee constant so we'll add that as another import from this constants file and then use it down here for the XPR timestamp we'll add one week okay and then the last two fields are this um pre-check contract and data which like I said earlier is an optional like fulfillment uh condition that the requester wants to be true uh because it's optional we're not going to be using it today but um yeah so in order to not use it we have to pass in the zero address and there's a a helpful constant from uh the web3 library I'm using theme um that is just called the zero address so we can import that at the top and then use that as the pre-check contract address in here and then last but not least pre-check data in order for it to pass the offchain validation that vhm does we just have to add an ox but this can be anything it just has to be some kind of arbitrary bite string because we're not using this step it doesn't really matter okay so that just about covers all of the requests the only thing we haven't done yet is encoded the call data for um the the mint function on the nft contract so to uh reference the nft contract let me pull that up it's just a simple Min function that takes one input argument um which is the two address that should be receiving the nft uh there's another helpful function from VM called encode function data that we can use for encoding the call data here um we can Define this as another local variable above the calls constant so this will be const encoded call data is equal to encode function data and this takes in actually before we Define that we'll just add this in as the value for data encoded call data um and then now for encode function data this accepts one input parameter which is an object with a couple of fields that are necessary here the first one being uh the ABI for this nft contract which is actually already being imported uh as nft ABI for some of the other help help functions that are defined in this class so uh we can just use that so this will be ABI colon nft ABI I don't know why I closed that contract we still need it next up we need to define the function name that we're encoding data for so that's just simply mint so if we copy that this field name is function name with mint passed in and then because mint accepts one input parameter we now have to specify that with a field name called args so this will be args which is an array of the input parameters and in this context it's just the two address which is passed in here as address and that should be it for encoding call data all right so at this point we have a uh fully set up RP 7755 request um now we need to set up the function call to submit the request so what does that look like to give you a little bit more context on how this class is set up we have uh something called a wallet client in like as a local variable here and if you're unfamiliar with VH as a web 3 Library it uses things called public clients and wallet clients the public client is for like reading state from the chain and wallet client is for writing state to the chain uh this wallet client is already defined in the Constructor so we can just use it as is for um the target chain that we are submitting the request to which is mock base here so this is going to be an asynchronous call so we'll start it with a weight this. wallet client and the function name that we care about here is right to contract so under the hood this this creates your um your your your transaction and assigns it and and sends it to the network this will return a transaction hash if it's successful so we can store that as hash and then we need to set up the configuration for like what contract we writing to what function we're writing to what parameters are needed um so that can be a single um object in here where we Define for reference I'm going to pull up the constants file again we need to define the address that we're sending the transaction to so address this is going to be coming from mock base because we're submitting the request to mock base uh the the contract we care about is the outbox contract for the standard so this will be mock base. contracts. outbox next we need to specify the ABI for the outbox so this can be we're going to have to import this at the top so this will be import outbox ABI from do do slabi outbox we already have that populated in this um directory so we can Define that as the ABI here we then need to define the function name that we're requesting or sending the transaction to um so if we pull up the rip 7755 outbox contract over here the entry point as I said earlier for the U standard is this request cross chain call function so we can copy that into function name we then need to define the input args so um as you see here it's just one argument uh which is the cross chain request this is going to be um the request that we just defined here and then last but not least we need to Define um any value that should be submitted with this with this transaction so that's going to be a field called value and in this context it's not actually going to be mint price because if you remember we added a 2% buffer to the mint price as the reward amount for this um cross chain call so the value here should be request. reward amount to account for that buffer and that should be it for setting up the the contract send um the last piece here is using a public client from vhim we can um wait for a transaction receipt to make sure that the request is confirmed or the transaction is confirmed so we can do that with another await call um in the Constance file for the chain configurations one of the fields for Mach base is a public client so we can use that directly so this will be await mock base.

public client dot um the function name that we're going to use here is called wait for transaction receipt and that takes in just one input object which is uh the transaction hash and in JavaScript if the field name and the the value name that you're setting it as are the same name you can omit the colon and the the value so like this is the same thing as this um so I'll leave it like this for clarity and if we get through this wait for transaction receipt call then the transaction should be successful so then we'll log something console.log um transaction success okay so that should be our full request um it's pretty straightforward I just wanted to kind of give you a walk through on all the different field names and how they should be assigned and um what the values mean and like how they're fitting together um this should be enough in place to run the system end to end so if we take a look in the root of the directory uh this the root read me here there's a list of commands for spinning up all of these all the infra here to deploy the contracts on top of and all of the services that are needed to get everything to work together so um we can start by running these commands the first thing we need to do is spin up our local chains so if we open new terminals that are each at the root of the project we can open three in split screen side by side and this is going to be meant to represent the the mackl one and then the two Mach L2 chains so we can start that with um there's a make file defining all these commands if you want to see what they're actually doing under the hood we can run make start mock L1 in the first terminal so this is like our mock L1 chain we can then run make start mock base this will be our mock base chain in the middle and then lastly we can do make start mock arbitrum to run our mock arbitrum chain in the terminal on the right here so now with these three chains running we have um we have our our local chains running so we can deploy our contracts on top of them there's another make file command set up for deploying the contracts in the correct order that is required by the offchain services here so we can open a new terminal um the three blockchains are still running in the background here um I just have a new terminal running on top of them we can run make setup contracts and that's going to compile and deploy all of the contracts to the local network so that'll just take a second while that's going we can copy the make start um Sinker command so what this is going to do is start the offchain service for sharing State representations bidirectionally between the m l one and the mackl 2s uh and this one's pretty chatty so we can now open a new terminal for the final two terminals that are needed here there's a ton of terminals I know um we need to start the offchain fulfiller to listen to in response to requests uh so that's make start fulfiller so now we have the fulfiller listening we can do a split screen here to now run the demo so to run the demo app and mint the nft we would just run make demo so if we type that in not too pretty but logs to the console uh what it what it's what it's doing here current state so we have welcome to the demo uh mint your nft the Devcon nft on mock arbitrum we currently have zero in um in the wallet address that's being used for the demo here the price is 1 e and the current base balance is almost 10,000 eth because this is uh one of the default um accounts on the local Anvil nodes so now if we press enter this is going to send that mint request to the local network and we can see if everything worked successfully and it did so the transaction went through we actually if we run this again we already have the current nft on the destination chain because the fulfiller if you saw over here picked up on the request almost right away and submitted it because it validated that the um incentive was sufficient and then now in a second we should see something else happen um because the finality delay of 10 seconds has gone past and uh what the fulfiller just did is it picked up on the fact that it waited long enough and it's now allowed to claim the reward for that request and it generated this massive storage proof here and submitted that storage proof to the outbox contract on the mock base chain and everything was successful so this was validated against the prover contract that we just implemented um and so if you take a look in the fulfiller directory here here um after that ran it logged the proof into a Json file so you can see exactly what the proof was that it used to um verify that the call was submitted so that's what this file is and then inside of the SRC directory there's a database directory that is storing uh db. Json which this is the representation of the request that it picked up on um that we just submitted and then it has a rewards file that it's tracking how many how much eat that it's claimed in Rewards so we have a locally running like fully working system uh end to end wo um yeah and I'm realizing right now that the reward tracking is not accounting for gas cost on the destination chain but um nonetheless you get the you get the idea so that just about does it um if you take a look here yeah like I said uh if we run the demo again we now see that the current nft balance is one because the the nft was actually minted on the destination chain as uh defined by the encoded call data that we set up for the Target calls um and then so if we run it again now it would be just incrementing from there so that does it for the demo thank you everyone for coming and I'll be hanging around for a little bit if anyone has questions or if you want to chat um the standard a little bit more like I said we have an open source repo for proof of concept here so if anyone feels compelled to contribute we fully invite you to uh contribute so thank [Applause] you for

Automatic transcript — names and jargon may be misspelled.