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

# LayerZero V2 Aptos Move OApp

> The OApp Standard provides developers with a _generic message passing interface_ to send and receive arbitrary pieces of data between contracts existing...

The OApp Standard provides developers with a *generic message passing interface* to **send** and **receive** arbitrary pieces of data between contracts existing on different blockchain networks.

<img src="https://mintcdn.com/layerzero/MzSiOdXt8xlDlEr4/images/learn/ABLight.svg?fit=max&auto=format&n=MzSiOdXt8xlDlEr4&q=85&s=26159ac201830230d42dbab6e67168a9" alt="Diagram showing crosschain messaging between Network A and Network B using the OApp Standard, with an arrow indicating the message flow via LayerZero Send and Receive" className="block dark:hidden" width="1920" height="517" data-path="images/learn/ABLight.svg" />

<img src="https://mintcdn.com/layerzero/MzSiOdXt8xlDlEr4/images/learn/ABDark.svg?fit=max&auto=format&n=MzSiOdXt8xlDlEr4&q=85&s=57ee89cb8ac22fcdd3d6ba916d27f750" alt="Diagram showing crosschain messaging between Network A and Network B using the OApp Standard, with an arrow indicating the message flow via LayerZero Send and Receive" className="hidden dark:block" width="1920" height="517" data-path="images/learn/ABDark.svg" />

This interface can easily be extended to include anything from specific financial logic in a DeFi application, a voting mechanism in a DAO, and broadly any smart contract use case.

Below is an overview of how the **Aptos Move OApp Standard** aligns with the **LayerZero V2 OApp Contract Standard** on [EVM](../../evm/oapp/overview) and/or [Solana](../../solana/oapp/overview):

1. **`oapp::oapp`** (main OApp interface and example usage)

2. **`oapp::oapp_compose`** (handles composable message logic)

3. **`oapp::oapp_core`** (contains core utilities such as sending messages, quoting fees, setting config/delegates/peers)

4. **`oapp::oapp_receive`** (handles low-level message reception logic)

5. **`oapp::oapp_store`** (internal persistent storage and admin/delegate logic)

This structure replicates in Aptos Move the same interface and flow you would expect from an OApp-based contract on EVM or Solana using LayerZero V2.

## Overview

A **LayerZero OApp** (Omnichain Application) is a contract/module that can:

* **Send** and **Receive** messages across chains

* Optionally **Compose** messages (which is a feature to re-enter the OApp with new logic after a message is processed)

* **Quote** fees for sending crosschain messages

* Manage **Admin** and **Delegate** roles for secure crosschain interactions

In Move, these responsibilities are broken out into the above modules to keep the code well-organized.

### Key Components

* **Sending Messages**: Uses the `lz_send` function from `oapp::oapp_core`.

* **Quoting Fees**: Uses `lz_quote` from `oapp::oapp_core`.

* **Receiving Messages**: Handled by `lz_receive` in `oapp::oapp_receive` and overridden into your OApp’s logic.

* **Composing Messages**: Enabled by `lz_compose` in `oapp::oapp_compose`.

* **Admin/Delegate Permissions**: Managed through `oapp::oapp_core` and stored in `oapp::oapp_store`.

## Main OApp Module (`oapp::oapp`)

The main OApp Module defines entry functions that an application developer can call (for example, to **send** or **quote** crosschain messages).

This contract can house your custom logic for receiving messages (though the base code is handled in `oapp_receive`, you can add extra handling via `lz_receive_impl`).

