[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
so
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
fulfillment info
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
pre-check
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