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

> Below is comprehensive documentation for Aptos Move OFT modules, explaining both the OFT and OFT Adapter, mirroring the LayerZero V2 OFT Standard you...

Below is comprehensive documentation for Aptos Move **OFT** modules, explaining both the **OFT** and **OFT Adapter**, mirroring the **LayerZero V2 OFT Standard** you might see on [EVM](../../evm/oft/quickstart) or [Solana](../../solana/oft/overview).

The Omnichain Fungible Token (OFT) Standard allows **fungible tokens** to be transferred across multiple blockchains without asset wrapping or middlechains.

This standard works by either debiting (`burn` / `lock`) tokens on the source chain, sending a message via LayerZero, and delivering a function call to credit (`mint` / `unlock`) the same number of tokens on the destination chain.

This creates a **unified supply** across all networks that the OFT supports.

### What is OFT?

An **Omnichain Fungible Token (OFT)** is a LayerZero-based token that can be sent across chains without wrapping or middle-chains. It supports:

* **Burn + Mint** (OFT): Remove supply from the source chain, re-create it on the destination.

<img src="https://mintcdn.com/layerzero/l5FYciYAmKUwmOFF/images/learn/oft_mechanism_light.jpg?fit=max&auto=format&n=l5FYciYAmKUwmOFF&q=85&s=f1789e8bc96aac6d6a4949e10798c85d" alt="Diagram showing OFT burn-and-mint mechanism: tokens are burned (subtracted) on the source chain and minted (added) on the destination chain, connected by an arrow representing the crosschain transfer" className="block dark:hidden" width="3840" height="1034" data-path="images/learn/oft_mechanism_light.jpg" />

<img src="https://mintcdn.com/layerzero/l5FYciYAmKUwmOFF/images/learn/oft_mechanism.jpg?fit=max&auto=format&n=l5FYciYAmKUwmOFF&q=85&s=3d32bc3d05cf1241b84ca9b4426c42d2" alt="Diagram showing OFT burn-and-mint mechanism: tokens are burned (subtracted) on the source chain and minted (added) on the destination chain, connected by an arrow representing the crosschain transfer" className="hidden dark:block" width="3840" height="1034" data-path="images/learn/oft_mechanism.jpg" />

* **Lock + Unlock** (OFT Adapter): Move supply into an escrow on the source, release it on the destination.

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/learn/oft-adapter-light.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=bdff2990c335cf923c28c1e22c97d617" alt="Diagram showing OFT Adapter lock-and-unlock mechanism: tokens are locked in an escrow on the source chain and released on the destination chain" className="block dark:hidden" width="1920" height="517" data-path="images/learn/oft-adapter-light.svg" />

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/learn/oft-adapter-dark.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=b03d1a2fe2bab25f7896e27537e2d77d" alt="Diagram showing OFT Adapter lock-and-unlock mechanism: tokens are locked in an escrow on the source chain and released on the destination chain" className="hidden dark:block" width="1920" height="517" data-path="images/learn/oft-adapter-dark.svg" />

On EVM, you see this logic embedded in an `OFT.sol` or `OFTAdapter.sol` contract. In Aptos Move, we achieve the same through specialized modules:

1. **`oft::oft_fa`** – OFT “mint/burn” approach.

2. **`oft::oft_adapter_fa`** – OFT Adapter “lock/unlock” approach.

3. **`oft::oft`** – Unified interface for user-level send, quote, and receive entry points.

4. **`oft::oft_core`** – Core bridging logic shared by both OFT and OFT Adapter.

5. **`oft::oft_impl_config`** – Central config for fees, blocklisting, rate limits (used by both).

6. **`oft::oft_store`** – Tracks shared vs. local decimals so each chain can represent the token with different local decimals if needed.

7. **`oft::oapp_core` / `oft::oapp_store`** – The OApp plumbing for bridging messages crosschain, handling admin/delegate roles, peer configuration, etc.

The **EVM/Solana OFT** relies on `ERC20`/`SPL` logic for mint/burn or lock/unlock.

The **Aptos OFT** relies on Move’s `Fungible Asset` standard. `oft::oft_fa` does actual mint/burn, while `oft::oft_adapter_fa` locks/unlocks an existing `Fungible Asset` in an escrow.

### 2. Relating the OApp and OFT Modules

The **OApp Standard** gives your contract the ability to:

* **Send** crosschain messages (`lz_send`)

* **Receive** crosschain messages (via `lz_receive`)

* **Quote** crosschain fees