```rust wrap theme={null}
module oapp::oapp {
    use std::signer::address_of;
    use std::primary_fungible_store;
    use std::option::{self, Option};
    use endpoint_v2_common::bytes32::Bytes32;
    use oapp::oapp_core::{combine_options, lz_quote, lz_send, refund_fees};
    use oapp::oapp_store::OAPP_ADDRESS;

    const STANDARD_MESSAGE_TYPE: u16 = 1;

    /// An example "send" entry function for crosschain messages.
    public entry fun example_message_sender(
        account: &signer,
        dst_eid: u32,
        message: vector<u8>,
        extra_options: vector<u8>,
        native_fee: u64,
    ) {
        let sender = address_of(account);

        // Withdraw fees
        let native_metadata = object::address_to_object<Metadata>(@native_token_metadata_address);
        let native_fee_fa = primary_fungible_store::withdraw(account, native_metadata, native_fee);
        let zro_fee_fa = option::none();

        // Build + send the message
        lz_send(
            dst_eid,
            message,
            combine_options(dst_eid, STANDARD_MESSAGE_TYPE, extra_options),
            &mut native_fee_fa,
            &mut zro_fee_fa,
        );

        // Refund any unused fees to the user
        refund_fees(sender, native_fee_fa, zro_fee_fa);
    }

    #[view]
    /// Quoting the fees for sending a crosschain message
    public fun example_message_quoter(
        dst_eid: u32,
        message: vector<u8>,
        extra_options: vector<u8>,
    ): (u64, u64) {
        let options = combine_options(dst_eid, STANDARD_MESSAGE_TYPE, extra_options);
        lz_quote(dst_eid, message, options, false)
    }

    public(friend) fun lz_receive_impl(
        _src_eid: u32,
        _sender: Bytes32,
        _nonce: u64,
        _guid: Bytes32,
        _message: vector<u8>,
        _extra_data: vector<u8>,
        receive_value: Option<FungibleAsset>,
    ) {
        // Deposit the received token, if any
        option::destroy(receive_value, |value| primary_fungible_store::deposit(OAPP_ADDRESS(), value));

        // TODO: OApp developer can add custom logic for incoming messages here.
    }
    ...
}
```

### Key Points

* **`example_message_sender`** is a reference entry function. Developers can create their own, based on the same pattern, to send a message crosschain.
* **`lz_receive_impl`** is the function that your OApp can override/extend with your custom "on-message" logic.

