Starknet: An NFT contract with Nile and L1 <-> L2 reputation messaging
TLDR
- Starknet employs an account-based model and a distinct L1–L2 messaging architecture to enable cross-chain interactions with Ethereum.
- This tutorial walks through deploying a Starknet-based NFT contract (with a reputation system) and a corresponding L1 contract on Ethereum Sepolia.
- You’ll mint NFTs on L2, send messages from L2 to L1 (to withdraw reputation points), and send messages back from L1 to L2 (to raise an NFT’s reputation).
- Tools used include Cairo, OpenZeppelin’s Nile, the Starknet CLI, Remix, and MetaMask (for L1) plus ArgentX (for L2).
Main article
The objective of this tutorial is twofold:
- Get you familiar with the foundational Starknet concepts: the account model and the L1 <-> L2 messaging.
- Do a hands-on walkthrough with the tooling available on Starknet, the smart contracts, and the communication between the L1 Ethereum network and the L2 Starknet network.
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.
Prerequisites
- Chainstack account to deploy an Ethereum Sepolia node and a Starknet testnet node.
- Cairo to compile Starknet contracts and to interact with the network through the Starknet CLI.
- OpenZeppelin Nile to create and deploy an NFT contract.
- OpenZeppelin Cairo contracts to use the ERC-721 contract library in Cairo.
- MetaMask to deploy the L1 contract and interact with the contract.
- ArgentX to interact with the L2 NFT contract.
Overview
To get from zero to a deployed NFT contract with a reputation system on L2 and its counterpart on L1, do the following:
With Chainstack, create a .
With Chainstack, deploy an Ethereum Sepolia testnet node.
With Chainstack, deploy a Starknet testnet node.
With Chainstack, access your Ethereum Sepolia testnet node credentials and your Starknet testnet node credentials.
Set up your MetaMask with the Ethereum Sepolia testnet node.
Deploy the L1 contract on the Ethereum Sepolia testnet and verify the contract.
With the Starknet CLI, deploy an account on the Starknet testnet.
With Nile, deploy the L2 NFT contract on the Starknet network.
Mint an NFT on L2.
Increase and decrease the reputation of the minted NFT on L2.
On L1, withdraw the reputation points of the minted L2 NFT.
On L1, increase the reputation of the minted L2 NFT.
Step-by-step
Create a public chain project
See Create a project.
Join the Ethereum Sepolia and Starknet testnets
For the L1 <-> L2 messaging, you need access to both an Ethereum network and a Starknet network. See Join a public network.
Get the node access and credentials
See View node access and credentials.
Set up MetaMask
You will deploy the L1 contract with Remix through MetaMask.
To set up, see Ethereum tools: MetaMask.
Set up an account on Starknet
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.
Set up the L2 NFT contract
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:
- Consume messages from your L2 contract through the Starknet L1 core contract.
- Send messages to your L2 contract.
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:
- The NFT part of the contract is based on the OpenZeppelin ERC721 Mintable Burnable library.
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.
Set up and 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:
- The contract has the interface to communicate with the L1 Starknet core contract.
- The constructor of the contract takes the L1 Starknet core contract address.
- The
withdrawToIdle
external function withdraws the NFT reputation points sent to L1 using the L2rep_down
function. Mechanically, once you hit the L2rep_down
function, the L2 state is pushed as a message into the L1 Starknet core contract. Therep_down
message then sits in the L1 Starknet core contract until it gets consumed by triggering thewithdrawToIdle
function in your L1 contract. - The contract has the
repUp
external function of the L2 contract computed forREPUP_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:
- In Environment, select Injected Provider - Metamask.
- In Contract, select
L1L2ERC721Rep
. - In Deploy, provide
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:
- Find the contract by the address and click Contract > Verify and Publish.
- For Compiler Type, select Solidity (Single file).
- For Compiler Version, select v0.8.7.
- For Open Source License Type, select No License (None).
- For Optimization, select No.
- In the Solidity Contract Code field, paste the entirety of your
L1L2ERC721Rep.sol
contract. - Click Verify and Publish.
This will verify the contract and turn it into a web app that you can interact with through MetaMask.
Deploy the L2 NFT contract
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:
- CONTRACT_NAME — the name of the contract, which is
NFT_with_reputation
for this tutorial. - NFT_NAME_INT — Nile accepts only integers for the deployment constructor. Provide any NFT collection name in integer format. See below for the conversion script.
- NFT_TICKER_INT — Any NFT collection ticker in integer format.
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.
Interact with the L2 contract
Mint an NFT:
- Use Voyager to open your deployed contract.
- Click Write Contract.
- Click Connect ArgentX Wallet and make sure you connect with the account address that you provided as the owner when deploying the contract.
- In
mint
, provide a deployed account to mint an NFT to in theto
field and any ID intokenId
. This will be the ID of the minted NFT that you will assign the reputation points to. - Click Transact.
Your NFT is now minted on L2.
L2 -> L1 messaging
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:
- Starknet core mainnet: 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4
- Starknet core testnet: 0xde29d060D45901Fb19ED6C6e959EB22d8626708e
Now that you have an NFT minted on L2, assign reputation points to it:
- Use Voyager to open your deployed contract.
- Click Write Contract.
- In
rep_up
, provide the ID of your minted NFT innftId
and any integer for reputation points inpoints
. For example, assign1500
. - Click Transact.
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.
- Use Voyager to open your deployed contract.
- Click Write Contract.
- In
rep_down
, provide the ID of your minted NFT innftId
and any integer for reputation points inpoints
. For example, remove700
points. - Click Transact.
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.
L1 -> L2 messaging
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:
Use Etherscan to open your contract.
Click Write Contract > Connect to Web3.
In withdrawToIdle
, provide:
l2ContractAddress
— the address of your NFT contract deployed on L2nftId
— the ID of the minted NFT. For this tutorial, it is1
.points
— the number of reputation points you removed from the NFT contract on L2. For this tutorial, it is700
.
Click Write.
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:
Use Etherscan to open your contract.
Click Write Contract > Connect to Web3.
In repUp
, provide:
l2ContractAddress
— the address of your NFT contract deployed on L2nftId
— the ID of the minted NFT. For this tutorial, it is1
.points
— the number of reputation points you want to put back in the NFT reputation on the L2 contract. For this tutorial, it is300
.
Click Write.
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.
Conclusion
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.