starknet/cairo-dev-guide

Starknet Cairo Development Guide

starknetguide👥 Communityconfidence highhealth 100%
v1.0.0·Updated 3/20/2026

Why Starknet?

  • ZK Rollup: All transactions are verified by STARK proofs — security comes from math, not game theory
  • Native AA: All accounts are contract accounts (no EOA concept)
  • Cairo VM: Designed specifically for verifiable computation, independent of the EVM
  • Low gas: Compute-intensive tasks are 10-100x cheaper than Ethereum

Cairo Language Basics

// Cairo 2.x syntax (Rust-like)
use starknet::ContractAddress;

#[starknet::contract]
mod ERC20 {
  use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map};

  #[storage]
  struct Storage {
    name: ByteArray,
    total_supply: u256,
    balances: Map<ContractAddress, u256>,
    allowances: Map<(ContractAddress, ContractAddress), u256>,
  }

  #[event]
  #[derive(Drop, starknet::Event)]
  enum Event {
    Transfer: Transfer,
  }

  #[derive(Drop, starknet::Event)]
  struct Transfer {
    #[key]
    from: ContractAddress,
    #[key]
    to: ContractAddress,
    value: u256,
  }

  #[abi(embed_v0)]
  impl ERC20Impl of super::IERC20<ContractState> {
    fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
      self.balances.read(account)
    }

    fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
      let sender = starknet::get_caller_address();
      let sender_balance = self.balances.read(sender);
      assert(sender_balance >= amount, 'Insufficient balance');
      
      self.balances.write(sender, sender_balance - amount);
      self.balances.write(recipient, self.balances.read(recipient) + amount);
      
      self.emit(Transfer { from: sender, to: recipient, value: amount });
      true
    }
  }
}

Key Differences from the EVM

ConceptEVM/SolidityStarknet/Cairo
Base typeuint256felt252 (252-bit field element)
StoragemappingMap<K, V>
AccountsEOA + contractsAll contracts
Eventsevent#[event] + #[derive]
Constructorconstructor#[constructor]
InheritanceisComposition + interfaces
msg.sendermsg.senderget_caller_address()
addressaddressContractAddress

OpenZeppelin Cairo Components

# Scarb.toml
[dependencies]
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.15.0" }
#[starknet::contract]
mod MyToken {
  use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
  use starknet::ContractAddress;

  component!(path: ERC20Component, storage: erc20, event: ERC20Event);

  #[abi(embed_v0)]
  impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
  #[abi(embed_v0)]
  impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
  impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;

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

  #[constructor]
  fn constructor(ref self: ContractState, recipient: ContractAddress) {
    self.erc20.initializer("MyToken", "MTK");
    self.erc20.mint(recipient, 1000000000000000000000000); // 1M tokens
  }
}

Deployment Workflow

# Install tooling
curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh
pip install starknet-devnet

# Create a new project
scarb new my_contract
cd my_contract && scarb build

# Local testnet
starknet-devnet --seed 0

# Deploy with sncast
sncast --account my_account declare --contract-name MyContract
sncast --account my_account deploy --class-hash 0x...

Starknet.js Frontend Integration

npm install starknet
import { RpcProvider, Account, Contract, cairo } from "starknet"

// Connect provider
const provider = new RpcProvider({ nodeUrl: "https://starknet-mainnet.public.blastapi.io" })

// Connect wallet (ArgentX / Braavos)
const { account } = await window.starknet.enable()

// Interact with contract
const contract = new Contract(ABI, contractAddress, account)

// Read
const balance = await contract.balanceOf(address)

// Write (invoke)
const tx = await contract.transfer(recipient, cairo.uint256(1000n))
await provider.waitForTransaction(tx.transaction_hash)

Account Abstraction (Native Support)

All accounts on Starknet are contracts, with built-in support for:

  • Custom signature schemes (ECDSA / WebAuthn / multisig)
  • Session Keys (users grant apps temporary operation permissions)
  • Paymaster (gas sponsorship, pay with any token)
  • Multicall (execute multiple operations in a single signature)
// Starknet native multicall (no extra SDK required)
const multiCall = await account.execute([
  { contractAddress: tokenA, entrypoint: "approve", calldata: [spender, amount] },
  { contractAddress: dex, entrypoint: "swap", calldata: [tokenA, tokenB, amount] },
])

Tooling Ecosystem

ToolPurpose
ScarbCairo package manager + compiler
Starknet Foundry (snforge)Testing framework (Foundry-like)
ArgentX / BraavosLeading wallets
VoyagerBlock explorer
starknet-devnetLocal testnet