* **Enforce** admin- or delegate-level controls

The **OFT Standard** then builds on top of that to specifically handle:

* **Fungible Asset** bridging

* Local token manipulations (burn/mint or lock/unlock)

* Additional rate-limiting, blocklists, bridging fees, etc.

All crosschain calls still flow through `lz_send` and `lz_receive` in `oft_core`, which rely on the OApp’s ability to call the LayerZero Endpoint. This is exactly how the EVM `OFT` extends `OApp` to unify crosschain token operations.

## OFT: `oft::oft_fa`

When tokens are sent crosschain, the module **burns** tokens from the sender’s local supply.

On the receiving chain, it **mints** newly created tokens for the recipient.

In EVM, you might see this with an `ERC20` implementation that calls `_burn` in `send()` and `_mint` in `lzReceive()`. On Aptos, `oft_fa.move` uses **Move’s** `FungibleAsset`:

<Accordion title="Code Snippet: debit_fungible_asset (Burn on Send)">
  ```rust wrap theme={null}
  public(friend) fun debit_fungible_asset(
      sender: address,
      fa: &mut FungibleAsset,
      min_amount_ld: u64,
      dst_eid: u32,
  ): (u64, u64) acquires OftImpl {
      // 1. Check blocklist
      assert_not_blocklisted(sender);

      // 2. Determine the “send” and “receive” amounts (minus dust/fees)
      let amount_ld = fungible_asset::amount(fa);
      let (amount_sent_ld, amount_received_ld) = debit_view(amount_ld, min_amount_ld, dst_eid);

      // 3. Rate limit checks (no exceeding capacity)
      try_consume_rate_limit_capacity(dst_eid, amount_received_ld);

      // 4. Subtract the fee from the total
      let extracted_fa = fungible_asset::extract(fa, amount_sent_ld);
      if (fee_ld > 0) { ... }

      // 5. Burn the final extracted tokens
      fungible_asset::burn(&store().burn_ref, extracted_fa);

      (amount_sent_ld, amount_received_ld)
  }
  ```
</Accordion>

<Accordion title="Code Snippet: credit (Mint on Receive)">
  ```rust wrap theme={null}
  public(friend) fun credit(
      to: address,
      amount_ld: u64,
      src_eid: u32,
      lz_receive_value: Option<FungibleAsset>,
  ): u64 acquires OftImpl {
      // 1. (Optional) deposit crosschain wrapped asset to the admin
      option::for_each(lz_receive_value, |fa| primary_fungible_store::deposit(@oft_admin, fa));

      // 2. Release rate limit capacity for net inflows
      release_rate_limit_capacity(src_eid, amount_ld);

      // 3. Mint the tokens to the final recipient (or redirect if blocklisted)
      primary_fungible_store::mint(
          &store().mint_ref,
          redirect_to_admin_if_blocklisted(to, amount_ld),
          amount_ld
      );
      amount_ld
  }
  ```
</Accordion>

You will want to use the `oft::oft_fa` implementation when you want:

* a brand new token on Aptos representing the crosschain supply.

* each chain to independently mint/burn.

* to bridge a new “canonical” supply for an existing non-Aptos asset on an Aptos chain.

## Adapter OFT: `oft::oft_adapter_fa`

Instead of burning/minting tokens, the module **locks** tokens into an escrow on send and **unlocks** them from escrow on receive.

This can be used if you already have an existing token on an Aptos Move chain that can’t share its mint/burn capabilities.

On EVM, you might see an OFT that uses a "lockbox" to hold user tokens, sending representations of that held asset crosschain. The approach is the same on Aptos:

<Accordion title="Code Snippet: debit_fungible_asset (Lock on Send)">
  ```rust wrap theme={null}
  public(friend) fun debit_fungible_asset(
      sender: address,
      fa: &mut FungibleAsset,
      min_amount_ld: u64,
      dst_eid: u32,
  ): (u64, u64) acquires OftImpl {
      // 1. Check blocklist
      assert_not_blocklisted(sender);

      // 2. Determine the “send” and “receive” amounts
      let (amount_sent_ld, amount_received_ld) = debit_view(amount_ld, min_amount_ld, dst_eid);

      // 3. Subtract fees if any
      let extracted_fa = fungible_asset::extract(fa, amount_sent_ld);
      if (fee_ld > 0) { ... }

      // 4. Deposit the net tokens into an “escrow” account
      primary_fungible_store::deposit(escrow_address(), extracted_fa);

      (amount_sent_ld, amount_received_ld)
  }
  ```