By default, this module **imports** functions from [`oapp::oapp_core`](#3-oapp-core-module-oappoapp_core) and [`oapp::oapp_store`](#6-internal-store-module-oappoapp_store) to make its job easier.

## OApp Core Module (`oapp::oapp_core`)

The Core Module provides lower-level helper functions to **send** messages, **quote** fees, manage OApp configuration, handle **admin** or **delegate** actions, and keep track of enforced configuration [options](../../evm/configuration/options).

```rust wrap theme={null}
module oapp::oapp_core {
    use endpoint_v2::endpoint;
    use endpoint_v2_common::bytes32::Bytes32;
    use std::option::{self, Option};

    friend oapp::oapp;

    /// Sends a crosschain message.
    public(friend) fun lz_send(
        dst_eid: u32,
        message: vector<u8>,
        options: vector<u8>,
        native_fee: &mut FungibleAsset,
        zro_fee: &mut Option<FungibleAsset>,
    ): MessagingReceipt {
        endpoint::send(&oapp_store::call_ref(), dst_eid, get_peer_bytes32(dst_eid), message, options, native_fee, zro_fee)
    }

    #[view]
    /// Quotes the cost of a crosschain message in both native & ZRO tokens.
    public fun lz_quote(
        dst_eid: u32,
        message: vector<u8>,
        options: vector<u8>,
        pay_in_zro: bool,
    ): (u64, u64) {
        endpoint::quote(OAPP_ADDRESS(), dst_eid, get_peer_bytes32(dst_eid), message, options, pay_in_zro)
    }

    ...
}
```

* **`lz_send`**: Calls the underlying LayerZero Endpoint to perform crosschain message sending.

* **`lz_quote`**: Returns the quote for fees needed to send the message in the native gas token or ZRO if enabled.

* **Peer Management**: The concept of peers (i.e., the paired OApp addresses) is captured by `set_peer(...)`, `has_peer(...)`, etc. per blockchain pathway (i.e., from Aptos to ETH).

* **Admin & Delegate**: Functions like `transfer_admin`, `set_delegate`, `assert_authorized`, etc. manage who can update the OApp configuration or call certain restricted functions.

* **Enforced Options**: By default, the system can enforce specific message options (like certain gas limits, native gas drops, etc.) for sending to specific destination pathways. This is done via `get_enforced_options` and `combine_options`.

## OApp Receive Module (`oapp::oapp_receive`)

When a crosschain message arrives on Aptos, the OApp's configured Executor will route the call into this module’s `lz_receive` or `lz_receive_with_value`.

This module then calls **`lz_receive_impl`** in your main `oapp::oapp` (or whichever module is designated).

```rust wrap theme={null}
module oapp::oapp_receive {
    use endpoint_v2::endpoint;

    /// Main entry for receiving a crosschain message.
    public entry fun lz_receive(
        src_eid: u32,
        sender: vector<u8>,
        nonce: u64,
        guid: vector<u8>,
        message: vector<u8>,
        extra_data: vector<u8>,
    ) {
        lz_receive_with_value(
            src_eid,
            sender,
            nonce,
            wrap_guid(to_bytes32(guid)),
            message,
            extra_data,
            option::none(),
        )
    }

    /// The actual function that can carry a token value
    public fun lz_receive_with_value(
        src_eid: u32,
        sender: vector<u8>,
        nonce: u64,
        wrapped_guid: WrappedGuid,
        message: vector<u8>,
        extra_data: vector<u8>,
        value: Option<FungibleAsset>,
    ) {
        // Validation, clearing, then calls your custom logic
        endpoint::clear(&oapp_store::call_ref(), src_eid, to_bytes32(sender), nonce, wrapped_guid, message);

        lz_receive_impl(
            src_eid,
            to_bytes32(sender),
            nonce,
            get_guid_from_wrapped(&wrapped_guid),
            message,
            extra_data,
            value,
        );
    }
}
```

This means that:

* The configured Executor contract on Aptos calls `lz_receive(...)` on your OApp.

* The message is checked to see if it was sent from an authorized peer (i.e. checking if `sender` is one of your OApp’s configured peers).

* The function `lz_receive_impl` is invoked from your main OApp module to perform any final business logic.

## Compose Module (`oapp::oapp_compose`)

**"Compose"** is a LayerZero feature that allows an OApp to schedule a subsequent call to itself after a message is processed.

In [EVM](../../evm/oapp/message-design-patterns#composed), this is typically invoked via specialized calls to the Endpoint contract in the child OApp's lzReceive implementation, and delivered to a contract which implements `ILayerZeroComposer.sol`.

In Aptos Move, `oapp::oapp_compose` includes the logic to handle the composition of messages after they are cleared or to initiate them from the local OApp.

```rust wrap theme={null}
module oapp::oapp_compose {
    public entry fun lz_compose(
        from: address,
        guid: vector<u8>,
        index: u16,
        message: vector<u8>,
        extra_data: vector<u8>,
    ) {
        endpoint::clear_compose(&oapp_store::call_ref(), from, wrap_guid_and_index(guid, index), message);
        lz_compose_impl(
            from,
            to_bytes32(guid),
            index,
            message,
            extra_data,
            option::none(),
        )
    }

    public fun lz_compose_with_value(
        from: address,
        guid_and_index: WrappedGuidAndIndex,
        message: vector<u8>,
        extra_data: vector<u8>,
        value: Option<FungibleAsset>,
    ) {
        // Similar logic, but includes the possibility of receiving a token in the compose
        endpoint::clear_compose(&oapp_store::call_ref(), from, guid_and_index, message);
        lz_compose_impl(from, guid, index, message, extra_data, value);
    }

    // Developer can override or fill in the body of lz_compose_impl with custom logic
}
```

In typical OApp implementations, you will only need to implement `lz_compose_impl` if your OApp truly needs the advanced external call style logic after a crosschain message has been received.

## Internal Store Module (`oapp::oapp_store`)

The internal store **manages** the global OApp state:

* The OApp’s own address

* The current **Admin** and **Delegate** addresses

* A table of recognized **Peers** (paired addresses from other chains)

* A table of enforced messaging **options**

```rust wrap theme={null}
module oapp::oapp_store {
    struct OAppStore has key {
        contract_signer: ContractSigner,
        admin: address,
        peers: Table<u32, Bytes32>,
        delegate: address,
        enforced_options: Table<EnforcedOptionsKey, vector<u8>>,
    }

    public(friend) fun get_admin(): address acquires OAppStore {
        store().admin
    }

    public(friend) fun has_peer(eid: u32): bool acquires OAppStore {
        table::contains(&store().peers, eid)
    }

    public(friend) fun set_peer(eid: u32, peer: Bytes32) acquires OAppStore {
        table::upsert(&mut store_mut().peers, eid, peer)
    }

    ...
}
```

On Aptos, you typically store data via `move_to<T>(account, T { ... })`. This module sets up a global `OAppStore` resource at `@oapp`.

Functions like `has_peer()`, `set_peer()`, `get_delegate()`, etc., let the other modules read and write data in a structured manner.

## Putting It All Together

1. **Initialization**

   * On "init", the modules are registered with the `endpoint_v2` contract.

   * The OApp store (`oapp::oapp_store::OAppStore`) is created at the address `@oapp`.

2. **Configuration**

   * You set up your **Admin** address and optional **Delegate** if you want certain calls (e.g. `set_send_library`, `skip`, `burn`, or `nilify`) to be callable by someone other than the admin.

   * You **set peers** by calling `set_peer(account, remote_eid, remote_peer_address)`.

3. **Sending a Message**

   * Call your custom send function (like `example_message_sender`) from your main OApp module, which internally calls `lz_send`.

   * Under the hood, the endpoint collects the message, your fees, and orchestrates crosschain delivery.

4. **Receiving a Message**

   * The LayerZero Executor calls `oapp::oapp_receive::lz_receive`

   * This function automatically calls `lz_receive_impl` in your `oapp::oapp`.

   * You handle the message payload or any FungibleAsset that might have come along with it.

5. **Optional: Composing**

   * If you want advanced functionality that re-calls the OApp after clearing, implement `lz_compose_impl` in `oapp::oapp_compose`.

   * Typically only needed for specialized re-entrancy or bridging flows.

## Customizing for Your Own OApp

* **Rename your main modules** if desired (e.g., from `oapp::oapp` to `oapp::my_app`). Update the friend usage accordingly.

* **Implement** your own send/receive logic in `oapp::oapp` entry functions.

* **Override** `lz_receive_impl` to process the crosschain message data (e.g., parse the vector bytes).

* **Implement** or skip the `lz_compose_impl` in `oapp_compose` if your OApp doesn’t need composition logic.

* **Manage** your OApp’s admin and delegate roles carefully. The admin can set local storage options (like peers), while the delegate can call endpoint-level changes (like DVNs, Executors, Message Libraries).

## Conclusion

The **Aptos Move OApp Standard** mirrors the **LayerZero V2 OApp Contract Standard** on EVM and Solana by:

* Splitting crosschain responsibilities into send, receive, and optional compose modules.

* Offering a straightforward pattern for quoting fees, paying them, and optionally paying them in the ZRO token.

* Enforcing the same security patterns around admin/delegates, ensuring that the correct roles handle the correct privileges.

* Providing a strong separation of concerns in well-structured modules to keep your OApp’s logic clean and maintainable.

Use these modules as your foundation for building powerful, omnichain Move applications on Aptos with the same design concepts you would expect from a LayerZero V2 OApp on other chains.