Starknet Cairo Development Guide

Why Starknet?

  • ZK Rollup: All transactions are verified by STARK proofs — security comes from math, not game theory
  • Native AA: All accounts are contract accounts (no EOA concept)
  • Cairo VM: Designed specifically for verifiable computation, independent of the EVM
  • Low gas: Compute-intensive tasks are 10-100x cheaper than Ethereum

Cairo Language Basics

// Cairo 2.x syntax (Rust-like)
use starknet::ContractAddress;

#[starknet::contract]
mod ERC20 {
  use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map};

  #[storage]
  struct Storage {
    name: ByteArray,
    total_supply: u256,
    balances: Map<ContractAddress, u256>,
    allowances: Map<(ContractAddress, ContractAddress), u256>,
  }

  #[event]
  #[derive(Drop, starknet::Event)]
  enum Event {
    Transfer: Transfer,
  }

  #[derive(Drop, starknet::Event)]
  struct Transfer {
    #[key]
    from: ContractAddress,
    #[key]
    to: ContractAddress,
    value: u256,
  }

  #[abi(embed_v0)]
  impl ERC20Impl of super::IERC20<ContractState> {
    fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
      self.balances.read(account)
    }

    fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
      let sender = starknet::get_caller_address();
      let sender_balance = self.balances.read(sender);
      assert(sender_balance >= amount, 'Insufficient balance');
      
      self.balances.write(sender, sender_balance - amount);
      self.balances.write(recipient, self.balances.read(recipient) + amount);
      
      self.emit(Transfer { from: sender, to: recipient, value: amount });
      true
    }
  }
}

Key Differences from the EVM

ConceptEVM/SolidityStarknet/Cairo
Base typeuint256felt252 (252-bit field element)
StoragemappingMap<K, V>
AccountsEOA + contractsAll contracts
Eventsevent#[event] + #[derive]
Constructorconstructor#[constructor]
InheritanceisComposition + interfaces
msg.sendermsg.senderget_caller_address()
addressaddressContractAddress

OpenZeppelin Cairo Components

# Scarb.toml
[dependencies]
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.15.0" }
#[starknet::contract]
mod MyToken {
  use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
  use starknet::ContractAddress;

  component!(path: ERC20Component, storage: erc20, event: ERC20Event);

  #[abi(embed_v0)]
  impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
  #[abi(embed_v0)]
  impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
  impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;

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

  #[constructor]
  fn constructor(ref self: ContractState, recipient: ContractAddress) {
    self.erc20.initializer("MyToken", "MTK");
    self.erc20.mint(recipient, 1000000000000000000000000); // 1M tokens
  }
}

Deployment Workflow

# Install tooling
curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh
pip install starknet-devnet

# Create a new project
scarb new my_contract
cd my_contract && scarb build

# Local testnet
starknet-devnet --seed 0

# Deploy with sncast
sncast --account my_account declare --contract-name MyContract
sncast --account my_account deploy --class-hash 0x...

Starknet.js Frontend Integration

npm install starknet
import { RpcProvider, Account, Contract, cairo } from "starknet"

// Connect provider
const provider = new RpcProvider({ nodeUrl: "https://starknet-mainnet.public.blastapi.io" })

// Connect wallet (ArgentX / Braavos)
const { account } = await window.starknet.enable()

// Interact with contract
const contract = new Contract(ABI, contractAddress, account)

// Read
const balance = await contract.balanceOf(address)

// Write (invoke)
const tx = await contract.transfer(recipient, cairo.uint256(1000n))
await provider.waitForTransaction(tx.transaction_hash)

Account Abstraction (Native Support)

All accounts on Starknet are contracts, with built-in support for:

  • Custom signature schemes (ECDSA / WebAuthn / multisig)
  • Session Keys (users grant apps temporary operation permissions)
  • Paymaster (gas sponsorship, pay with any token)
  • Multicall (execute multiple operations in a single signature)
// Starknet native multicall (no extra SDK required)
const multiCall = await account.execute([
  { contractAddress: tokenA, entrypoint: "approve", calldata: [spender, amount] },
  { contractAddress: dex, entrypoint: "swap", calldata: [tokenA, tokenB, amount] },
])

Tooling Ecosystem

ToolPurpose
ScarbCairo package manager + compiler
Starknet Foundry (snforge)Testing framework (Foundry-like)
ArgentX / BraavosLeading wallets
VoyagerBlock explorer
starknet-devnetLocal testnet
KatanaLocal node by the Dojo team