⭐ Starknet

← Target Reference | VM: Cairo VM

Starknet is the STARK-based Ethereum L2 powered by the Cairo VM. Trident is designed to compile to Sierra (.sierra) and link against starknet.ext.* for Starknet-specific runtime bindings. Starknet features native account abstraction -- every account is a smart contract.


Runtime Parameters

Parameter Value
VM Cairo VM
Runtime binding starknet.ext.*
Account model Account (native account abstraction)
Storage model Key-value
Transaction model Signed (Stark curve)
Cost model Steps + builtins
Cross-chain Ethereum L2 (L1 messaging)

Programming Model

Entry Points

Starknet contracts expose typed entry points categorized by mutability:

  • External -- mutates state, callable by transactions and other contracts
  • View -- read-only, no state mutation
  • Constructor -- runs once at deployment
  • L1 handler -- triggered by messages from Ethereum L1
program my_token

use starknet.ext.storage
use starknet.ext.account

// Constructor
#[constructor]
fn init(name: Field, supply: Field) {
    let deployer: Field = starknet.ext.account.caller()
    starknet.ext.storage.write(storage_var("total_supply"), supply)
    starknet.ext.storage.write_map(storage_var("balances"), deployer, supply)
}

// External function
pub fn transfer(to: Field, amount: Field) {
    let sender: Field = starknet.ext.account.caller()
    let sender_bal: Field = starknet.ext.storage.read_map(
        storage_var("balances"), sender
    )
    assert(sender_bal >= amount)

    starknet.ext.storage.write_map(
        storage_var("balances"), sender, sub(sender_bal, amount)
    )
    starknet.ext.storage.write_map(
        storage_var("balances"), to, sender_bal + amount
    )

    reveal Transfer { from: sender, to: to, amount: amount }
}

// View function
#[view]
pub fn balance_of(owner: Field) -> Field {
    starknet.ext.storage.read_map(storage_var("balances"), owner)
}

// L1 handler -- triggered by Ethereum L1 message
#[l1_handler]
fn handle_deposit(from_l1: Field, amount: Field) {
    let recipient: Field = starknet.ext.account.caller()
    // ... credit the deposit ...
}

State Access

Starknet contracts have persistent key-value storage. Storage variables are addressed by a Pedersen hash of the variable name. Mappings use h(h(var_name, key1), key2) for nested keys.

use starknet.ext.storage

// Storage variable access
let var_addr: Field = storage_var("total_supply")
let supply: Field = starknet.ext.storage.read(var_addr)
starknet.ext.storage.write(var_addr, new_supply)

// Mapping access
let bal: Field = starknet.ext.storage.read_map(
    storage_var("balances"), owner
)
starknet.ext.storage.write_map(
    storage_var("balances"), owner, new_balance
)

// Nested mapping
let allowance: Field = starknet.ext.storage.read_map2(
    storage_var("allowances"), owner, spender
)

storage_var("name") computes sn_keccak(name) -- the Starknet storage address convention.

Identity and Authorization

Starknet has native account abstraction -- every account is a smart contract that validates its own transactions. There is no privileged signature scheme.

use starknet.ext.account

let caller: Field = starknet.ext.account.caller()           // get_caller_address
let contract: Field = starknet.ext.account.self_address()    // get_contract_address
let tx_info: Field = starknet.ext.account.tx_info()          // transaction info hash

Account contracts implement __validate__ and __execute__ entry points. The protocol calls __validate__ first (must succeed or transaction is rejected), then __execute__ runs the actual logic.

Value Transfer

Starknet has no native transfer opcode. Token transfers are contract calls to the ERC-20 contract:

use starknet.ext.call

// Transfer STRK tokens via ERC-20 contract call
starknet.ext.call.invoke(
    STRK_CONTRACT_ADDRESS,
    selector("transfer"),
    [recipient, amount_low, amount_high]   // u256 as two felts
)

Cross-Contract Interaction

Starknet supports contract calls and library calls:

use starknet.ext.call

// Regular contract call
let result: [Field; N] = starknet.ext.call.invoke(
    contract_address,
    selector("function_name"),
    calldata
)

// Library call (runs code in caller's context, like delegatecall)
let result: [Field; N] = starknet.ext.call.library_call(
    class_hash,
    selector("function_name"),
    calldata
)

// Deploy a new contract
let deployed_address: Field = starknet.ext.call.deploy(
    class_hash,
    constructor_calldata,
    salt
)

