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.