How to Use OpenZeppelin Contracts in StarkNet with Cairo: A Practical Example
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" } Contract: #[starknet::interface] trait IERC { 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; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[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 { fn minting( ref self: ContractState, recipient: ContractAddress, amount: u256 ) { self.erc20.mint(recipient, amount); } } } Breaking Down the Contract Defining the Interface IERC The IERC trait defines an interface for minting tokens: #[starknet::interface] trait IERC { fn minting( ref self: ContractState, recipient: starknet::ContractAddress, amount: u256 ); } 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; Composing with component! component!(path: ERC20Component, storage: erc20, event: ERC20Event); 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, } 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, } 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); } 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 { fn minting( ref self: ContractState, rec

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" }
Contract:
#[starknet::interface]
trait IERC {
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;
impl ERC20InternalImpl = ERC20Component::InternalImpl;
#[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 {
fn minting(
ref self: ContractState,
recipient: ContractAddress,
amount: u256
) {
self.erc20.mint(recipient, amount);
}
}
}
Breaking Down the Contract
Defining the Interface IERC
The IERC trait defines an interface for minting tokens:
#[starknet::interface]
trait IERC {
fn minting(
ref self: ContractState,
recipient: starknet::ContractAddress,
amount: u256
);
}
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;
Composing with component!
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
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,
}
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,
}
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);
}
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 {
fn minting(
ref self: ContractState,
recipient: ContractAddress,
amount: u256
) {
self.erc20.mint(recipient, amount);
}
}
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!