</Accordion>

<Accordion title="Code Snippet: credit (Unlock on Receive)">
  ```rust wrap theme={null}
  public(friend) fun credit(
      to: address,
      amount_ld: u64,
      src_eid: u32,
      lz_receive_value: Option<FungibleAsset>,
  ): u64 acquires OftImpl {
      // 1. (Optional) deposit crosschain “wrapped” asset to admin
      option::for_each(lz_receive_value, |fa| primary_fungible_store::deposit(@oft_admin, fa));

      // 2. Release rate limit capacity
      release_rate_limit_capacity(src_eid, amount_ld);

      // 3. Unlock from escrow into the final recipient
      let escrow_signer = &object::generate_signer_for_extending(&store().escrow_extend_ref);
      primary_fungible_store::transfer(
          escrow_signer,
          metadata(),
          redirect_to_admin_if_blocklisted(to, amount_ld),
          amount_ld
      );

      amount_ld
  }
  ```
</Accordion>

You will want to use the `oft::oft_adapter_fa` implementation when you:

* have a pre-existing token on Aptos and cannot or do not want to grant mint/burn to the bridging contract.

The adapter approach “locks” user tokens, so be mindful of ensuring adequate liquidity in the adapter if bridging in from other chains.

<Warning>
  Typically, only one chain uses the adapter approach (since the “escrow” is meant to represent the supply on that chain). Other chains should use full mint/burn logic.
</Warning>

## Common Interface: `oft::oft`

Both **`oft_fa`** and **`oft_adapter_fa`** feed into the same top-level interface (`oft::oft`). This module:

* Exposes user-facing functions like `send_withdraw(...)`, `send(...)`, `quote_oft(...)`, etc.

* Delegates the actual bridging logic to either “OFT” or "OFT Adapter" code (by depending on whichever you’ve chosen).

* Implements the final `lz_receive_impl(...)` function so that crosschain messages from `oft_core` eventually call your `credit(...)`.

Example from `oft.move`:

```rust wrap theme={null}
public entry fun send_withdraw(
    account: &signer,
    dst_eid: u32,
    to: vector<u8>,
    amount_ld: u64,
    ...
) {
    // 1. Withdraw tokens from user
    let send_value = primary_fungible_store::withdraw(account, metadata(), amount_ld);

    // 2. Withdraw crosschain fees
    let (native_fee_fa, zro_fee_fa) = withdraw_lz_fees(account, native_fee, zro_fee);

    // 3. Call OFT core “send” logic
    send_internal(
        sender,
        dst_eid,
        to_bytes32(to),
        &mut send_value,
        ...
    );

    // 4. Refund leftover fees & deposit any leftover tokens
    refund_fees(sender, native_fee_fa, zro_fee_fa);
    primary_fungible_store::deposit(sender, send_value);
}
```

## Core Logic: `oft::oft_core`

Regardless of whether it’s an **OFT** or **OFT Adapter**, the crosschain bridging sequence is the same:

1. **`send(...)`** – Encodes the message, calls your `debit` function, and dispatches it over the LayerZero Endpoint.

2. **`receive(...)`** – Decodes the message, calls your `credit` function, and optionally calls “compose” logic if there is a follow-up message.

In an EVM environment, OFT variants do something similar with `_burn`, `_mint`, or `_transfer`. The separation is conceptually the same.

```rust wrap theme={null}
public(friend) inline fun send(
    user_sender: address,
    dst_eid: u32,
    to: Bytes32,
    compose_payload: vector<u8>,
    send_impl: |vector<u8>, vector<u8>| MessagingReceipt,
    debit: |bool| (u64, u64),
    build_options: |u64, u16| vector<u8>,
    inspect: |&vector<u8>, &vector<u8>|,
): (MessagingReceipt, u64, u64) {
    let (amount_sent_ld, amount_received_ld) = debit(true);

    // Construct the message to contain 'amount_received_ld' and 'to' address
    let (message, msg_type) = encode_oft_msg(user_sender, amount_received_ld, to, compose_payload);
    let options = build_options(amount_received_ld, msg_type);

    inspect(&message, &options);
    let messaging_receipt = send_impl(message, options);

    // Emit an event for crosschain reference
    ...
}
```

## Implementation Config: `oft::oft_impl_config`

Both **OFT** and **OFT Adapter** share the same configuration for:

* **Fees**: `fee_bps`, `fee_deposit_address`.

