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

# OFT on Stellar

> Deploy an Omnichain Fungible Token (OFT) on Stellar using Soroban smart contracts. Learn deployment, token integration, send/receive mechanics, and extensions.

## What is an OFT on Stellar?

An **Omnichain Fungible Token (OFT)** on Stellar is a Soroban smart contract that enables crosschain token transfers via the LayerZero protocol. It extends the [OApp](/v2/developers/stellar/oapp/overview) contract with token handling logic for debiting (lock/burn) and crediting (unlock/mint) tokens.

Stellar OFTs integrate with **SEP-41** tokens -- Stellar's standard token interface (analogous to ERC-20 on EVM).

### Classic Assets Receiving Requirements

These requirements apply when your OFT token is a **SAC (wrapped classic asset)**. If you are using a custom contract token, trustlines are not required for any address type.

#### G-Address (EOA)

G-address recipients must meet two prerequisites before they can receive classic assets:

1. **Account activation**: The account must hold a minimum of 1 XLM to exist on the Stellar network.
2. **Trustline**: The account must have an explicit trustline for the classic asset being received.

If `lz_receive` fails due to unmet prerequisites, delivery can be retried once the recipient account is activated and the trustline is established.

#### C-Address (Smart Contract)

C-address recipients are not subject to these restrictions. As long as the contract address exists on-chain, it can receive assets directly.

## OFT Types

Stellar uses a single OFT contract with an `OftType` enum to determine behavior:

```rust wrap theme={null}
enum OftType {
    LockUnlock,           // Lock tokens on send, unlock on receive
    MintBurn(Address),    // Burn tokens on send, mint on receive via Mintable contract
}
```

| Mode           | Send (Debit)                | Receive (Credit)                        | When to Use                                                   |
| -------------- | --------------------------- | --------------------------------------- | ------------------------------------------------------------- |
| **LockUnlock** | Lock tokens in OFT contract | Unlock tokens from OFT contract         | Wrapping an existing token that you don't control minting for |
| **MintBurn**   | Burn tokens from sender     | Mint tokens to recipient via `Mintable` | You control the token supply (e.g., via SAC Manager)          |

<Tip>
  **MintBurn** is the recommended approach when you have control over the token supply.
</Tip>

## Setup & Deployment