L1/L2 messaging -- send messages to Ethereum L1:

use starknet.ext.messaging

// Send message to L1 contract
starknet.ext.messaging.send_to_l1(l1_address, payload)

Events

Starknet events use indexed keys and unindexed data:

event Transfer { from: Field, to: Field, amount: Field }

// reveal compiles to emit_event with indexed keys
reveal Transfer { from: sender, to: receiver, amount: value }

reveal maps to emit_event. seal emits only the Pedersen hash of the event data as a key.


Portable Alternative (os.*)

Programs that don't need Starknet-specific features can use os.* instead of starknet.ext.* for cross-chain portability:

starknet.ext.* (this OS only) os.* (any OS)
starknet.ext.storage.read(addr) os.state.read(key) → storage_var read
starknet.ext.account.caller() os.neuron.id() → get_caller_address
starknet.ext.call.invoke(addr, sel, args) No portable equivalent (cross-contract is OS-specific)
starknet.ext.messaging.send_to_l1(to, data) No portable equivalent (L1/L2 messaging is OS-specific)

Use starknet.ext.* when you need: L1/L2 messaging, library calls, Pedersen-addressed storage vars, or other Starknet-specific features. See os.md for the full os.* API.


Ecosystem Mapping

Cairo/Starknet concept Trident equivalent
#[starknet::contract] mod MyToken program my_token with use starknet.ext.*
#[constructor] #[constructor] fn init(...)
#[external(v0)] pub fn function_name(...)
#[view] #[view] pub fn function_name(...)
#[l1_handler] #[l1_handler] fn handle_*(...)
get_caller_address() starknet.ext.account.caller()
get_contract_address() starknet.ext.account.self_address()
get_tx_info() starknet.ext.account.tx_info()
get_block_number() starknet.ext.account.block_number()
get_block_timestamp() starknet.ext.account.block_timestamp()
@storage_var fn balance(addr) -> felt252 starknet.ext.storage.read_map(storage_var("balance"), addr)
self.balance.read(addr) starknet.ext.storage.read_map(var, addr)
self.balance.write(addr, val) starknet.ext.storage.write_map(var, addr, val)
IMyContract::transfer(addr, args) starknet.ext.call.invoke(addr, selector, args)
library_call(class_hash, ...) starknet.ext.call.library_call(hash, selector, args)
deploy_syscall(...) starknet.ext.call.deploy(hash, calldata, salt)
send_message_to_l1(to, payload) starknet.ext.messaging.send_to_l1(addr, payload)
self.emit(Transfer { ... }) reveal Transfer { ... }
selector!("function_name") selector("function_name") (sn_keccak)
assert(condition, 'error') assert(condition)

starknet.ext.* API Reference

Module Function Signature Description
storage read(addr) Field -> Field Read storage variable
write(addr, val) (Field, Field) -> () Write storage variable
read_map(addr, key) (Field, Field) -> Field Read mapping entry
write_map(addr, key, val) (Field, Field, Field) -> () Write mapping entry
read_map2(addr, k1, k2) (Field, Field, Field) -> Field Read nested mapping
write_map2(addr, k1, k2, val) (Field, Field, Field, Field) -> () Write nested mapping
account caller() -> Field get_caller_address
self_address() -> Field get_contract_address
tx_info() -> Field Transaction info hash
block_number() -> Field Current block number
block_timestamp() -> Field Current block timestamp
call invoke(addr, selector, args) (Field, Field, [Field]) -> [Field] Contract call
library_call(hash, selector, args) (Field, Field, [Field]) -> [Field] Library call
deploy(hash, args, salt) (Field, [Field], Field) -> Field Deploy contract
event emit(keys, data) ([Field], [Field]) -> () Raw event emission
messaging send_to_l1(addr, payload) (Field, [Field]) -> () L1 message
crypto pedersen(a, b) (Field, Field) -> Field Pedersen hash
poseidon(data) [Field] -> Field Poseidon hash

Notes

Starknet is the only chain with native account abstraction -- every account is a contract, and any signature scheme can be used for transaction validation. This aligns naturally with Trident's hash-preimage authorization pattern.

Proving happens on Starknet's sequencer (not client-side). The compiler outputs Sierra (Safe Intermediate Representation) which the sequencer JIT-compiles to Cairo assembly for execution.

For VM details, see cairo.md. For mental model migration from smart contracts, see For Onchain Devs.

Pages in this namespace

Local Graph