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.

- Lock + Unlock (OFT Adapter): Move supply into an escrow on the source, release it on the destination.
OFT.sol or OFTAdapter.sol contract. In Aptos Move, we achieve the same through specialized modules:
-
oft::oft_fa– OFT “mint/burn” approach. -
oft::oft_adapter_fa– OFT Adapter “lock/unlock” approach. -
oft::oft– Unified interface for user-level send, quote, and receive entry points. -
oft::oft_core– Core bridging logic shared by both OFT and OFT Adapter. -
oft::oft_impl_config– Central config for fees, blocklisting, rate limits (used by both). -
oft::oft_store– Tracks shared vs. local decimals so each chain can represent the token with different local decimals if needed. -
oft::oapp_core/oft::oapp_store– The OApp plumbing for bridging messages crosschain, handling admin/delegate roles, peer configuration, etc.
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
- Fungible Asset bridging
- Local token manipulations (burn/mint or lock/unlock)
- Additional rate-limiting, blocklists, bridging fees, etc.
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:
Code Snippet: debit_fungible_asset (Burn on Send)
Code Snippet: debit_fungible_asset (Burn on Send)
Code Snippet: credit (Mint on Receive)
Code Snippet: credit (Mint on Receive)
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:
Code Snippet: debit_fungible_asset (Lock on Send)
Code Snippet: debit_fungible_asset (Lock on Send)
Code Snippet: credit (Unlock on Receive)
Code Snippet: credit (Unlock on Receive)
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.
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 fromoft_coreeventually call yourcredit(...).
oft.move:
Core Logic: oft::oft_core
Regardless of whether it’s an OFT or OFT Adapter, the crosschain bridging sequence is the same:
-
send(...)– Encodes the message, calls yourdebitfunction, and dispatches it over the LayerZero Endpoint. -
receive(...)– Decodes the message, calls yourcreditfunction, and optionally calls “compose” logic if there is a follow-up message.
_burn, _mint, or _transfer. The separation is conceptually the same.
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.
Internal Store: oft::oft_store
Holds two critical values:
-
shared_decimals: The universal decimals used across all chains. -
decimal_conversion_rate: The factor bridging from local decimals to shared decimals.
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
-
Deploy & Initialize
- Deploy the modules (
oft_adapter_fa,oft_fa,oft, etc.). - Call
init_moduleorinit_module_for_test. - For the chosen path (native vs. adapter), run the relevant
initialize(...)function (e.g.,oft_fa.initializeoroft_adapter_fa.initialize).
- Deploy the modules (
-
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.
- Adjust fees, blocklists, or rate-limits using
-
Sending
- A user calls
send_withdraw(...)fromoft::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.
- A user calls
-
Receiving
- The LayerZero Executor calls your OApp’s
lz_receive_impl(...). - This triggers
oft_core::receive(...), which decodes the message and calls yourcredit(...)logic (mint or unlock).
- The LayerZero Executor calls your OApp’s
-
Monitor
- Check events:
OftSentandOftReceivedinoft_core. - Track blocklist changes, fee deposit addresses, and rate limit usage in
oft_impl_config.
- Check events:
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.