<Tip>
  For the full OFT reference implementation, see the [OFT source](https://github.com/LayerZero-Labs/monorepo-external/tree/main/apps/oft-app/contracts/stellar/oft) in the LayerZero monorepo.
</Tip>

**Prerequisites**:

* [Stellar CLI](https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli) (v25.1.0+)
* [Rust](https://www.rust-lang.org/tools/install) (v1.90.0+) with `wasm32v1-none` target (`rustup target add wasm32v1-none`)
* Familiarity with [Soroban smart contracts](https://developers.stellar.org/docs/learn/smart-contract-internals) and [Rust on Soroban](https://developers.stellar.org/docs/build/guides)

This guide uses **MintBurn** mode. For LockUnlock mode, see [Using LockUnlock Mode](#using-lockunlock-mode) below.

**The MintBurn 3-contract pattern:**

```
Token (SAC) ← SAC Manager ← OFT
```

1. **Token (SAC)**: Your SEP-41 token
2. **SAC Manager**: RBAC-controlled admin wrapper that implements `Mintable`. The OFT calls it to mint; it calls the SAC as its admin.
3. **OFT**: Deployed with `oft_type: MintBurn(sac_manager_address)`

### Step 1: Create and Deploy Your Token

If you don't already have a token, deploy a [SEP-41](https://stellar.org/protocol/sep-41)-compliant fungible token on Soroban.

The simplest approach is to wrap a [Stellar classic asset](https://developers.stellar.org/docs/tokens/stellar-asset-contract) as a **Stellar Asset Contract (SAC)**:

```bash wrap theme={null}
# Deploy the classic asset as a SAC on testnet
stellar contract asset deploy \
  --asset MY_TOKEN:YOUR_ISSUER_ADDRESS \
  --network testnet \
  --source my-account
```

This returns the SAC contract address — your SEP-41 token address.

Alternatively, you can build a custom Soroban contract token using [OpenZeppelin Stellar Contracts](https://docs.openzeppelin.com/stellar-contracts) for more control over token logic.

### Step 2: Deploy the SAC Manager (Mintable Contract)

For MintBurn mode, the OFT needs a contract that implements the `Mintable` trait to mint tokens on crosschain receive:

```rust wrap theme={null}
pub trait Mintable {
    fn mint(env: &Env, to: &Address, amount: i128, operator: &Address);
}
```

The recommended approach is to use the **SAC Manager** contract provided by LayerZero, which wraps a SAC with role-based access control.

**Create SAC Manager project**:

```bash wrap theme={null}
stellar contract init sac-manager
cd sac-manager
```

**Configure `Cargo.toml`** with LayerZero dependencies:

```toml wrap theme={null}
[package]
name = "sac-manager"
version = "0.1.0"
edition = "2021"

[dependencies]
soroban-sdk = "25.1.1"
cfg-if = "1.0"
# LayerZero dependencies
# common-macros = { ... }
# utils = { ... }

[dev-dependencies]
soroban-sdk = { version = "25.1.1", features = ["testutils"] }

[lib]
crate-type = ["cdylib"]
```

**Implement SAC Manager**:

```rust wrap theme={null}
use common_macros::{contract_impl, lz_contract, only_role, storage};
use soroban_sdk::{token::StellarAssetClient, Address, Env};
use utils::rbac::RoleBasedAccessControl;

#[storage]
pub enum SACManagerStorage {
    #[instance(Address)]
    SacToken,
}

#[lz_contract]
pub struct SACManager;

const MINTER_ROLE: &str = "MINTER_ROLE";
const ADMIN_MANAGER_ROLE: &str = "ADMIN_MANAGER_ROLE";
const BLACKLISTER_ROLE: &str = "BLACKLISTER_ROLE";
const CLAWBACK_ROLE: &str = "CLAWBACK_ROLE";

#[contract_impl]
impl SACManager {
    pub fn __constructor(env: &Env, sac_token: &Address, owner: &Address) {
        Self::init_owner(env, owner);
        SACManagerStorage::set_sac_token(env, sac_token);
    }

    pub fn underlying_sac(env: &Env) -> Address {
        SACManagerStorage::sac_token(env).unwrap()
    }
}

// Each method is role-gated — only addresses with the matching role can call it
#[contract_impl(contracttrait)]
impl SACAdminWrapper for SACManager {
    #[only_role(operator, ADMIN_MANAGER_ROLE)]
    fn set_admin(env: &Env, new_admin: &Address, operator: &Address) {
        sac_client(env).set_admin(new_admin);
    }

    #[only_role(operator, BLACKLISTER_ROLE)]
    fn set_authorized(env: &Env, id: &Address, authorize: bool, operator: &Address) {
        sac_client(env).set_authorized(id, &authorize);
    }

    #[only_role(operator, CLAWBACK_ROLE)]
    fn clawback(env: &Env, from: &Address, amount: i128, operator: &Address) {
        sac_client(env).clawback(from, &amount);
    }

    // This is the method the OFT calls to mint tokens on crosschain receive
    #[only_role(operator, MINTER_ROLE)]
    fn mint(env: &Env, to: &Address, amount: i128, operator: &Address) {
        sac_client(env).mint(to, &amount);
    }
}

#[contract_impl(contracttrait)]
impl RoleBasedAccessControl for SACManager {}

fn sac_client(env: &Env) -> StellarAssetClient<'_> {
    StellarAssetClient::new(env, &SACManager::underlying_sac(env))
}
```

<Info>
  The SAC Manager source is available in the [LayerZero Stellar contracts](https://github.com/LayerZero-Labs/monorepo-external/blob/main/apps/oft-app/contracts/stellar/sac-manager/src/sac_manager.rs). You can also implement your own contract — any contract that satisfies the `Mintable` trait will work.
</Info>

**Build and deploy**:

```bash wrap theme={null}
# Build the SAC Manager
stellar contract build --release

# Deploy the SAC Manager
stellar contract deploy \
  --wasm target/wasm32v1-none/release/sac_manager.wasm \
  --network testnet \
  --source my-account \
  -- \
  --sac_token <TOKEN_ADDRESS> \
  --owner <DEPLOYER_PUBLIC_KEY>

# Set SAC Manager as the token admin (so it can mint)
stellar contract invoke \
  --id <TOKEN_ADDRESS> \
  --network testnet \
  --source my-account \
  -- \
  set_admin \
  --new_admin <SAC_MANAGER_ADDRESS>
```

<Warning>
  **The issuer account must be locked (master weight set to 0).** In Stellar classic assets, transfers from/to the issuer are equivalent to minting/burning. The issuer can always mint more tokens and perform other classic operations directly, even when an explicit admin (this contract) is set. If the issuer account is not locked, the RBAC model enforced by this contract can be bypassed, breaking the trust model.
</Warning>

### Step 3: Create and Deploy the OFT Contract

**Create OFT project**:

```bash wrap theme={null}
stellar contract init my-oft
cd my-oft
```

**Configure `Cargo.toml`** with LayerZero dependencies:

```toml wrap theme={null}
[package]
name = "my-oft"
version = "0.1.0"
edition = "2021"

[dependencies]
soroban-sdk = "25.1.1"
cfg-if = "1.0"
# LayerZero OFT dependencies
# oapp = { ... }
# oapp-macros = { ... }
# oft-core = { ... }
# endpoint-v2 = { ... }
# common-macros = { ... }
# utils = { ... }

[dev-dependencies]
soroban-sdk = { version = "25.1.1", features = ["testutils"] }

[lib]
crate-type = ["cdylib"]
```

<Info>
  Check the [OFT core](https://github.com/LayerZero-Labs/monorepo-external/tree/main/apps/oft-app/contracts/stellar/oft-core), [OApp contracts](https://github.com/LayerZero-Labs/monorepo-external/tree/main/apps/oapp-app/contracts/stellar), [protocol contracts](https://github.com/LayerZero-Labs/monorepo-external/tree/main/contracts/protocol/stellar/contracts) on LayerZero GitHub for the latest Stellar contract packages.
</Info>

**Implement OFT**:

```rust wrap theme={null}
#![no_std]
use soroban_sdk::{Address, Env};
use common_macros::{contract_impl, lz_contract, storage};
use oapp_macros::oapp;
use oft_core::{impl_oft_lz_receive, OFTCore, OFTInternal};
use oft::{lock_unlock, mint_burn, OftType};

// The #[lz_contract] macro generates contract, ownable, TTL traits
// The #[oapp] macro generates OApp trait implementations
// Storage for OFT-specific state
#[storage]
enum OFTStorage {
    #[instance(OftType)]
    OftType,
}

#[lz_contract]
#[oapp]
pub struct OFT;

// Handle incoming crosschain messages
impl_oft_lz_receive!(OFT);

// Constructor
#[contract_impl]
impl OFT {
    pub fn __constructor(
        env: &Env,
        token: &Address,           // SEP-41 token address
        shared_decimals: u32,      // Crosschain decimal precision (typically 6)
        oft_type: OftType,         // LockUnlock or MintBurn(mintable_address)
        endpoint: &Address,        // LayerZero Endpoint V2 address
        delegate: &Address,        // Initial owner and endpoint delegate
    ) {
        Self::__initialize_oft(env, token, shared_decimals, delegate, endpoint, delegate);
        OFTStorage::set_oft_type(env, &oft_type);
    }
}

// Expose OFTCore public methods (quote_oft, quote_send, send)
// All methods have default implementations — override to customize (e.g., fee details, rate limits)
#[contract_impl(contracttrait)]
impl OFTCore for OFT {}

// Internal OFT logic — you must implement __debit() and __credit()
impl OFTInternal for OFT {
    /// Debits tokens from the sender for crosschain transfer.
    /// Returns (amount_sent, amount_received) after dust removal and fees.
    fn __debit(env: &Env, from: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
        match Self::oft_type(env) {
            OftType::LockUnlock => {
                // Transfer tokens from sender to this contract (lock)
                lock_unlock::debit::<Self>(env, &Self::token(env), from, amount_ld, min_amount_ld, dst_eid)
            }
            OftType::MintBurn(_) => {
                // Burn tokens directly from the sender
                mint_burn::debit::<Self>(env, &Self::token(env), from, amount_ld, min_amount_ld, dst_eid)
            }
        }
    }

    /// Credits tokens to the recipient after receiving a crosschain transfer.
    /// Returns the amount actually credited.
    fn __credit(env: &Env, to: &Address, amount_ld: i128, src_eid: u32) -> i128 {
        match Self::oft_type(env) {
            OftType::LockUnlock => {
                // Transfer tokens from this contract to recipient (unlock)
                lock_unlock::credit::<Self>(env, &Self::token(env), to, amount_ld, src_eid)
            }
            OftType::MintBurn(mintable) => {
                // Mint tokens to the recipient via the Mintable contract
                mint_burn::credit::<Self>(env, &mintable, to, amount_ld, src_eid)
            }
        }
    }
}
```

The key traits generated and implemented:

| Trait               | Description                                                                                           |
| ------------------- | ----------------------------------------------------------------------------------------------------- |
| `OFTCore`           | Public methods: `quote_oft()`, `quote_send()`, `send()`. Expose via `#[contract_impl(contracttrait)]` |
| `OFTInternal`       | `__debit()` and `__credit()` — defines how tokens are locked/burned and unlocked/minted               |
| `LzReceiveInternal` | Handles incoming messages via `impl_oft_lz_receive!` macro. Decodes OFT messages and credits tokens   |

**Build and deploy**:

```bash wrap theme={null}
# Build the OFT contract
stellar contract build

# Deploy the OFT contract
stellar contract deploy \
  --wasm target/wasm32v1-none/release/my_oft.wasm \
  --network testnet \
  --source my-account \
  -- \
  --token <TOKEN_ADDRESS> \
  --shared_decimals 6 \
  --oft_type '{"MintBurn":"<SAC_MANAGER_ADDRESS>"}' \
  --endpoint <ENDPOINT_ADDRESS> \
  --delegate <DELEGATE_ADDRESS>
```

**Constructor parameters:**

| Parameter         | Type      | Description                                                    |
| ----------------- | --------- | -------------------------------------------------------------- |
| `token`           | `Address` | SEP-41 token contract address                                  |
| `shared_decimals` | `u32`     | Crosschain decimal precision (typically 6)                     |
| `oft_type`        | `OftType` | `LockUnlock` or `MintBurn(mintable_address)`                   |
| `endpoint`        | `Address` | LayerZero Endpoint V2 address                                  |
| `delegate`        | `Address` | Initial contract owner and delegate for endpoint configuration |

<Note>
  Unlike the OApp constructor (which takes separate `owner` and `delegate` parameters), the OFT constructor uses `delegate` as both the initial owner and the endpoint delegate.
</Note>

### Step 4: Grant Minting Permissions

Grant the `MINTER_ROLE` on the SAC Manager to the OFT contract, so it can mint tokens during crosschain receives:

```bash wrap theme={null}
stellar contract invoke \
  --id <SAC_MANAGER_ADDRESS> \
  --network testnet \
  --source my-account \
  -- \
  grant_role \
  --account <OFT_ADDRESS> \
  --role MINTER_ROLE \
  --caller <DEPLOYER_PUBLIC_KEY>
```

### Step 5: Configure the OFT

The `set_peer` and `set_enforced_options` functions require the contract's **AUTHORIZER** (the owner by default). After deployment, configure peers and enforced options:

```bash wrap theme={null}
# Set peer for each remote chain
stellar contract invoke \
  --id <OFT_ADDRESS> \
  --network testnet \
  --source my-account \
  -- \
  set_peer \
  --eid <REMOTE_EID> \
  --peer <REMOTE_OFT_BYTES32> \
  --operator <OWNER_ADDRESS>
```

<Warning>
  The Stellar CLI (v25.1.0) has a known bug with `Option<BytesN<32>>` arguments -- `set_peer` may silently set the peer to `None`. If this happens, use the JavaScript SDK workaround described in the [OApp overview](/v2/developers/stellar/oapp/overview#step-3-configure-peers).
</Warning>

```bash wrap theme={null}
# Set enforced options
stellar contract invoke \
  --id <OFT_ADDRESS> \
  --network testnet \
  --source my-account \
  -- \
  set_enforced_options \
  --options '[{"eid": <DST_EID>, "msg_type": 1, "options": "<OPTIONS_HEX>"}]' \
  --operator <OWNER_ADDRESS>
```

### Using LockUnlock Mode

If you don't have mint authority over the token (e.g., bridging an existing asset), use **LockUnlock** mode instead. The OFT holds tokens in its own balance — locking on send, unlocking on receive.

No SAC Manager or Mintable contract is needed. Simply deploy the OFT with `LockUnlock`:

```bash wrap theme={null}
stellar contract deploy \
  --wasm target/wasm32v1-none/release/my_oft.wasm \
  --network testnet \
  --source my-account \
  -- \
  --token <TOKEN_ADDRESS> \
  --shared_decimals 6 \
  --oft_type "LockUnlock" \
  --endpoint <ENDPOINT_ADDRESS> \
  --delegate <DELEGATE_ADDRESS>
```

Then proceed to [Step 5: Configure the OFT](#step-5-configure-the-oft).

## Core Operations

<Warning>
  **Do not use the issuer account as a sender or recipient.** On Stellar, transfers from/to the token issuer are equivalent to minting/burning — the issuer does not hold a balance of its own asset. Tokens sent to the issuer are silently destroyed, which would cause asset loss in crosschain transfers.

  When testing OFT transfers, always use a **non-issuer** account. To set up a non-issuer holder:

  1. Generate a separate keypair and fund it via Friendbot
  2. Add a trustline for the custom asset: `Operation.changeTrust({ asset })`
  3. Transfer tokens from the issuer to the holder: `Operation.payment({ destination, asset, amount })`
</Warning>

### Sending Tokens

Sending tokens crosschain involves quoting the fee and executing the transfer:

#### Step 1: Build SendParam

```rust wrap theme={null}
let send_param = SendParam {
    dst_eid: 30101,                    // Destination endpoint ID
    to: recipient_bytes32,             // Recipient address (32 bytes)
    amount_ld: 1_000_000_000,         // Amount in local decimals
    min_amount_ld: 990_000_000,       // Minimum after fees (slippage protection)
    extra_options: Bytes::new(&env),   // Additional execution options
    compose_msg: Bytes::new(&env),     // Optional compose message
    oft_cmd: Bytes::new(&env),         // Custom OFT command
};
```

#### Step 2: Quote the Transfer

```rust wrap theme={null}
// Preview the transfer (limits, fees, expected amounts after dust removal)
let (limits, fee_details, receipt) = oft.quote_oft(&from, &send_param);

// Quote the LayerZero messaging fee
let fee = oft.quote_send(&from, &send_param, &false);
```

`quote_oft` returns:

| Return        | Type                | Description                                                           |
| ------------- | ------------------- | --------------------------------------------------------------------- |
| `limits`      | `OFTLimit`          | Min/max amounts in local decimals                                     |
| `fee_details` | `Vec<OFTFeeDetail>` | Breakdown of OFT-level fees                                           |
| `receipt`     | `OFTReceipt`        | Expected `amount_sent_ld` and `amount_received_ld` after dust removal |

Both `quote_oft` and `quote_send` are read-only — they do not execute any state changes.

#### Step 3: Execute Send

```rust wrap theme={null}
let (messaging_receipt, oft_receipt) = oft.send(
    &from,            // Sender (must authorize)
    &send_param,
    &fee,             // From quote_send
    &refund_address,  // Excess fee refund recipient
);
```

The sender must authorize the call (`from.require_auth()` is called internally).

### Receiving Tokens

Token receipt is handled automatically by the OFT contract's `lz_receive` implementation:

1. Decode the `OFTMessage` from the inbound payload
2. Resolve the recipient address — the crosschain OFT message carries only the 32-byte address payload, but Stellar addresses are 35 bytes (1-byte version + 32-byte payload + 2-byte checksum) with two possible types (C-address for contracts, G-address for accounts). The `resolve_address` utility disambiguates by checking if a contract exists at the 32-byte address; if so, it resolves to a C-address, otherwise it falls back to a G-address. This is handled automatically.
3. Convert amount from shared decimals to local decimals
4. Credit tokens:
   * **LockUnlock**: `token.transfer(from: oft_contract, to: recipient, amount)`
   * **MintBurn**: `mintable.mint(to: recipient, amount, operator: oft_contract)`
5. If a compose message is included, register it via `endpoint.send_compose` for subsequent execution
6. Emit `OFTReceived` event

## Decimal Precision

OFTs use a **shared decimal** system for crosschain consistency. The shared decimals value determines the precision used in the crosschain message, while local decimals are the token's actual precision.

| Parameter                 | Description                           | Example              |
| ------------------------- | ------------------------------------- | -------------------- |
| `local_decimals`          | Token's native decimals (from SEP-41) | 7 (Stellar standard) |
| `shared_decimals`         | Crosschain precision                  | 6                    |
| `decimal_conversion_rate` | `10^(local - shared)`                 | 10                   |

<Warning>
  Any amount below the `decimal_conversion_rate` is considered **dust** and is removed before sending. For example, with a conversion rate of 10, an amount of `1,000,015` becomes `1,000,010` (the trailing 5 is dust).
</Warning>

**Recommended configuration:**

| Source Token Decimals | Recommended `shared_decimals` | Conversion Rate |
| --------------------- | ----------------------------- | --------------- |
| 7 (Stellar default)   | 6                             | 10              |
| 8                     | 6                             | 100             |
| 18 (EVM default)      | 6                             | 10^12           |

## OFT Extensions

The Stellar OFT supports three optional extensions that can be enabled at deployment:

### Pausable

Pause and unpause all OFT operations (send, receive, quote):

```rust wrap theme={null}
// Pause the OFT (requires PAUSER role)
oft.pause(&operator);

// Unpause the OFT (requires UNPAUSER role)
oft.unpause(&operator);

// Check pause status
let paused = oft.is_paused();
```

When paused, `send`, `quote_send`, `quote_oft`, and `lz_receive` (credit) all revert with error code `3110` (`Paused`).

### OFT Fee

Charge a fee on outbound transfers, configurable per destination or as a default:

```rust wrap theme={null}
// Set a default fee of 0.5% (50 bps) -- requires FEE_CONFIG_MANAGER_ROLE
oft.set_default_fee_bps(&Some(50), &operator);

// Override for a specific destination (1% fee to chain 30101)
oft.set_fee_bps(&30101, &Some(100), &operator);

// Explicitly set 0% fee for a destination (overrides default)
oft.set_fee_bps(&30102, &Some(0), &operator);

// Remove destination override (falls back to default)
oft.set_fee_bps(&30102, &None, &operator);

// Set where fees are collected -- requires AUTHORIZER (contract owner)
oft.set_fee_deposit_address(&Some(treasury_address), &operator);
```

**Fee resolution order:** Per-destination fee > Default fee > 0 (no fee)

Fee calculation: `fee = amount_ld * fee_bps / 10_000`

### Rate Limiter

Control transfer volume with a **leaky bucket** algorithm:

```rust wrap theme={null}
use oft::rate_limiter::{Direction, Mode, RateLimitConfig};

// Set outbound rate limit: 1M tokens per hour -- requires RATE_LIMITER_MANAGER_ROLE
oft.set_rate_limit(&Direction::Outbound, &dst_eid, &Some(RateLimitConfig {
    limit: 1_000_000_000_000,  // In local decimals
    window_seconds: 3600,
    mode: Mode::Net,           // Net: inbound releases decrement outbound in-flight
}), &operator);

// Check current capacity
let capacity = oft.rate_limit_capacity(&Direction::Outbound, &dst_eid);
let in_flight = oft.rate_limit_in_flight(&Direction::Outbound, &dst_eid);
```

**Rate limit modes:**

| Mode    | Behavior                                                                             |
| ------- | ------------------------------------------------------------------------------------ |
| `Net`   | Inbound credits release outbound capacity (and vice versa). Good for balanced flows. |
| `Gross` | Each direction tracked independently. More restrictive.                              |

## OFT Wire Format

For developers building custom integrations, the OFT message wire format:

```
[send_to: 32 bytes][amount_sd: 8 bytes][compose_from: 32 bytes (optional)][compose_msg: variable (optional)]
```

* **Message type 1** (`SEND`): `send_to` + `amount_sd`
* **Message type 2** (`SEND_AND_CALL`): `send_to` + `amount_sd` + `compose_from` + `compose_msg`

## Events

| Event                  | Topics                    | Data                                                  |
| ---------------------- | ------------------------- | ----------------------------------------------------- |
| `OFTSent`              | `guid`, `dst_eid`, `from` | `amount_sent_ld`, `amount_received_ld`                |
| `OFTReceived`          | `guid`, `src_eid`, `to`   | `amount_received_ld`                                  |
| `MsgInspectorSet`      | --                        | `inspector: Option<Address>`                          |
| `PausedSet`            | --                        | `paused: bool`                                        |
| `DefaultFeeBpsSet`     | --                        | `fee_bps: Option<u32>`                                |
| `FeeBpsSet`            | --                        | `dst_eid`, `fee_bps: Option<u32>`                     |
| `FeeDepositAddressSet` | --                        | `fee_deposit_address: Option<Address>`                |
| `RateLimitSet`         | --                        | `direction`, `eid`, `config: Option<RateLimitConfig>` |

## Best Practices & Deployment Checklist

* **Set `shared_decimals` carefully** -- it cannot be changed after deployment. Use 6 for compatibility with most chains.
* **Configure peers on both sides** -- the remote OFT must also set your Stellar OFT as a peer.
* **Set enforced options** -- always configure minimum gas for each destination to prevent failed deliveries.
* **Test with small amounts** -- verify the full send/receive flow with minimal amounts before large transfers.
* **Monitor rate limits** -- if enabled, ensure limits are set appropriately for expected volume.
* **Grant minting permissions** -- for MintBurn mode, ensure the OFT has the required role on the Mintable contract.

## Next Steps

* **[DVN & Executor Config](/v2/developers/stellar/configuration/dvn-executor-config)**: Configure your security stack.
* **[Technical Overview](/v2/developers/stellar/technical-overview)**: Understand the full message lifecycle.
* **[Troubleshooting](/v2/developers/stellar/troubleshooting/common-errors)**: Common errors and solutions.
