DEV Community

Cover image for Using OpenZeppelin in StarkNet contracts
Aditya41205
Aditya41205

Posted on

3 1 1 1

Using OpenZeppelin in StarkNet contracts

Introduction

StarkNet is rapidly becoming a go-to Layer 2 scaling solution for Ethereum, bringing massive scalability and low fees to decentralized applications. At its core, StarkNet uses Cairo — a powerful new language designed specifically for writing provable, efficient smart contracts.

But writing smart contracts from scratch is time-consuming and error-prone. That’s where OpenZeppelin comes in. OpenZeppelin is widely trusted for providing battle-tested, reusable contract components on Ethereum. Now, OpenZeppelin is bringing the same security and modularity to StarkNet with a growing set of Cairo contracts.

In this post, I’ll show you how to build an ERC20 token on StarkNet by leveraging OpenZeppelin’s Cairo components. We’ll walk through an example contract step-by-step, highlighting key StarkNet concepts along the way.

Why Use OpenZeppelin on StarkNet?

OpenZeppelin contracts help you:

Save Development Time: You don’t need to reinvent the wheel or debug low-level logic like token transfers or storage management.

Increase Security: The code is audited and battle-tested by a huge community.

Follow Best Practices: OpenZeppelin enforces standards, reducing subtle bugs and ensuring interoperability.

Write Modular Code: Components can be composed and extended easily, which fits perfectly with StarkNet’s contract architecture.

Code Breakdown: Your ERC20 Token Contract

Here’s a simplified example of an ERC20 token contract that uses OpenZeppelin Cairo components:

You should include openzeppelin in Scarb.toml.

[dependencies]
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git" }
Enter fullscreen mode Exit fullscreen mode

Contract:

#[starknet::interface]
trait IERC<ContractState> {

fn minting(
        ref self: ContractState,
        recipient: starknet::ContractAddress,
        amount: u256
    );
}
#[starknet::contract]
mod MyERC20Token {
    use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
    use starknet::ContractAddress;
    component!(path: ERC20Component, storage: erc20, event: ERC20Event);
    #[abi(embed_v0)]
    impl ERC20MixinImpl = ERC20Component::ERC20Impl<ContractState>;

    impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
    #[storage]
    struct Storage {
        #[substorage(v0)]
        erc20: ERC20Component::Storage,
    }
    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        #[flat]
        ERC20Event: ERC20Component::Event,
    }
    #[constructor]
    fn constructor(
        ref self: ContractState,
        fixed_supply: u256,
        recipient: ContractAddress
    ) {
        let name = "Test";
        let symbol = "TST";
        self.erc20.initializer(name, symbol);
        self.erc20.mint(recipient, fixed_supply);
    }
    #[abi(embed_v0)]
    impl t of super::IERC<ContractState> {
        fn minting(
            ref self: ContractState,
            recipient: ContractAddress,
            amount: u256
        ) {
            self.erc20.mint(recipient, amount);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Breaking Down the Contract

Defining the Interface IERC
The IERC trait defines an interface for minting tokens:

#[starknet::interface]
trait IERC<ContractState> {

    fn minting(
        ref self: ContractState,
        recipient: starknet::ContractAddress,
        amount: u256
    );
}
Enter fullscreen mode Exit fullscreen mode

The minting function lets external callers mint tokens to a specified recipient address.
The #[abi(embed_v0)] macro generates ABI code so this function can be called externally.

Contract Module and Imports

Inside the MyERC20Token module, we import OpenZeppelin’s ERC20 component and StarkNet address utilities:

use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};

use starknet::ContractAddress;
Enter fullscreen mode Exit fullscreen mode

Composing with component!

component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Enter fullscreen mode Exit fullscreen mode

This macro pulls in OpenZeppelin’s ERC20 logic, storage, and events into your contract under the name erc20.
It helps modularize your contract by reusing battle-tested components.

Storage Definition

#[storage]
struct Storage {
    #[substorage(v0)]
    erc20: ERC20Component::Storage,
}
Enter fullscreen mode Exit fullscreen mode

The contract’s storage embeds OpenZeppelin’s ERC20 storage.
The #[substorage(v0)] attribute allows versioned, modular storage management.

Event Wrapping

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
    #[flat]
    ERC20Event: ERC20Component::Event,
}
Enter fullscreen mode Exit fullscreen mode

Your contract wraps OpenZeppelin’s ERC20 events so it can emit them properly on StarkNet.

Constructor: Initialize and Mint

#[constructor]
fn constructor(
    ref self: ContractState,
    fixed_supply: u256,
    recipient: ContractAddress
) {
    let name = "Test";
    let symbol = "TST";
    self.erc20.initializer(name, symbol);
    self.erc20.mint(recipient, fixed_supply);
}
Enter fullscreen mode Exit fullscreen mode

On deployment, the contract sets the token name and symbol.
It immediately mints the entire fixed_supply to the recipient address.

Minting Implementation

#[abi(embed_v0)]
impl t of super::IERC<ContractState> {
    fn minting(
        ref self: ContractState,
        recipient: ContractAddress,
        amount: u256
    ) {
        self.erc20.mint(recipient, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

This implements the minting method from the IERC interface.
It calls the OpenZeppelin mint function to mint tokens after deployment.

Deploying and Interacting with the Contract

To deploy:
Compile the contract with the Cairo compiler targeting StarkNet.

Deploy the contract to StarkNet testnet or a local devnet.
Call the constructor with your fixed supply and recipient address.

To interact:

Use the minting function to mint additional tokens to any address after deployment.

Use standard ERC20 calls (transfer, approve, balanceOf) inherited from OpenZeppelin’s ERC20 component.

Conclusion

Using OpenZeppelin’s Cairo components on StarkNet significantly speeds up development of secure, standard-compliant smart contracts. This example shows how easily you can compose and extend OpenZeppelin’s battle-tested ERC20 token implementation on StarkNet.

If you want to build robust Layer 2 dApps, leveraging OpenZeppelin on StarkNet is a smart choice. Feel free to check out the OpenZeppelin Cairo repository and experiment with the components.

Drop a comment if you have questions or want more tutorials on StarkNet Cairo smart contracts!

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Tiger Data image

🐯 🚀 Timescale is now TigerData: Building the Modern PostgreSQL for the Analytical and Agentic Era

We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future.

So we’re changing our name: from Timescale to TigerData. Not to change who we are, but to reflect who we’ve become. TigerData is bold, fast, and built to power the next era of software.

Read more

👋 Kindness is contagious

Explore this insightful write-up, celebrated by our thriving DEV Community. Developers everywhere are invited to contribute and elevate our shared expertise.

A simple "thank you" can brighten someone’s day—leave your appreciation in the comments!

On DEV, knowledge-sharing fuels our progress and strengthens our community ties. Found this useful? A quick thank you to the author makes all the difference.

Okay