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

# Extensions

> Cross-chain extensions (Fee, Rate Limiter, PauseByID) and token-level controls (Allowlist, Pause).

Stablecoin OFT enforces security policies at two levels (see [Architecture](/v2/developers/evm/stablecoin-oft/architecture)):

* **Cross-chain extensions** (Fee, Rate Limiter, PauseByID) — mixed into the OFT via `OFTCoreExtendedRBACUpgradeable`, configured per destination chain (EID). These intercept `_debit` and `_credit` on cross-chain sends and receives.
* **Token controls** (Allowlist, Pause) — built into `ERC20Plus`, applied to **all** token operations including local transfers. Documented in detail on the [ERC20Plus](/v2/developers/evm/stablecoin-oft/erc20plus) page; summarized here for completeness.

## Overview

| Module           | Layer | Purpose                                        | Granularity               | Where Applied                              |
| ---------------- | ----- | ---------------------------------------------- | ------------------------- | ------------------------------------------ |
| **Fee**          | OFT   | Collect basis-point fees on outbound transfers | Per-destination + default | `_debitView()`                             |
| **Rate Limiter** | OFT   | Token bucket rate limits with linear decay     | Per-destination + default | `_debit()` and `_credit()`                 |
| **PauseByID**    | OFT   | Halt transfers to/from specific destinations   | Per-destination + default | `_debit()` modifier                        |
| **Allowlist**    | Token | Restrict token holders by address              | Global (on `ERC20Plus`)   | `transfer()` / `transferFrom()` / `burn()` |
| **Pause**        | Token | Halt all token operations globally             | Global (on `ERC20Plus`)   | `transfer()` / `transferFrom()` / `burn()` |

## Fee Module

Collects fees in basis points (BPS) on outbound cross-chain transfers. Fees are deducted from the transfer amount before it reaches the destination chain.

### How It Works

1. User calls `send()` with `amountLD`
2. `_debitView()` calculates the fee: `fee = (amountLD * feeBps) / 10_000`
3. `amountReceivedLD = removeDust(amountLD - fee)`
4. `amountSentLD = amountLD` (the user pays the full amount)
5. The difference (`amountSentLD - amountReceivedLD`) is retained as fee

### Configuration

**Default fee** applies to all destinations unless overridden:

```solidity theme={null}
function setDefaultFeeBps(uint16 _feeBps) external; // FEE_CONFIG_MANAGER_ROLE
```

**Per-destination override:**

```solidity theme={null}
function setFeeBps(
    uint256 _id,        // Destination EID
    uint16 _feeBps,     // Fee in BPS (0-10000)
    bool _enabled       // true = use this override; false = fall back to default
) external; // FEE_CONFIG_MANAGER_ROLE
```

When `enabled` is `false`, the destination falls back to the default fee.

**Fee settlement is push-based across all OFT variants.** When fees are collected during `_debit`, they are transferred immediately to the fee deposit address supplied at initialization. Treasury accounting should follow normal ERC20 `Transfer` (or native) inflows to that address rather than a separate withdrawal action.

### Constants

```solidity theme={null}
uint16 public constant BPS_DENOMINATOR = 10_000;
```

A fee of 50 BPS = 0.50%. Setting `_feeBps > BPS_DENOMINATOR` reverts with `InvalidBps(feeBps)`.

### Roles

| Role                      | Functions                           |
| ------------------------- | ----------------------------------- |
| `FEE_CONFIG_MANAGER_ROLE` | `setDefaultFeeBps()`, `setFeeBps()` |

## Rate Limiter Module

Enforces transfer volume limits using a token bucket algorithm with linear decay. Supports per-destination limits for both outbound (send) and inbound (receive) directions.

### How It Works

The rate limiter uses a token bucket model:

1. Outbound transfers consume capacity; inbound transfers replenish it (when net accounting is enabled)
2. Capacity regenerates linearly over the configured time window
3. If a transfer would exceed available capacity, it reverts with `RateLimitExceeded`

**Example:** With a limit of 1,000,000 tokens and a window of 3,600 seconds (1 hour), the regeneration rate is \~277.78 tokens/second:

* At t=0: Available = 1,000,000
* User sends 800,000 tokens. Available = 200,000
* At t=1800 (30 min): 500,000 regenerated. Available = 700,000
* At t=3600 (1 hr): Fully regenerated. Available = 1,000,000
* If 300,000 tokens arrive inbound at any point, available increases by 300,000 (capped at limit)

### Configuration

**Global configuration:**

```solidity theme={null}
function setRateLimitGlobalConfig(RateLimitGlobalConfig memory _globalConfig) external;
// RATE_LIMITER_MANAGER_ROLE

struct RateLimitGlobalConfig {
    bool useGlobalState;      // Use single bucket for all destinations
    bool isGloballyDisabled;  // Disable all rate limiting
}
```

**Per-destination configuration:**

```solidity theme={null}
function setRateLimitConfigs(SetRateLimitConfigParam[] calldata _params) external;
// RATE_LIMITER_MANAGER_ROLE

struct RateLimitConfig {
    bool overrideDefaultConfig;    // true = use this config; false = use default
    bool outboundEnabled;          // Enable outbound rate limit
    bool inboundEnabled;           // Enable inbound rate limit
    bool netAccountingEnabled;     // Offset outflow with inflows
    bool addressExemptionEnabled;  // Allow per-address exemptions
    uint96 outboundLimit;          // Max outbound tokens in window
    uint96 inboundLimit;           // Max inbound tokens in window
    uint32 outboundWindow;         // Outbound decay window (seconds)
    uint32 inboundWindow;          // Inbound decay window (seconds)
}
```

**Manual state override** (for emergency adjustments):

```solidity theme={null}
function setRateLimitStates(SetRateLimitStateParam[] calldata _params) external;
// RATE_LIMITER_MANAGER_ROLE

struct RateLimitState {
    uint96 outboundUsage;   // Current outbound usage
    uint96 inboundUsage;    // Current inbound usage
    uint40 lastUpdated;     // Timestamp (cannot be in the future)
}
```

**Address exemptions:**

```solidity theme={null}
function setRateLimitAddressExemptions(
    SetRateLimitAddressExceptionParam[] calldata _exemptions
) external; // RATE_LIMITER_MANAGER_ROLE

struct SetRateLimitAddressExceptionParam {
    address user;
    bool isExempt;
}
```

Exemptions only apply when `addressExemptionEnabled` is `true` in the destination's config.

**Checkpoint** (call before changing limits or windows):

```solidity theme={null}
function checkpointRateLimits(uint256[] calldata _ids) external;
// RATE_LIMITER_MANAGER_ROLE
```

Writes decayed usages to storage so new config applies to the current state rather than stale values.

### Net vs Gross Accounting

When `netAccountingEnabled` is `true`:

* Outbound transfers reduce inbound usage (and vice versa)
* This allows "round-trip" capacity: if 500k tokens leave and 500k arrive, net usage is zero

When `netAccountingEnabled` is `false`:

* Outbound and inbound are tracked independently
* Each direction has its own separate bucket

### Scaling

Rate limit amounts are stored as `uint96`. For tokens with amounts exceeding `type(uint96).max` (\~79 billion with 18 decimals), use the `SCALE_DECIMALS` constructor parameter to downscale amounts. For example, `SCALE_DECIMALS = 6` divides all amounts by `10^6` before storing.

### Roles

| Role                        | Functions                                                                                                                                  |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `RATE_LIMITER_MANAGER_ROLE` | `setRateLimitGlobalConfig()`, `setRateLimitConfigs()`, `setRateLimitStates()`, `setRateLimitAddressExemptions()`, `checkpointRateLimits()` |

## PauseByID Module

Per-destination pause controls that halt outbound transfers to specific chains without affecting the rest of the network.

### How It Works

The `_debit()` function includes a `whenNotPaused(_dstEid)` modifier. If the destination EID is paused, the transaction reverts with `Paused(uint256 id)`.

Pause logic evaluates:

1. Is there a specific `PauseConfig` for this destination with `enabled = true`?
2. If yes, use that config's `paused` value
3. If no, use the `defaultPaused` value

### Configuration

**Default pause** (applies to all destinations without specific config):

