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.