Starknet Types
When building smart contracts on Starknet, you'll work with specialized types that represent blockchain-specific concepts. These types allow you to interact with deployed contracts through their addresses, handle cross-chain communication, and handle contract-specific data types. This chapter introduces the Starknet-specific types provided by the Core library.
Contract Address
The ContractAddress
type represents the address of a deployed contract on Starknet. Every deployed contract has a unique address that identifies it on the network. You'll use it to call other contracts, check caller identities, manage access control, and anything that involves on-chain accounts.
use starknet::{ContractAddress, get_caller_address};
#[starknet::interface]
pub trait IAddressExample<TContractState> {
fn get_owner(self: @TContractState) -> ContractAddress;
fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress);
}
#[starknet::contract]
mod AddressExample {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use super::{ContractAddress, get_caller_address};
#[storage]
struct Storage {
owner: ContractAddress,
}
#[constructor]
fn constructor(ref self: ContractState, initial_owner: ContractAddress) {
self.owner.write(initial_owner);
}
#[abi(embed_v0)]
impl AddressExampleImpl of super::IAddressExample<ContractState> {
fn get_owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}
fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can transfer');
self.owner.write(new_owner);
}
}
}
Contract addresses in Starknet have a value range of [0, 2^251)
, which is enforced by the type system. You can create a ContractAddress
from a felt252
using the regular TryInto
trait.
Storage Address
The StorageAddress
type represents the location of a value within a contract's storage. While you typically won't create these addresses directly (the storage system handles this for you through types like Map and Vec), understanding this type is important for advanced storage patterns. Each value stored in the Storage
struct has its own StorageAddress
, and can be accessed directly following the rules defined in the Storage chapter.
#[starknet::contract]
mod StorageExample {
use starknet::storage_access::StorageAddress;
#[storage]
struct Storage {
value: u256,
}
// This is an internal function that demonstrates StorageAddress usage
// In practice, you rarely need to work with StorageAddress directly
fn read_from_storage_address(address: StorageAddress) -> felt252 {
starknet::syscalls::storage_read_syscall(0, address).unwrap()
}
}
Storage addresses share the same value range as contract addresses [0, 2^251)
. The related StorageBaseAddress
type represents base addresses that can be combined with offsets, with a slightly smaller range of [0, 2^251 - 256)
to accommodate offset calculations.
Ethereum Address
The EthAddress
type represents a 20-byte Ethereum address and is used mostly for building cross-chain applications on Starknet. This type is used in L1-L2 messaging, token bridges, and any contract that needs to interact with Ethereum.
use starknet::EthAddress;
#[starknet::interface]
pub trait IEthAddressExample<TContractState> {
fn set_l1_contract(ref self: TContractState, l1_contract: EthAddress);
fn send_message_to_l1(ref self: TContractState, recipient: EthAddress, amount: felt252);
}
#[starknet::contract]
mod EthAddressExample {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::send_message_to_l1_syscall;
use super::EthAddress;
#[storage]
struct Storage {
l1_contract: EthAddress,
}
#[abi(embed_v0)]
impl EthAddressExampleImpl of super::IEthAddressExample<ContractState> {
fn set_l1_contract(ref self: ContractState, l1_contract: EthAddress) {
self.l1_contract.write(l1_contract);
}
fn send_message_to_l1(ref self: ContractState, recipient: EthAddress, amount: felt252) {
// Send a message to L1 with recipient and amount
let payload = array![recipient.into(), amount];
send_message_to_l1_syscall(self.l1_contract.read().into(), payload.span()).unwrap();
}
}
#[l1_handler]
fn handle_message_from_l1(ref self: ContractState, from_address: felt252, amount: felt252) {
// Verify the message comes from the expected L1 contract
assert(from_address == self.l1_contract.read().into(), 'Invalid L1 sender');
// Process the message...
}
}
This example shows the key uses of EthAddress
:
- Storing L1 contract addresses
- Sending messages to Ethereum using
send_message_to_l1_syscall
- Receiving and validating messages from L1 with
#[l1_handler]
The EthAddress
type ensures type safety and can be converted to/from felt252
for L1-L2 message serialization.
Class Hash
The ClassHash
type represents the hash of a contract class (the contract's code). In Starknet's architecture, contract classes are deployed separately from contract instances, allowing multiple contracts to share the same code. As such, you can use the same class hash to deploy multiple contracts, or to upgrade a contract to a new version.
use starknet::ClassHash;
#[starknet::interface]
pub trait IClassHashExample<TContractState> {
fn get_implementation_hash(self: @TContractState) -> ClassHash;
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
}
#[starknet::contract]
mod ClassHashExample {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::replace_class_syscall;
use super::ClassHash;
#[storage]
struct Storage {
implementation_hash: ClassHash,
}
#[constructor]
fn constructor(ref self: ContractState, initial_class_hash: ClassHash) {
self.implementation_hash.write(initial_class_hash);
}
#[abi(embed_v0)]
impl ClassHashExampleImpl of super::IClassHashExample<ContractState> {
fn get_implementation_hash(self: @ContractState) -> ClassHash {
self.implementation_hash.read()
}
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
replace_class_syscall(new_class_hash).unwrap();
self.implementation_hash.write(new_class_hash);
}
}
}
Class hashes have the same value range as addresses [0, 2^251)
. They uniquely identify a specific version of contract code and are used in deployment operations, proxy patterns, and upgrade mechanisms.
Working with Block and Transaction Information
Starknet provides several functions to access information about the current execution context. These functions return specialized types or structures containing blockchain state information.
#[starknet::interface]
pub trait IBlockInfo<TContractState> {
fn get_block_info(self: @TContractState) -> (u64, u64);
fn get_tx_info(self: @TContractState) -> (ContractAddress, felt252);
}
#[starknet::contract]
mod BlockInfoExample {
use starknet::{get_block_info, get_tx_info};
use super::ContractAddress;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl BlockInfoImpl of super::IBlockInfo<ContractState> {
fn get_block_info(self: @ContractState) -> (u64, u64) {
let block_info = get_block_info();
(block_info.block_number, block_info.block_timestamp)
}
fn get_tx_info(self: @ContractState) -> (ContractAddress, felt252) {
let tx_info = get_tx_info();
// Access transaction details
let sender = tx_info.account_contract_address;
let tx_hash = tx_info.transaction_hash;
(sender, tx_hash)
}
}
}
The BlockInfo
structure contains details about the current block, including its number and timestamp. The TxInfo
structure provides transaction-specific information, including the sender's address, transaction hash, and fee details.