```solidity theme={null}
function setDefaultPaused(bool _paused) external; // PAUSER_ROLE or UNPAUSER_ROLE
```

**Per-destination pause:**

```solidity theme={null}
function setPaused(SetPausedParam[] calldata _params) external;
// PAUSER_ROLE or UNPAUSER_ROLE

struct SetPausedParam {
    uint256 id;       // Destination EID
    bool paused;      // Whether to pause
    bool enabled;     // true = use this config; false = fall back to default
}
```

### Use Cases

* **Chain compromise:** Pause a single destination without halting all cross-chain operations
* **Maintenance:** Temporarily halt transfers to a chain during upgrades
* **Regulatory action:** Block transfers to/from a specific chain

### Interaction with quoteOFT

When a destination is paused, `quoteOFT()` returns `maxAmountLD = 0` in the `OFTLimit`, signaling to UIs that no transfer is possible.

## Token-Level Controls (on `ERC20Plus`)

The following controls live on the token itself, not on the OFT. They apply to **all** token operations — local transfers and cross-chain sends alike.

### Allowlist

The allowlist system operates in one of three modes at any time:

| Mode          | Behavior                      | Who Can Transfer                      |
| ------------- | ----------------------------- | ------------------------------------- |
| **Open**      | No restrictions               | Everyone                              |
| **Blacklist** | Block specific addresses      | Everyone except blacklisted addresses |
| **Whitelist** | Allow only specific addresses | Only whitelisted addresses            |

**Mode transitions** are controlled by `DEFAULT_ADMIN_ROLE`. Switching modes does not clear existing lists — the blacklist and whitelist are maintained independently and apply only when their respective mode is active.

Both lists are implemented as OpenZeppelin `EnumerableSet.AddressSet`, supporting:

* `blacklistedCount()` / `whitelistedCount()` for total counts
* `getBlacklist(offset, limit)` / `getWhitelist(offset, limit)` for paginated enumeration
* `isBlacklisted(address)` / `isWhitelisted(address)` for individual queries

The allowlist check (`isAllowlisted`) is enforced on `transfer`, `transferFrom`, and `burn` for both sender and receiver.

| Role                 | Functions            |
| -------------------- | -------------------- |
| `DEFAULT_ADMIN_ROLE` | `setAllowlistMode()` |
| `BLACKLISTER_ROLE`   | `setBlacklisted()`   |
| `WHITELISTER_ROLE`   | `setWhitelisted()`   |

### Fund Recovery

For compliance scenarios (e.g., court orders, sanctions enforcement), addresses holding `DEFAULT_ADMIN_ROLE` can transfer tokens away from non-allowlisted addresses:

```solidity theme={null}
function recoverFunds(address _from, address _to, uint256 _amount) external;
```

`_from` must NOT be allowlisted under the current mode. Attempting to recover from an allowlisted address reverts with `CannotRecoverFromAllowlisted(address user)`.

### Pause (Global)

Halts all `transfer`, `transferFrom`, and `burn` calls. Unlike `PauseByID` (which targets individual destinations), global pause blocks everything — local and cross-chain.

| Role            | Functions   |
| --------------- | ----------- |
| `PAUSER_ROLE`   | `pause()`   |
| `UNPAUSER_ROLE` | `unpause()` |

The pause/unpause split ensures a compromised pauser key cannot also undo a legitimate security pause.

## Execution Order

On an outbound `send()`, modules execute in this order:

```
1. PauseByID      → whenNotPaused(dstEid) modifier on _debit()
2. Fee            → _debitView() calculates fee, reduces amountReceivedLD
3. Rate Limiter   → _outflow() checks and updates outbound bucket
4. Token Transfer → burn/lock/wrap the tokens
```

On an inbound receive (`_lzReceive`):

```
1. Rate Limiter   → _inflow() checks and updates inbound bucket
2. Token Transfer → mint/unlock/unwrap the tokens
```

## Next Steps

* [RBAC Reference](/v2/developers/evm/stablecoin-oft/rbac-reference) for the complete role-to-function mapping
* [OFTs](/v2/developers/evm/stablecoin-oft/ofts) for deployable variants and initialization (including fee deposit)
