TLDR:
The objective of this tutorial is twofold:
For the tutorial, you will implement an NFT contract with a simple reputation points system. You will run the NFT contract on the L2 Starknet testnet, and you will be able to interact with the L2 reputation system through the L1 Ethereum Sepolia testnet.
To get from zero to a deployed NFT contract with a reputation system on L2 and its counterpart on L1, do the following:
See Create a project.
For the L1 <-> L2 messaging, you need access to both an Ethereum network and a Starknet network. See Join a public network.
See View node access and credentials.
You will deploy the L1 contract with Remix through MetaMask.
To set up, see Ethereum tools: MetaMask.
Unlike on other EVM-based networks, accounts on Starknet are abstract and decoupled from the signer. Accounts are contracts and can contain any code—for example, an account can be multi-signature or have any other mechanisms built in.
For this tutorial, you will deploy the standard account that comes with the Starknet CLI and is based on the OpenZeppelin account contract implementation.
To deploy an account:
Fund the deployed account with L2 Goerli ETH.
There will be three contracts in total to do the L1 <-> L2 messaging for this tutorial:
Starknet L1 core contract — the default Starknet contract deployed on L1. See the official core contract addresses.
Starknet L2 NFT contract — your tutorial contract in Cairo that you will deploy on the Starknet testnet. This contract will communicate with your Ethereum L1 contract.
Ethereum L1 contract — your tutorial contract in Solidity that you will deploy on the Ethereum Sepolia testnet. This contract will:
You are going to use the OpenZeppelin ERC721 Mintable Burnable contract and modify it to add the L1 <-> L2 reputation messaging.
Initialize the project with Nile:
Install the OpenZeppelin contract libraries:
In the contracts/
directory, create a contract called NFT_with_reputation.cairo
:
If you were going to just create a simple NFT contract on the Starknet network, this would have been enough. But you are going to add to the NFT contract a reputation system that will communicate with the L1 contract.
Update your NFT_with_reputation.cairo
contract with the following code:
where L1_MESSAGING_CONTRACT is the address of the L1 contract that you will deploy later.
The contract implementation is the following:
nftId
is used to identify the NFT token ID when it is minted.rep_up
can be called by any contract on L2 to increase the reputation of the NFT.rep_down
can be called by any contract on L2 to decrease the reputation of the NFT and send the reputation points as a message to the L1 contract.get_rep
can be called by any contract on L2 to get the current reputation of the NFT.l1_handler
processes the message from the L1 contract.You will deploy the L2 contract after you deploy the L1 contract.
The L1 contract will communicate with your L2 contract through the L1 Starknet core contract.
In Remix, create the L1L2ERC721Rep.sol
contract:
The contract implementation is the following:
withdrawToIdle
external function withdraws the NFT reputation points sent to L1 using the L2 rep_down
function. Mechanically, once you hit the L2 rep_down
function, the L2 state is pushed as a message into the L1 Starknet core contract. The rep_down
message then sits in the L1 Starknet core contract until it gets consumed by triggering the withdrawToIdle
function in your L1 contract.repUp
external function of the L2 contract computed for REPUP_SELECTOR
. To compute:Create a Python script with the function name (repUp
):
Run the script.
The reputation points withdrawn to an account through withdrawToIdle can then be used in the L1 repUp
function to send a message to the L2 NFT contract to increase the NFT reputation.
If this seems a little complicated at this point, do not worry—just keep following the tutorial and you will have a full hands-on walkthrough, which will help understand each of the steps better.
In Remix, compile the L1L2ERC721Rep.sol
contract.
In Remix, on the deployment tab:
L1L2ERC721Rep
.0xde29d060D45901Fb19ED6C6e959EB22d8626708e
. This is the address of the L1 Starknet core contract on the Ethereum Sepolia testnet.Clicking Deploy will engage your MetaMask instance connected to the Ethereum Sepolia testnet and deploy the contract.
Verify the deployed contract on Etherscan:
L1L2ERC721Rep.sol
contract.This will verify the contract and turn it into a web app that you can interact with through MetaMask.
Now that you have the address of your L1 contract, swap L1_MESSAGING_CONTRACT
in the L2 contract code that you have prepared with the actual L1 contract address.
In your Nile project directory, compile the contract:
Deploy the contract:
where:
NFT_with_reputation
for this tutorial.To convert a string to an integer, run the following Python script:
Example:
Example of deploying the contract with the name NFTwithRep
, ticker NFTRP
, and owner 0x02a152cb1a753a858c950bb710d957e7f3f78d87435831e7fae394f9233e60fa
:
You can check the deployment status with the starknet tx_status --hash HASH --network=alpha-sepolia
command.
Example:
When you get the ACCEPTED_ON_L2
status, the contract is running on the Starknet network.
If you get the REJECTED
status, reattempt the transaction and up the suggested network fee when prompted by ArgentX.
Mint an NFT:
mint
, provide a deployed account to mint an NFT to in the to
field and any ID in tokenId
. This will be the ID of the minted NFT that you will assign the reputation points to.Your NFT is now minted on L2.
Sending a message from L2 to L1 takes much longer than sending a message from L1 to L2.
When sending a message from L2 to L1, Starknet puts messages in a batch and pushes the state onto L1 at different time intervals. It can take 30 minutes for your message to arrive on L1 on testnet.
You can watch state updates live as transactions to the L1 Starknet core contracts:
Now that you have an NFT minted on L2, assign reputation points to it:
rep_up
, provide the ID of your minted NFT in nftId
and any integer for reputation points in points
. For example, assign 1500
.Once the transaction is accepted on L2, you can check the assigned reputation points on Read Contract > get_rep
.
Now that you have the points assigned, you will decrease the reputation partially and send the reputation points to L1.
rep_down
, provide the ID of your minted NFT in nftId
and any integer for reputation points in points
. For example, remove 700
points.And now you wait. Your transaction will go through the following states in the specified order:
RECEIVED
— the transaction is received by the Starknet node.PENDING
– the transaction is valid and is in a pending block.ACCEPTED_ON_L2
– the transaction is in an accepted block on L2.ACCEPTED_ON_L1
— the transaction is pushed onto L2 as a message and ready to be consumed on L1.To go from RECEIVED
to ACCEPTED_ON_L1
will take about 30 minutes on average on the testnet and 2 hours on the mainnet.
You can check the deployment status with the starknet tx_status --hash HASH --network=alpha-sepolia
command.
Example of an L2 transaction sending a message to L1 with nftId
: 1
and rep_down
: 700
: 0x6f753d8249a1f94d6ede18e34717cddf35936c1bcce329be599ad6c1ca7afaf.
Example of the message arriving on L1 (#89): 0x56a8e663d0e607fc6eb74b6b7bb713c5acdc262130e7790a8b252c192b41fcc9.
You now have the message in the Starknet core contract on L1 waiting to be consumed by triggering your L1 contract.
Sending a message from L1 to L2 is much faster than sending a message from L2 to L1.
The Starknet network will receive the L1 -> L2 message after a few block confirmations on the L1 network.
First, consume the message on L1:
withdrawToIdle
, provide:
l2ContractAddress
— the address of your NFT contract deployed on L2nftId
— the ID of the minted NFT. For this tutorial, it is 1
.points
— the number of reputation points you removed from the NFT contract on L2. For this tutorial, it is 700
.This will withdraw the reputation points to the idle state—not assigned to the NFT on L2. This constitutes consuming the L2 message on L1.
Now send the L1 -> L2 message—for this tutorial, it is putting the idle reputation points back on L2 for the NFT reputation by calling repUp
on L1.
First, check the current reputation points of the NFT on L2 by calling get_rep
on the L2 contract. If you are following the example numbers in this tutorial, you should get the reputation of 800
for nftId
: 1
on L2.
Now up the reputation by 300
points by sending a message from L1 to L2:
repUp
, provide:
l2ContractAddress
— the address of your NFT contract deployed on L2nftId
— the ID of the minted NFT. For this tutorial, it is 1
.points
— the number of reputation points you want to put back in the NFT reputation on the L2 contract. For this tutorial, it is 300
.After a few blocks on L1, call get_rep
on the L2 contract. You should get the reputation of 1100
for nftId
: 1
on L2.
Congratulations! You have completed the full L1 <-> L2 messaging exercise on Starknet.
This tutorial guided you through the basics of operating on Starknet, using tooling like Nile and OpenZeppelin, implementing your own L1 <-> L2 messaging protocol on the contract level, and actually sending the L1 <-> L2 messages.
As part of the tutorial, you also created an NFT contract that implements a basic reputation system for each of the minted tokens.
This tutorial uses testnet, however, the exact same instructions and sequence work on the mainnet.