> ## 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.

# IOTA L1 Fundamentals for LayerZero Developers

> Overview of IOTA L1 Fundamentals for  Developers on LayerZero V2. Learn the architecture, features, and how to get started building. LayerZero enables...

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

**What you'll learn**:

* IOTA's object model vs EVM's account model
* Why dynamic dispatch doesn't work and how the Call pattern solves it
* Capabilities for authorization instead of `msg.sender`
* Programmable Transaction Blocks (PTBs) for atomic multi-step execution
* Gas model differences and rebate mechanism

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

## VM Architecture

IOTA uses the Move programming language and employs an [object-based model](https://docs.iota.org/developer/iota-101/objects/object-model) rather than the account-based model used by EVM chains. This fundamental difference requires different patterns for implementing crosschain functionality.

### IOTA Object Model

IOTA organizes state into [**objects**](https://docs.iota.org/developer/iota-101/objects/object-model) with different [ownership types](https://docs.iota.org/developer/iota-101/objects/object-ownership). For an introduction to IOTA's object model, see [Getting Started](/v2/developers/iota/getting-started#object-ownership-types).

LayerZero uses all three ownership types:

* **Shared**: `EndpointV2`, `MessagingChannel`, `OApp`, `OFT<T>` (accessible by anyone, mutable by authorized)
* **Owned**: `AdminCap`, `CallCap` (belong to specific address, used for authorization)
* **Immutable**: Published packages, `CoinMetadata<T>` (read-only, never change)

Each object has:

* **Unique ID** ([`UID`](https://docs.iota.org/developer/iota-101/objects/uid-id)): Globally unique identifier
* [**Abilities**](https://docs.iota.org/developer/iota-101/move-overview/structs-and-abilities/abilities-intro): Define what operations are allowed (`key`, `store`, `copy`, `drop`)
* **Type**: Determines structure and behavior

### No Dynamic Dispatch

Unlike EVM chains that support dynamic dispatch through `delegatecall`, **IOTA does not support dynamic dispatch**. Function calls must target modules known at compile time.

**Why this matters**: The LayerZero Endpoint needs to call back into OApp modules whose addresses vary per deployment—not known when the Endpoint is published. This architectural constraint requires a different approach.

### Call Pattern (Hot Potato)

LayerZero solves the dynamic dispatch limitation using a capability-based pattern called "[hot potato](https://docs.iota.org/developer/iota-101/move-overview/patterns/hot-potato)."

To achieve dynamic routing, LayerZero uses the **Call pattern**—a capability-based [hot potato implementation](https://docs.iota.org/developer/iota-101/move-overview/patterns/hot-potato).

The `Call<Param, Result>` struct:

* Has **no** `drop` or `store` abilities (cannot be ignored or saved)
* Can only be created by the caller module
* Must be consumed by the designated callee
* Enforces proper sequencing through lifecycle states
* Returns results back to the caller

**Call Lifecycle**:

```
Active → Creating (child calls) → Waiting → Active → Completed → Destroyed
```

This ensures atomicity: if any step fails, the entire PTB reverts.

### Programmable Transaction Blocks (PTBs)

IOTA's execution model centers around [**Programmable Transaction Blocks**](https://docs.iota.org/developer/iota-101/transactions/ptb/programmable-transaction-blocks)—atomic command sequences that:

* Execute multiple Move function calls
* Pass objects and results between calls
* Guarantee all-or-nothing execution
* Enable complex multi-contract workflows
* Support up to 1024 commands per block

## Message Flow Overview

LayerZero messages on IOTA flow through multiple modules using the Call pattern within a Programmable Transaction Block.

**High-Level Flow**:

```
Send:    OApp → Endpoint → ULN302 → Workers → Confirmation chain
Receive: Executor → Endpoint (clear) → OApp (validate & process)
```

**Key Mechanisms**:

* **Call pattern**: Dynamic routing through `Call<Param, Result>` objects
* **PTB coordination**: All steps happen atomically in one transaction
* **Capability validation**: Each module validates CallCap ownership
* **Storage management**: MessagingChannel tracks nonces and payload hashes

<Info>
  ### Complete Protocol Details

  For detailed send/verify/receive workflows with contract code, struct definitions, and transaction analysis, see [Protocol Overview](/v2/developers/iota/protocol-overview).
</Info>

## Transaction Execution Model

IOTA supports two types of function calls, each serving different purposes in the LayerZero protocol.

### Static Calls

Used when the target module is known at compile time:

* Direct function invocation within a PTB
* No intermediate `Call` object needed
* Example: OApp calling Endpoint (Endpoint object ID is known)

```rust wrap theme={null}
// Direct call (static)
endpoint::init_channel(&mut endpoint, &call_cap, remote_eid);
```

### Call Pattern (Dynamic Routing)

Used when the target module is not known at compile time:

* Caller creates a `Call<Param, Result>` object
* PTB routes the `Call` to the appropriate module
* Recipient processes and completes the `Call`
* Caller confirms the `Call` to extract results
* Example: Endpoint routing to OApp (OApp object ID varies per deployment)

```rust wrap theme={null}
// Create Call
let call = oapp::lz_send(&mut oapp, &call_cap, ...);
// PTB routes Call through Endpoint
// Confirm to extract results
let (_, receipt) = oapp::confirm_lz_send(&oapp, &call_cap, call);
```

### Atomicity Guarantees

All operations within a PTB are atomic:

* If any step fails, the entire transaction reverts
* No partial state changes
* Enables complex multi-step operations with safety guarantees

**For Implementation Details**: See [Protocol Overview](/v2/developers/iota/protocol-overview) for complete workflows including:

* Nonce management and packet construction
* Worker assignment and fee aggregation
* DVN verification and threshold checking
* Message delivery and payload clearing

## State Management Model

LayerZero on IOTA uses shared and owned objects to manage configuration and message state, rather than EVM-style storage slots.

### LayerZero State Storage

State is organized into objects with different ownership types, each serving specific purposes:

| State Type             | Storage Location                  | Ownership Type                             |
| ---------------------- | --------------------------------- | ------------------------------------------ |
| **Endpoint**           | `EndpointV2` shared object        | Shared (anyone can read, admin can modify) |
| **OApp Configuration** | `OApp` shared object              | Shared (owner via `AdminCap`)              |
| **OApp Peer Mappings** | `Peer` struct within `OApp`       | Embedded (has `store` ability)             |
| **Messaging Channels** | `MessagingChannel` shared objects | Shared (created per OApp)                  |
| **Library Configs**    | Objects within `Uln302`           | Shared object fields                       |
| **Admin Authority**    | `AdminCap` owned objects          | Owned (transferable to new admin)          |

**Key Concepts**:

* [**Shared objects**](https://docs.iota.org/developer/iota-101/objects/object-ownership/shared): Created with `transfer::share_object()`, accessible to all transactions
* [**Owned objects**](https://docs.iota.org/developer/iota-101/objects/object-ownership/address-owned): Created with `transfer::transfer()`, belong to specific addresses
* **Embedded structs**: Fields within objects (e.g., `Peer`, `EnforcedOptions`)
* [**Tables**](https://docs.iota.org/references/framework/table): Dynamic collections stored within objects (e.g., peer mappings by EID)

### Object-Based Configuration

Configuration is stored in **struct fields** and **Tables**, not storage slots:

```rust wrap theme={null}
public struct OApp has key {
    id: UID,
    oapp_cap: CallCap,              // Capability for calls
    admin_cap: address,              // Reference to AdminCap owner
    peer: Peer,                      // Embedded peer mappings (Table<u32, Bytes32>)
    enforced_options: EnforcedOptions, // Embedded options config
    sending_call: Option<address>,   // Track in-progress sends
}
```

### Initialization Requirements

Before sending messages, you must:

1. **Register the OApp**: Call `endpoint::register_oapp()` to create a `MessagingChannel`
2. **Initialize channels**: Call `endpoint::init_channel()` for each remote EID
3. **Set peer addresses**: Call `oapp::set_peer()` for each destination
4. **(Optional)** Set send/receive libraries (uses Endpoint defaults if not set)
5. **(Optional)** Configure ULN parameters (uses library defaults if not set)

## Security & Permission Model

IOTA's security model differs fundamentally from EVM's `msg.sender` approach, using owned objects to prove authorization.

### Capability-Based Authorization

Instead of checking the transaction sender, IOTA functions require [capability objects](https://docs.iota.org/developer/iota-101/move-overview/patterns/capabilities) as parameters:

| Capability       | Type  | Purpose                                                |
| ---------------- | ----- | ------------------------------------------------------ |
| `CallCap`        | Owned | Authorizes creating `Call` objects and calling modules |
| `AdminCap`       | Owned | Grants admin rights (set peers, configure options)     |
| `TreasuryCap<T>` | Owned | Grants mint/burn authority for coin type `T`           |
| `UpgradeCap`     | Owned | Authorizes package upgrades                            |

**Capability Pattern**:

```rust wrap theme={null}
public fun set_peer(
    self: &mut OApp,
    admin_cap: &AdminCap,  // Must provide AdminCap to prove authorization
    eid: u32,
    peer: Bytes32,
)
```

### CallCap Type System

`CallCap` objects have two types that determine their identifier:

```rust wrap theme={null}
/// From call_cap module
public enum CapType {
    Individual,           // ID = UID address (object-specific)
    Package(address),     // ID = Package address (package-wide)
}

public fun id(self: &CallCap): address {
    match (self.cap_type) {
        CapType::Individual => self.id.to_address(),  // Returns object UID
        CapType::Package(package) => package,          // Returns package address!
    }
}
```

**LayerZero OApps/OFTs use Package CallCaps**:

```rust wrap theme={null}
// Created with one-time witness
call_cap::new_package_cap(&otw, ctx)  // Creates Package type

// Returns package address
oapp_cap.id()  // → package address, not object UID
```

**Why This Matters**:

The registry architecture explains why package IDs are used throughout:

```rust wrap theme={null}
/// From oapp_registry.move
public struct OAppRegistry has store {
    // Maps OApp package address to its complete information
    oapps: Table<address, OAppRegistration>,  // ← Keyed by package address!
}

public(package) fun get_messaging_channel(
    self: &OAppRegistry,
    oapp: address  // Package address expected
): address {
    let registration = table_ext::borrow_or_abort!(&self.oapps, oapp, EOAppNotRegistered);
    registration.messaging_channel
}
```

**Impact on LayerZero**:

* Registry lookups use `callCap.id()` → package address
* `MessagingChannel.oapp` field stores package address
* **Peer addresses must be package IDs** (not object IDs)
* Verification checks receiver against package address
* Remote chains send to package address, not object

This is the fundamental reason why IOTA peer addresses are package IDs, not object IDs.

### Why This Matters for Configuration

When you deploy an OApp/OFT and configure peers:

**On IOTA side**:

```typescript wrap theme={null}
import {SDK} from '@layerzerolabs/lz-iotal1-sdk-v2';
import {Stage} from '@layerzerolabs/lz-definitions';

const sdk = new SDK({client, stage: Stage.MAINNET});
const oapp = sdk.getOApp(yourPackageId); // Package ID, not object ID!
```

**On remote EVM side**:

```solidity wrap theme={null}
// Use IOTA PACKAGE ID as peer
myOApp.setPeer(
    30378,  // IOTA mainnet EID
    bytes32(0x061a47bf...)  // Your IOTA PACKAGE ID
);
```

**What happens when message arrives**:

1. Remote chain sends to your package ID
2. IOTA Endpoint looks up package ID in registry
3. Finds your MessagingChannel
4. Routes message to your OApp object

This registry architecture is why peers must be package IDs.

### Validation Pattern

OApps validate `CallCap` ownership to ensure calls are authorized:

```rust wrap theme={null}
public fun send(
    self: &OApp,
    oapp_cap: &CallCap,  // Proves caller owns this OApp
    ...
) {
    self.assert_oapp_cap(oapp_cap);  // Validates CallCap belongs to this OApp
    // ... business logic
}

fun assert_oapp_cap(self: &OApp, cap: &CallCap) {
    assert!(self.oapp_cap.id() == cap.id(), EInvalidOAppCap);
}
```

This replaces Solidity's inheritance-based validation with explicit capability checks.

### Receive Path Security

When receiving messages, the OApp validates:

1. **Call Authorization**: The `Call` must come from the authorized Endpoint
2. **Peer Validation**: Message sender must match configured peer for source EID
3. **Message Integrity**: DVNs have verified the message before delivery

```rust wrap theme={null}
public fun lz_receive(
    self: &mut OApp,
    call: Call<LzReceiveParam, Void>,
) {
    // Validate Call came from Endpoint
    let (callee, param, _) = call.destroy(&self.oapp_cap);
    assert!(callee == endpoint_address(), EOnlyEndpoint);

    // Validate sender is the configured peer
    let peer = self.peer.get_peer(param.src_eid);
    assert!(param.sender == peer, EOnlyPeer);

    // Process message...
}
```

### Common Security Risks

* **Missing capability validation**: Not checking `CallCap` or `AdminCap`
* **Capability loss**: Transferring or losing owned capability objects
* **Incorrect peer configuration**: Setting wrong peer addresses
* **Bypassing validation**: Skipping `assert_oapp_cap()` checks

## Gas Model

IOTA's gas system differs from EVM by separating storage and computation costs, with a unique rebate mechanism.

### Storage Gas

* Charged for storing data onchain
* **Rebate mechanism**: When storage is freed, gas is refunded
* This can result in **negative gas utilization** for transactions that free storage

### Computation Gas

* Charged for execution/computation
* **Base Budget**: Every transaction requires a minimum of 1000 gas units
* Priority fees can be added during network congestion

For detailed gas information, see:

* [IOTA Gas Pricing](https://docs.iota.org/developer/iota-101/tokenomics/gas-pricing)
* [IOTA Gas in IOTA](https://docs.iota.org/developer/iota-101/tokenomics/gas-in-iota)

## Key IOTA Concepts for LayerZero

IOTA provides system-level objects and features that LayerZero leverages for crosschain messaging.

### Clock Object

The [Clock](https://docs.iota.org/references/framework/clock) is a system singleton object at address `0x6`:

```rust wrap theme={null}
// Access in functions
public fun some_function(clock: &Clock) {
    let timestamp_ms = clock.timestamp_ms();
    // Use for timeout validation, rate limiting, etc.
}

// In PTB
tx.object('0x6')  // Reference to Clock
```

Used in LayerZero for:

* Library timeout validation
* Rate limiter windows
* Message expiration checks

### Event System

IOTA [events](https://docs.iota.org/developer/iota-101/using-events) are emitted and indexed for off-chain monitoring:

```rust wrap theme={null}
use iota::event;

public struct MessageSentEvent has copy, drop {
    guid: Bytes32,
    dst_eid: u32,
    message: vector<u8>,
}

// Emit event
event::emit(MessageSentEvent { guid, dst_eid, message });
```

**Monitoring Events**:

```typescript wrap theme={null}
// Subscribe to events
const unsubscribe = await client.subscribeEvent({
  filter: {Package: packageId},
  onMessage: (event) => {
    console.log('Event:', event);
  },
});
```

***

## Key Takeaways

1. **No Dynamic Dispatch**: IOTA doesn't support dynamic dispatch; use Call pattern instead
2. **PTB-Centric**: All crosschain operations happen within Programmable Transaction Blocks
3. **Explicit Validation**: Replace EVM inheritance with explicit validation checks
4. **Object-Based State**: Configuration stored in object fields, not EVM-style storage slots
5. **Atomicity**: PTBs guarantee all-or-nothing execution
6. **Dual Gas Model**: Separate charges for storage and computation, with storage rebates

## Next Steps

* [OApp Implementation Guide](/v2/developers/iota/oapp/overview) - Build custom crosschain applications
* [OFT Implementation Guide](/v2/developers/iota/oft/overview) - Deploy crosschain tokens
* [OFT SDK](/v2/developers/iota/oft/sdk) - TypeScript SDK methods and patterns
* [Configuration Guide](/v2/developers/iota/configuration/dvn-executor-config) - DVN and executor configuration
* [Protocol Overview](/v2/developers/iota/protocol-overview) - Complete protocol workflows
* [IOTA Development Guidance](/v2/developers/iota/technical-reference/iota-guidance) - Best practices
