> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Starknet Fundamentals for LayerZero Developers

> Deep dive into Starknet-specific concepts for LayerZero development including Cairo types, storage model, dispatcher patterns, and gas model.

This page introduces the Starknet-specific concepts you need to understand before building LayerZero applications. If you're coming from EVM chains, this guide explains how Starknet differs and why LayerZero's implementation works the way it does.

**What you'll learn**:

* Cairo's `felt252` type system and `u256` representation
* Starknet storage model and component-based composition
* Transaction types, versions, and typed dispatcher calls
* Multi-call patterns for atomic configuration
* Resource bounds, fee estimation, and common gas pitfalls
* Reentrancy protections and clear-then-execute behavior
* Bytes32 address encoding for crosschain peers

<Tip>
  For complete protocol workflows with detailed code, see [Protocol Overview](/v2/developers/starknet/protocol-overview). For hands-on implementation, see [OApp](/v2/developers/starknet/oapp/overview) or [OFT](/v2/developers/starknet/oft/overview) guides.
</Tip>

## VM Architecture

Starknet uses the Cairo programming language and a field-element-based type system. These fundamentals shape how LayerZero contracts represent addresses, amounts, and payloads.

### The felt252 Type

Starknet's native type is `felt252` (field element), a \~251-bit unsigned integer:

```rust wrap theme={null}
// felt252 is the native Cairo type
let value: felt252 = 123;

// Maximum value is approximately 2^251
// Operations are performed modulo a prime field
```

**Key Properties**:

* Native to the STARK proof system (efficient proving)
* Wraps around on overflow (unlike Solidity's revert behavior)
* Can represent addresses, integers, and short strings

### Common Types

```rust wrap theme={null}
// Unsigned integers (built on felt252)
let a: u8 = 255;
let b: u32 = 4294967295;
let c: u64 = 18446744073709551615;
let d: u128 = 340282366920938463463374607431768211455;
let e: u256 = 0xffffffff_u256;  // Two felt252 values internally

// Signed integers
let f: i32 = -100;
let g: i128 = -1000000;

// Boolean
let flag: bool = true;

// Contract Address (wrapper around felt252)
let addr: ContractAddress = contract_address_const::<0x123>();

// ByteArray for dynamic bytes (like Solidity's bytes)
let data: ByteArray = "Hello, World!";
```

### u256 Representation

Unlike EVM's native 256-bit integers, Starknet represents `u256` as two `felt252` values:

```rust wrap theme={null}
// u256 is stored as (low: u128, high: u128)
let amount: u256 = 1000000000000000000_u256;

// Conversion to/from felt252 requires care
let as_felt: felt252 = amount.low.into();  // Only works if high == 0
```

**Implications for LayerZero**:

* Crosschain amount encoding must handle this difference
* OFT uses `u64` for shared decimals to ensure compatibility

## Message Flow Overview

LayerZero messages on Starknet flow through the Endpoint and verification system before reaching the destination OApp:

* **Send**: OApp -> Endpoint -> message library -> workers (DVNs/Executor)
* **Verify**: DVNs verify and submit to the receive library
* **Receive**: Executor calls `lz_receive` on the destination OApp

<Info>
  **Complete Protocol Details**

  For detailed send/verify/receive workflows with contract code and event flows, see [Protocol Overview](/v2/developers/starknet/protocol-overview).
</Info>

## Transaction Execution Model

Starknet uses distinct transaction types and typed dispatchers. These patterns determine how LayerZero contracts are deployed, called, and configured.

### Transaction Types

Starknet has distinct transaction types for different operations:

#### DECLARE

Publishes contract code to the network:

```bash theme={null}
sncast declare --contract-name MyContract
```

**Result**: `class_hash` - unique identifier for the contract code

**When to use**: First time deploying a new contract version

#### DEPLOY\_ACCOUNT

Deploys an account contract:

```bash theme={null}
sncast account deploy --name my_account
```

**Prerequisite**: The computed account address must be pre-funded

**When to use**: Setting up a new wallet/signer

#### INVOKE

Executes contract functions:

```bash theme={null}
sncast invoke --contract-address 0x... --function set_peer --arguments '...'
```

**When to use**: All regular contract interactions

### Transaction Versions

* **v0/v1/v2**: Deprecated and unsupported on current Starknet networks
* **v3**: Current transaction format with resource bounds (recommended)

```rust wrap theme={null}
// INVOKE v3 includes resource bounds
struct InvokeTransactionV3 {
    resource_bounds: ResourceBoundsMapping,  // l1_gas, l2_gas, l1_data_gas limits
    tip: u64,                                // Priority tip
    // ... other fields
}
```

### Dispatcher Pattern

Starknet doesn't support dynamic dispatch (no `delegatecall` equivalent). Instead, cross-contract calls use typed **dispatchers**. For more details, see the [Cairo Book: Dispatcher Pattern](https://book.cairo-lang.org/ch102-02-interacting-with-another-contract.html#the-dispatcher-pattern).

#### Interface Definition

```rust wrap theme={null}
#[starknet::interface]
pub trait IEndpointV2<TContractState> {
    fn send(ref self: TContractState, params: MessagingParams, refund_address: ContractAddress) -> MessageReceipt;
    fn quote(self: @TContractState, params: MessagingParams, sender: ContractAddress) -> MessagingFee;
    fn get_eid(self: @TContractState) -> u32;
}
```

#### Generated Dispatcher

The compiler generates a dispatcher for each interface:

```rust wrap theme={null}
// Auto-generated by the compiler
pub struct IEndpointV2Dispatcher {
    pub contract_address: ContractAddress,
}

impl IEndpointV2DispatcherTrait of IEndpointV2Dispatcher {
    fn send(self: IEndpointV2Dispatcher, params: MessagingParams, refund_address: ContractAddress) -> MessageReceipt {
        // Serializes params, calls contract, deserializes result
    }
}
```

#### Using Dispatchers

```rust wrap theme={null}
use layerzero::endpoint::interfaces::endpoint_v2::{IEndpointV2Dispatcher, IEndpointV2DispatcherTrait};

fn call_endpoint(endpoint_address: ContractAddress, params: MessagingParams) -> MessagingFee {
    let endpoint = IEndpointV2Dispatcher { contract_address: endpoint_address };

    // Type-safe cross-contract call
    endpoint.quote(params, get_contract_address())
}
```

**Benefits**:

* Compile-time type checking
* Automatic serialization/deserialization
* Clear error messages

**vs EVM**:

| EVM                                  | Starknet                    |
| ------------------------------------ | --------------------------- |
| `interface.function{value: x}(args)` | `dispatcher.function(args)` |
| Dynamic dispatch via address         | Typed dispatcher            |
| `abi.encode/decode`                  | Automatic Serde             |

### Multi-Call Transactions

Multicall is implemented at the account-contract level: a single INVOKE can execute multiple calls atomically when the account supports it. Most major account implementations (Ready Wallet, formerly Argent; Braavos; OpenZeppelin Account) expose multicall by default. If an account contract does not implement multicall, batching is not available for that account.

#### Batching with Account.execute

```typescript wrap theme={null}
// Using starknet.js
const calls = [
  {
    contractAddress: oftAddress,
    entrypoint: 'set_peer',
    calldata: [dstEid, peerAddressLow, peerAddressHigh],
  },
  {
    contractAddress: oftAddress,
    entrypoint: 'set_enforced_options',
    calldata: [...],
  },
  {
    contractAddress: oftAddress,
    entrypoint: 'set_dvn_config',
    calldata: [...],
  },
];

// All calls execute atomically
const response = await account.execute(calls);
```

#### Benefits

* **Atomicity**: All calls succeed or all fail
* **Gas efficiency**: Single transaction overhead
* **Configuration safety**: Set all config before enabling pathway

#### LayerZero Configuration Pattern

```typescript wrap theme={null}
// Recommended: Configure everything in one transaction
const configCalls = [
  // 1. Set library (optional)
  { contractAddress: oft, entrypoint: 'set_send_library', calldata: [...] },

  // 2. Configure DVNs
  { contractAddress: oft, entrypoint: 'set_dvn_config', calldata: [...] },

  // 3. Set enforced options
  { contractAddress: oft, entrypoint: 'set_enforced_options', calldata: [...] },

  // 4. Set peer (LAST - enables pathway)
  { contractAddress: oft, entrypoint: 'set_peer', calldata: [...] },
];

await account.execute(configCalls);
```

## State Management Model

Starknet contracts use a key-value storage model and component-based composition rather than inheritance. These patterns shape how LayerZero contracts store configuration and expose functionality.

### Contract Storage Model

#### Storage Structure

Starknet contracts use a key-value storage model with `felt252` keys:

```rust wrap theme={null}
#[storage]
struct Storage {
    // Simple values
    owner: ContractAddress,
    total_supply: u256,

    // Mappings
    balances: Map<ContractAddress, u256>,
    allowances: Map<(ContractAddress, ContractAddress), u256>,

    // Component substorages
    #[substorage(v0)]
    erc20: ERC20Component::Storage,
    #[substorage(v0)]
    oapp_core: OAppCoreComponent::Storage,
}
```

#### Storage Access

```rust wrap theme={null}
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};

fn example(ref self: ContractState) {
    // Read
    let current_owner = self.owner.read();
    let balance = self.balances.entry(some_address).read();

    // Write
    self.owner.write(new_owner);
    self.balances.entry(some_address).write(new_balance);
}
```

#### Storage Layout

Storage keys are computed deterministically:

* Simple variables: `sn_keccak(variable_name)`
* Mappings: `h(h(variable_name), key1, key2, ...)`

This is abstracted by the compiler, but understanding it helps with:

* Debugging storage reads/writes
* Computing storage proofs for crosschain verification

### Component System

Cairo uses a **component system** instead of inheritance:

#### Defining a Component

```rust wrap theme={null}
#[starknet::component]
pub mod OAppCoreComponent {
    #[storage]
    pub struct Storage {
        OAppCore_endpoint: ContractAddress,
        OAppCore_peers: Map<u32, Bytes32>,
    }

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

    #[embeddable_as(OAppCoreImpl)]
    impl OAppCore<TContractState, +HasComponent<TContractState>> of IOAppCore<ComponentState<TContractState>> {
        fn set_peer(ref self: ComponentState<TContractState>, eid: u32, peer: Bytes32) {
            // Implementation
        }
    }
}
```

#### Using Components in a Contract

```rust wrap theme={null}
#[starknet::contract]
mod MyOApp {
    use layerzero::oapps::oapp::oapp_core::OAppCoreComponent;
    use openzeppelin::access::ownable::OwnableComponent;

    // Declare components
    component!(path: OAppCoreComponent, storage: oapp_core, event: OAppCoreEvent);
    component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);

    // Embed implementations (exposes external functions)
    #[abi(embed_v0)]
    impl OAppCoreImpl = OAppCoreComponent::OAppCoreImpl<ContractState>;

    // Internal implementations (not exposed)
    impl OAppCoreInternalImpl = OAppCoreComponent::InternalImpl<ContractState>;

    #[storage]
    struct Storage {
        #[substorage(v0)]
        oapp_core: OAppCoreComponent::Storage,
        #[substorage(v0)]
        ownable: OwnableComponent::Storage,
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        #[flat]
        OAppCoreEvent: OAppCoreComponent::Event,
        #[flat]
        OwnableEvent: OwnableComponent::Event,
    }
}
```

**vs Solidity Inheritance**:

| Solidity             | Cairo                             |
| -------------------- | --------------------------------- |
| `contract A is B, C` | `component!(path: B, ...)`        |
| `override`           | Trait impl                        |
| Diamond problem      | No conflicts (explicit embedding) |

## Security & Permission Model

Starknet's execution model affects how reentrancy is handled in LayerZero contracts.

### Reentrancy Model

Unlike EVM, Starknet's execution model provides some inherent reentrancy protections:

#### Sequential Execution

Transactions are executed sequentially within a block, not concurrently. However, within a single transaction, reentrancy is still possible.

#### ReentrancyGuard Component

LayerZero contracts use OpenZeppelin's ReentrancyGuard:

```rust wrap theme={null}
use openzeppelin::security::ReentrancyGuardComponent;

component!(path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent);

fn protected_function(ref self: ContractState) {
    self.reentrancy_guard.start();  // Acquire lock

    // Protected logic here
    // External calls are safe

    self.reentrancy_guard.end();    // Release lock
}
```

#### Clear-Then-Execute Pattern

The Endpoint uses this pattern for `lz_receive`:

```rust wrap theme={null}
fn lz_receive(ref self: ContractState, ...) {
    // 1. Clear payload hash first (marks as executed)
    self._clear_payload(receiver, @origin, @payload);

    // 2. Transfer value
    native_token.transfer_from(executor, receiver, value);

    // 3. Execute callback (safe even if it calls back)
    receiver_dispatcher.lz_receive(origin, guid, message, executor, extra_data, value);
}
```

## Gas Model

Starknet's fee model differs from EVM and uses explicit resource bounds.

### Gas Model

| Resource        | Description                |
| --------------- | -------------------------- |
| **L1 Gas**      | Cost for DA on Ethereum L1 |
| **L2 Gas**      | Compute cost on Starknet   |
| **L1 Data Gas** | Calldata size on L1        |

### Setting Resource Bounds

```bash theme={null}
# Using sncast (fee cap; tooling derives resource bounds)
sncast invoke \
  --contract-address 0x... \
  --function transfer \
  --network sepolia \
  --arguments '0x123, 1000'
```

### Estimating Fees

```typescript wrap theme={null}
// Using starknet.js
const { suggestedMaxFee } = await account.estimateInvokeFee({
  contractAddress: oftAddress,
  entrypoint: 'send',
  calldata: [...],
});

// Add buffer for safety and use as a fee cap
const maxFee = suggestedMaxFee * 1.2n;
```

### Common Issues

| Error                  | Cause                   | Solution                                          |
| ---------------------- | ----------------------- | ------------------------------------------------- |
| "Insufficient max fee" | Resource bounds too low | Increase fee cap (`--max-fee`) or resource bounds |
| "Insufficient balance" | Account underfunded     | Add STRK/ETH                                      |
| "Transaction reverted" | Contract logic error    | Check calldata                                    |

## Key Starknet Concepts for LayerZero

Address encoding is critical for crosschain peer verification.

### Address Encoding for Crosschain

#### Bytes32 for Cross-VM Compatibility

LayerZero uses `Bytes32` for addresses to support different address sizes:

```rust wrap theme={null}
// Starknet addresses (felt252) -> Bytes32
use lz_utils::bytes::{Bytes32, ContractAddressIntoBytes32};

let starknet_addr: ContractAddress = ...;
let as_bytes32: Bytes32 = starknet_addr.into();

// EVM addresses (20 bytes) come padded to 32 bytes
let evm_peer: Bytes32 = Bytes32 { value: 0x000000000000000000000000abcdef... };
```

#### Setting Peers

```rust wrap theme={null}
fn set_peer(ref self: ContractState, eid: u32, peer: Bytes32) {
    self.ownable.assert_only_owner();
    self.peers.write(eid, peer);
}

// When receiving, verify the peer
fn _lz_receive(ref self: ..., origin: Origin, ...) {
    let expected_peer = self.peers.read(origin.src_eid);
    assert(origin.sender == expected_peer, 'invalid peer');
    // Process message
}
```

## Key Takeaways

* `felt252` and `u256` encoding affect how LayerZero represents amounts and addresses.
* Storage and components replace inheritance; configuration lives in structured storage.
* Typed dispatchers provide safe cross-contract calls without dynamic dispatch.
* Multicall enables atomic configuration before opening pathways.
* Resource bounds and fee estimation require explicit handling on Starknet.
* Clear-then-execute prevents reentrancy during `lz_receive`.
* Bytes32 encoding standardizes peer addresses across chains.

## Next Steps

* [OApp Overview](/v2/developers/starknet/oapp/overview) - Building OApps
* [OFT Overview](/v2/developers/starknet/oft/overview) - Token transfers
* [Technical Reference](/v2/developers/starknet/technical-reference/starknet-guidance) - Toolchain guide
* [Troubleshooting](/v2/developers/starknet/troubleshooting/common-errors) - Common errors