* **Blocklist**: Addresses can be disallowed from sending. Inbound tokens to them are re-routed to the admin.

* **Rate Limits**: Each endpoint (chain) can be rate-limited to prevent large surges of bridging.

Example for setting fees:

```rust wrap theme={null}
public entry fun set_fee_bps(admin: &signer, fee_bps: u64) {
    assert_admin(address_of(admin));
    oft_impl_config::set_fee_bps(fee_bps);
}
```

## Internal Store: `oft::oft_store`

Holds two critical values:

1. **`shared_decimals`**: The universal decimals used across all chains.

2. **`decimal_conversion_rate`**: The factor bridging from local decimals to shared decimals.

This matches the approach on EVM-based OFT, where you might define a consistent “decimals” across all chains, and each chain adapts locally if it wants a different local representation.

```rust wrap theme={null}
public(friend) fun initialize(shared_decimals: u8, decimal_conversion_rate: u64) acquires OftStore {
    assert!(store().decimal_conversion_rate == 0, EALREADY_INITIALIZED);
    store_mut().shared_decimals = shared_decimals;
    store_mut().decimal_conversion_rate = decimal_conversion_rate;
}
```

## Comparison Between OFT and OFT Adapter

| Feature                             | **OFT (mint/burn)**                                                                                            | **OFT Adapter (lock/unlock)**                                                                                                                       |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Token Ownership / Supply            | The OFT can create (mint) or destroy (burn) tokens. Perfect for brand-new token supply across multiple chains. | The OFT does **not** create or destroy tokens. It merely locks them into an escrow, then unlocks them upon crosschain receive.                      |
| Use Case                            | Great for truly omnichain tokens that unify supply. Each chain can hold a minted portion.                      | Ideal if an existing token is already deployed, and you can’t share mint/burn privileges with the bridging contract.                                |
| Implementation Module               | `oft_fa.move`                                                                                                  | `oft_adapter_fa.move`                                                                                                                               |
| `credit(...)` Behavior              | **Mint** the inbound tokens for the recipient.                                                                 | **Unlock** from escrow and deposit to the recipient.                                                                                                |
| `debit(...)` Behavior               | **Burn** from the sender’s local supply.                                                                       | **Lock** tokens in an escrow address.                                                                                                               |
| Rebalancing or Liquidity Management | Not required for new tokens (the total supply is burned on one side, minted on the other).                     | Must ensure enough tokens remain in escrow to handle inbound “unlocks” from other chains. If many tokens flow out, local liquidity may be depleted. |

## Putting It All Together

1. **Deploy & Initialize**

   * Deploy the modules (`oft_adapter_fa`, `oft_fa`, `oft`, etc.).
   * Call `init_module` or `init_module_for_test`.
   * For the chosen path (native vs. adapter), run the relevant `initialize(...)` function (e.g., `oft_fa.initialize` or `oft_adapter_fa.initialize`).

2. **Configure**

   * Adjust fees, blocklists, or rate-limits using `oft::oft_impl_config`.
   * For the adapter approach, ensure the escrow has enough tokens to handle inbound bridging from other chains.

3. **Sending**

   * A user calls `send_withdraw(...)` from `oft::oft`.
   * This performs the local “debit” logic (burn or lock) and constructs a crosschain message.
   * Then calls the underlying `lz_send(...)` from the OApp layer.

4. **Receiving**

   * The LayerZero Executor calls your OApp’s `lz_receive_impl(...)`.
   * This triggers `oft_core::receive(...)`, which decodes the message and calls your `credit(...)` logic (mint or unlock).

5. **Monitor**
   * Check events: `OftSent` and `OftReceived` in `oft_core`.
   * Track blocklist changes, fee deposit addresses, and rate limit usage in `oft_impl_config`.

## Conclusion

Whether you choose an **OFT** (mint/burn) or an **OFT Adapter** (lock/unlock):

* The **core bridging** is consistent with the **LayerZero V2 OFT Standard** on EVM/Solana.

* **Fee, blocklist, and rate-limit** logic is shared in `oft_impl_config`.

* **Message encoding/decoding** and **compose** features align with `oft_core`.

* **Shared decimals** plus local decimals ensure consistent crosschain supply.

**OFT** are perfect for new tokens that do not exist outside of the bridging context, while **OFT Adapter** allow you to adopt bridging on an existing, fully deployed token.

Both approaches integrate seamlessly with LayerZero’s crosschain messaging on Aptos, providing a robust, modular framework for omnichain fungible tokens.
