# Set Token Pool Rate Limits Using Hardhat
Source: https://docs.chain.link/ccip/evm/tutorials/cross-chain-tokens/update-rate-limiters-hardhat
Last Updated: 2026-04-27

> For the complete documentation index, see [llms.txt](/llms.txt).

## Guide Versions

- [Foundry](/ccip/evm/tutorials/cross-chain-tokens/update-rate-limiters-foundry)

- [Hardhat](/ccip/evm/tutorials/cross-chain-tokens/update-rate-limiters-hardhat)

In this tutorial you will:

1. Review current standard rate limiter configurations for your deployed token pool.
2. Update outbound and inbound standard rate limiter configurations.
3. Verify the updated configurations.
4. Test standard rate limiter enforcement with a cross-chain token transfer.
5. Optionally configure and test rate limits for faster than finality transfers.

## Before You Begin

### 1. Set up your development environment

1. Install Node.js and npm:
   - Make sure you have `Node.js v22.10.0` or above installed. If not, install **`Node.js v22.10.0`** using their [documentation](https://nodejs.org/en/download/).
   - `npm` is bundled with Node.js. If you can’t run `npm`, reinstall/update Node.js from the official installer.

2. Install/Update [Chainlink CCIP-CLI](https://docs.chain.link/ccip/tools/cli/). You can also find the [GitHub repository here](https://github.com/smartcontractkit/ccip-cli).

```bash filename="Terminal"
npm install -g @chainlink/ccip-cli
```

Verify the installation by running the following command:

```bash filename="Terminal"
ccip-cli --version
```

3. **Clone the repository and navigate to the project directory**:

[CCIP 2.0 template](https://github.com/smartcontractkit/docs-cct-hardhat)

Clone the CCIP 2.0 Hardhat template for a smoother CCT setup.

```bash filename="Terminal"
git clone https://github.com/smartcontractkit/docs-cct-hardhat.git
cd docs-cct-hardhat
```

4. Create a `.env` file by copying the `.env.example` file, and fill in the required values:

```bash filename="Terminal"
cp .env.example .env
```

```bash filename=".env"
# Keystore name (created via `npx hardhat keystore set`)
KEYSTORE_NAME=<your_private_key_name>

# RPC URLs (add the ones you need)
ETHEREUM_SEPOLIA_RPC_URL=your_eth_sepolia_rpc
ARBITRUM_SEPOLIA_RPC_URL=your_arbitrum_sepolia_rpc

# Etherscan API key (required only if you pass --verify to deployment tasks)
ETHERSCAN_API_KEY=your_etherscan_api_key
```

[Complete List of Supported Chains](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/helper-config.ts)

View the complete list of supported chains in the `helper-config.ts` file.

5. To make sure your terminal has access to these variables, run the following command:

```bash filename="Terminal"
source .env
```

6. Build the project:

```bash filename="Terminal"
npm install && npx hardhat compile
```

7. Create an encrypted Hardhat keystore, if you haven't already:

```bash filename="Terminal"
npx hardhat keystore set <your_private_key_name>
```

> **CAUTION: Private key format**
>
> When prompted for your private key, include the `0x` prefix.

> **NOTE: Note**
>
> 1. Hardhat prompts for a keystore password: set it on first use, or enter it to unlock on subsequent uses. Then it
>    asks you to enter your private key as hidden text.
> 2. The name you choose (e.g. `<your_private_key_name>`) is what you set as `KEYSTORE_NAME` in `.env`.
> 3. Use `npx hardhat keystore list` to check previously created keystores.

## Tutorial

> **TIP: Prerequisites**
>
> This tutorial assumes you have already deployed tokens and token pools on both chains. If not, complete one of the
> registration tutorials first:
>
> - [Burn & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
> - [Lock & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
>
> Make sure your token and token pool addresses are exported as environment variables:
>
> ```bash
> # For tokens
> export ETHEREUM_SEPOLIA_TOKEN=0x...
> export ARBITRUM_SEPOLIA_TOKEN=0x...
>
> # For token pools
> export ETHEREUM_SEPOLIA_TOKEN_POOL=0x...
> export ARBITRUM_SEPOLIA_TOKEN_POOL=0x...
> ```
>
> This tutorial supports both CCT registration models:
>
> - In the Burn & Mint model, rate limiters are configured on the `BurnMintTokenPool` on each chain.
> - In the Lock & Mint model, rate limiters are configured on the token pools: the source-chain `LockReleaseTokenPool` and the destination-chain `BurnMintTokenPool`. The `ERC20LockBox` is not rate-limited and is not updated by the tasks in this tutorial.

### 1. Review Current Rate Limiter Configurations

Use the `getCurrentRateLimits` task to read the current rate limiter configuration state for a specific lane from your token pool. The task reports whether rate limiting is enabled, the maximum capacity (bucket size), the refill rate (tokens per second), and the current token count in each bucket.

[getCurrentRateLimits.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/rate-limiter/getCurrentRateLimits.ts)

View the rate limiter query task on GitHub.

| Flag             | Required | Description                                                                                                                        |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `--destchain`    | Yes      | Remote chain whose lane is being queried (e.g. `arbitrumSepolia`)                                                                  |
| `--fastfinality` | No       | Read the faster than finality bucket instead of the standard finality bucket (v2 pools only; falls back to standard per direction) |

1. Check the Ethereum Sepolia pool (lane to Arbitrum Sepolia):

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain arbitrumSepolia \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📊 Get Current Rate Limits
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View rate limits
Bucket:       Standard finality
========================================

Pool Version: v2

  Outbound Enabled:  false
  Outbound Capacity: 0
  Outbound Rate:     0
  Outbound Tokens:   0
  Inbound  Enabled:  false
  Inbound  Capacity: 0
  Inbound  Rate:     0
  Inbound  Tokens:   0

========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
========================================
```

2. Check the Arbitrum Sepolia pool (lane to Ethereum Sepolia):

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain sepolia \
  --network arbitrumSepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📊 Get Current Rate Limits
========================================
Chain:        Arbitrum Sepolia
Remote Chain: Ethereum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       View rate limits
Bucket:       Standard finality
========================================

Pool Version: v2

  Outbound Enabled:  false
  Outbound Capacity: 0
  Outbound Rate:     0
  Outbound Tokens:   0
  Inbound  Enabled:  false
  Inbound  Capacity: 0
  Inbound  Rate:     0
  Inbound  Tokens:   0

========================================
Token Pool:   https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================
```

> **NOTE: Rate Limiters Disabled**
>
> 1. Rate limiting is disabled by default when no rate-limiting parameters are provided during
>    `applyChainUpdates`. In that case, both outbound and inbound capacities are `0`, meaning
>    there are no restrictions on token flow.
> 2. In the next step you will enable and configure these rate limiters.

### 2. Update Rate Limiter Configurations

Use the `updateRateLimiters` task to update rate limiter configurations for an existing lane in your token pool. The direction to update is inferred automatically from whichever `--outbound*` / `--inbound*` flags you set.

Only the directions you specify are updated; any direction you don't specify is left unchanged. Outbound and inbound are relative to the pool you're configuring:

1. `outbound` is tokens leaving this pool toward the `--destchain` remote chain;
2. `inbound` is tokens arriving from the `--destchain` remote chain into this pool.

> **NOTE: Lane must exist**
>
> 1. Rate limiters can only be updated for lanes that already exist.
> 2. If you haven't configured the lane yet, complete one of the two tutorials first:
>
>    - [Burn & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
>    - [Lock & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
> 3. Run `applyChainUpdates` to configure the lane.

[updateRateLimiters.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/rate-limiter/updateRateLimiters.ts)

View the rate limiter update task on GitHub.

| Flag                 | Required           | Description                                                                                        |
| -------------------- | ------------------ | -------------------------------------------------------------------------------------------------- |
| `--destchain`        | Yes                | Remote chain whose lane is being updated                                                           |
| `--outboundcapacity` | To update outbound | Token bucket capacity for outbound transfers                                                       |
| `--outboundrate`     | To update outbound | Token bucket refill rate (tokens/second) for outbound transfers                                    |
| `--outboundenabled`  | No                 | Override `isEnabled` explicitly (`true`/`false`; defaults to `true` when capacity or rate are set) |
| `--inboundcapacity`  | To update inbound  | Token bucket capacity for inbound transfers                                                        |
| `--inboundrate`      | To update inbound  | Token bucket refill rate (tokens/second) for inbound transfers                                     |
| `--inboundenabled`   | No                 | Override `isEnabled` explicitly (`true`/`false`; defaults to `true` when capacity or rate are set) |
| `--fastfinality`     | No                 | Update the faster than finality bucket instead of the standard finality bucket (v2 only)           |

> **NOTE: Access control and compatibility**
>
> 1. The pool owner and the designated `rateLimitAdmin` can update rate limiters.
> 2. The `rateLimitAdmin` is configured via `setDynamicConfig`.
> 3. This task supports both v1 and v2 token pools: on v2 pools it calls `setRateLimitConfig()`; on v1 pools
>    it falls back to `setChainRateLimiterConfig()`.
> 4. The `--fastfinality` flag is v2-only and is ignored on v1 pools.

> **NOTE: Configuration best practices**
>
> 1. Set inbound rate limits a bit higher than outbound limits on the same lane. Many chains finalize blocks in
>    batches, so multiple outbound messages may become executable on the destination simultaneously, requiring slightly
>    more inbound capacity.
> 2. The exact additional capacity required depends on the refill rate and the size of the source chain epochs and CCIP
>    round time. In most cases, a 5-10% buffer should be sufficient.
> 3. `rate` must be less than or equal to `capacity`, and when disabling rate limiting (
>    `isEnabled: false`), both `capacity` and `rate` must be `0`.

In this example, we configure an 18-decimal token with outbound capacity of 10 tokens (refilling at 0.1 tokens/sec) and inbound capacity of 11 tokens (refilling at 0.11 tokens/sec, a 10% buffer):

1. Update the Ethereum Sepolia pool (lane to Arbitrum Sepolia):

```bash filename="Terminal"
npx hardhat updateRateLimiters \
  --destchain arbitrumSepolia \
  --outboundcapacity 10000000000000000000 \
  --outboundrate 100000000000000000 \
  --inboundcapacity 11000000000000000000 \
  --inboundrate 110000000000000000 \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
⚡️ Update Rate Limiters
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       Update rate limits
Direction:    Both
Bucket:       Standard finality
========================================

Pool Version: v2 (setRateLimitConfig)

Current Rate Limiter State:
  Outbound Enabled:  false
  Outbound Capacity: 0
  Outbound Rate:     0
  Outbound Tokens:   0
  Inbound  Enabled:  false
  Inbound  Capacity: 0
  Inbound  Rate:     0
  Inbound  Tokens:   0

New Configuration:
  Outbound Enabled:  true
  Outbound Capacity: 10000000000000000000
  Outbound Rate:     100000000000000000
  Inbound Enabled:   true
  Inbound Capacity:  11000000000000000000
  Inbound Rate:      110000000000000000

[Step 1] Applying rate limit update on Ethereum Sepolia
⏳ Tx: 0xddeeff00112233445566778899aabbccddeeff00112233445566778899aabbcc

========================================
✅ Rate limiter update complete on Ethereum Sepolia!
========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Transaction:  0xddeeff00112233445566778899aabbccddeeff00112233445566778899aabbcc
========================================
```

2. Update the Arbitrum Sepolia pool (lane to Ethereum Sepolia):

```bash filename="Terminal"
npx hardhat updateRateLimiters \
  --destchain sepolia \
  --outboundcapacity 10000000000000000000 \
  --outboundrate 100000000000000000 \
  --inboundcapacity 11000000000000000000 \
  --inboundrate 110000000000000000 \
  --network arbitrumSepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
⚡️ Update Rate Limiters
========================================
Chain:        Arbitrum Sepolia
Remote Chain: Ethereum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       Update rate limits
Direction:    Both
Bucket:       Standard finality
========================================

Pool Version: v2 (setRateLimitConfig)

Current Rate Limiter State:
  Outbound Enabled:  false
  Outbound Capacity: 0
  Outbound Rate:     0
  Outbound Tokens:   0
  Inbound  Enabled:  false
  Inbound  Capacity: 0
  Inbound  Rate:     0
  Inbound  Tokens:   0

New Configuration:
  Outbound Enabled:  true
  Outbound Capacity: 10000000000000000000
  Outbound Rate:     100000000000000000
  Inbound Enabled:   true
  Inbound Capacity:  11000000000000000000
  Inbound Rate:      110000000000000000

[Step 1] Applying rate limit update on Arbitrum Sepolia
⏳ Tx: 0x2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70812

========================================
✅ Rate limiter update complete on Arbitrum Sepolia!
========================================
Token Pool:   https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Transaction:  0x2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70812
========================================
```

3. To **disable** rate limiters for a lane, pass `--outboundenabled false` and `--inboundenabled false`:

> **CAUTION: Disabling rate limiters**
>
> 1. We highly recommend that devs have at least some limits set up. We advise against completely disabling rate
>    limiters unless absolutely necessary.
> 2. **Informational-only for this tutorial:** If you disable rate limiters here, the later **Test the Rate Limiter Configurations** section will not behave as described (it will not revert
>    with `TokenMaxCapacityExceeded` / `TokenRateLimitReached`).

```bash filename="Terminal"
npx hardhat updateRateLimiters \
  --destchain arbitrumSepolia \
  --outboundenabled false \
  --inboundenabled false \
  --network sepolia
```

### 3. Verify the Updated Rate Limiter Configurations

Re-run the `getCurrentRateLimits` task on both chains to confirm the update was applied correctly.

1. Verify the Ethereum Sepolia pool:

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain arbitrumSepolia \
  --network sepolia
```

> **NOTE: Note**
>
> Your output might differ based on whether or not you disabled rate limiters in the previous step.

Your output should look something like this:

```bash filename="Terminal"
========================================
📊 Get Current Rate Limits
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View rate limits
Bucket:       Standard finality
========================================

Pool Version: v2

  Outbound Enabled:  true
  Outbound Capacity: 10000000000000000000
  Outbound Rate:     100000000000000000
  Outbound Tokens:   10000000000000000000
  Inbound  Enabled:  true
  Inbound  Capacity: 11000000000000000000
  Inbound  Rate:     110000000000000000
  Inbound  Tokens:   11000000000000000000

========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
========================================
```

2. Verify the Arbitrum Sepolia pool:

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain sepolia \
  --network arbitrumSepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📊 Get Current Rate Limits
========================================
Chain:        Arbitrum Sepolia
Remote Chain: Ethereum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       View rate limits
Bucket:       Standard finality
========================================

Pool Version: v2

  Outbound Enabled:  true
  Outbound Capacity: 10000000000000000000
  Outbound Rate:     100000000000000000
  Outbound Tokens:   10000000000000000000
  Inbound  Enabled:  true
  Inbound  Capacity: 11000000000000000000
  Inbound  Rate:     110000000000000000
  Inbound  Tokens:   11000000000000000000

========================================
Token Pool:   https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================
```

> **NOTE: Token bucket values**
>
> The `Tokens` field shows the current fill level of the bucket. Immediately after an update the bucket
> resets to full capacity. Over time, as transfers consume tokens, this value decreases and refills at the configured
> rate.

### 4. Test the Rate Limiter Configurations

Initiate a cross-chain transfer to verify that the rate limiter enforces the configured limits.

> **NOTE: Token balance**
>
> Ensure your EOA has a sufficient balance of the cross-chain token on the source chain. If you need more tokens, use
> the `mintTokens` task or transfer tokens from another account.

Before testing, export the router and token addresses for the source chain:

```bash filename="Terminal"
export ETHEREUM_SEPOLIA_ROUTER=0x...
export ETHEREUM_SEPOLIA_TOKEN=0x...
```

> **NOTE: Router address**
>
> You can find the router address for your chain in the [CCIP
> Directory](https://docs.chain.link/ccip/directory/testnet).

> **Note:**
>
> 1. Add `--fee-token LINK` to pay CCIP fees in LINK. If you omit this flag, fees are paid in the native gas token.
> 2. Use `--receiver <address>` for a custom recipient. If omitted on EVM-to-EVM, the receiver defaults to the sender.

#### Test 1: Exceed capacity

Transfer one wei more than the outbound capacity (10 tokens + 1 wei). This should fail because the requested amount exceeds the bucket size.

```bash filename="Terminal"
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=10.000000000000000001 \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Your output should look something like this:

```bash filename="Terminal"
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Hardhat keystore 'PRIVATE_KEY'
error: Failed to call "ccipSend"
Error = execution reverted: TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress)
Revert.error = TokenMaxCapacityExceeded(uint256,uint256,address)
Revert.capacity = 10000000000000000000n
Revert.requested = 10000000000000000001n
Revert.tokenAddress = 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
```

The transfer should fail with `TokenMaxCapacityExceeded` because the requested amount is larger than the bucket capacity.

> **NOTE: How does the token bucket work?**
>
> CCIP rate limiters use a  model. Think of the bucket as a virtual allowance that controls how
> many tokens can flow through the pool in a given window:
>
> 1. The bucket starts full at its configured **capacity**.
> 2. Each transfer **consumes** its amount from the bucket.
> 3. The bucket **refills** continuously at the configured **rate** (tokens per second).
>
> In this tutorial, the outbound bucket has a capacity of 10 tokens and refills at 0.1 tokens/sec. Transferring all 10
> tokens in one go "drains" the bucket to zero. Any immediate follow-up transfer will fail because the bucket needs 100
> seconds (10 ÷ 0.1) to fully replenish.

#### Test 2: Drain the bucket, then hit the rate

1. **First transfer** (succeeds: drains the outbound bucket):

```bash filename="Terminal"
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=10 \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Your output should look something like this:

```bash filename="Terminal"
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Hardhat keystore 'PRIVATE_KEY'
🚀 Sending message to 0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3 @ ethereum-testnet-sepolia-arbitrum-1 , tx => 0x3dc40bea29f3e3fc93ff8fce0dda45fd7f55a07ace5089646e018874c8b6745e , messageId => 0x4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293
CCIP Explorer: https://ccip.chain.link/msg/0x4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293
```

2. **Second transfer** (fails: bucket has not refilled):

Run the same command immediately:

```bash filename="Terminal"
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=10 \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Your output should look something like this:

```bash filename="Terminal"
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Hardhat keystore 'PRIVATE_KEY'
error: Failed to call "ccipSend"
Error = execution reverted: TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress)
Revert.error = TokenRateLimitReached(uint256,uint256,address)
Revert.minWaitInSeconds = 100n
Revert.available = 0n
Revert.tokenAddress = 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
```

The transfer should fail with `TokenRateLimitReached` because the bucket has not yet refilled. With a capacity of 10 tokens and a rate of 0.1 tokens per second, it takes 100 seconds to fully replenish.

### 5. (Optional) Configure Rate Limits for Faster Than Finality Transfers

This section applies to [`TokenPool`](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v2.0.0/chains/evm/contracts/pools/TokenPool.sol) v2.0 or later only. CCIP 2.0 token pools maintain two independent rate limiter buckets per lane:

- **Standard finality bucket**: Used for standard finality transfers (`finality=finalized`, which is the default when you omit `--extra finality=...`). This is the bucket you configured in the previous steps.
- **Faster Than Finality bucket**: Used for transfers that request faster than finality with `finality=<N>` (numeric block depth). If this bucket is disabled for a direction, faster than finality transfers for that direction fall back to the standard bucket (enforced on-chain by the token pool).

> **NOTE: Pool type and lockbox scope**
>
> Rate limiters are configured on token pools, regardless of whether you registered your token with Burn & Mint or
> Lock & Mint. In Lock & Mint, the `ERC20LockBox` is not rate-limited and is not updated by these
> tasks.

> **CAUTION: Faster Than Finality must be allowed first**
>
> 1. Updating the faster than finality bucket alone does **not** enable faster than finality transfers.
>    The pool owner must also configure the pool's **allowed finality config** with `setFinalityConfig`.
> 2. Setting the allowed finality config is owner-only. Updating rate limiters can be done by the pool owner or the
>    configured `rateLimitAdmin`.
> 3. For the full faster than finality setup flow, see the Configure Faster Than Finality section in either
>    registration tutorial:
>
>    - [Burn & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat#optional-configure-faster-than-finality-for-the-token-pool)
>    - [Lock & Mint](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat#optional-configure-fast-finality-for-the-token-pool)

#### Check the pool's allowed finality config

Use `getFinalityConfig` to confirm which faster than finality modes the pool currently allows.

[getFinalityConfig.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/finality-config/getFinalityConfig.ts)

View the finality config query task on GitHub.

1. Check the Ethereum Sepolia pool:

```bash filename="Terminal"
npx hardhat getFinalityConfig --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
⏱️  Get Finality Config
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View finality config
========================================

Allowed Finality Config (raw): 0x00000000

Mode: WAIT_FOR_FINALITY (default)
  Full finality is required. Faster Than Finality transfers are disabled.

========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
========================================
```

`BLOCK_DEPTH (32)` sets a minimum for numeric requests: `--extra finality=32` (or higher) is allowed; `--extra finality=10` reverts with `InvalidRequestedFinality(requestedFinality, allowedFinality)`.

2. Check the Arbitrum Sepolia pool:

```bash filename="Terminal"
npx hardhat getFinalityConfig --network arbitrumSepolia
```

> **NOTE: Token pool ABI**
>
> The Hardhat task reads shared TokenPool v2 functions, so the same command works for both Burn & Mint pools and
> Lock & Mint token pools. If you are not using the `{CHAIN}_TOKEN_POOL` environment variable, pass `--tokenpool 0xYourPool`.

#### Approach 1: Allow Faster Than Finality and Configure the Faster Than Finality Bucket in One Command

Use `setFinalityConfig` with rate limit flags to allow faster than finality and configure the faster than finality bucket in a single run.

[setFinalityConfig.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/finality-config/setFinalityConfig.ts)

View the finality config update task on GitHub.

This example configures the Ethereum Sepolia pool for transfers to Arbitrum Sepolia. To configure the reverse direction, run the same task on Arbitrum Sepolia with `--destchain sepolia --network arbitrumSepolia`.

```bash filename="Terminal"
npx hardhat setFinalityConfig \
  --blockdepth 32 \
  --destchain arbitrumSepolia \
  --outboundcapacity 5000000000000000000 \
  --outboundrate 50000000000000000 \
  --inboundcapacity 5500000000000000000 \
  --inboundrate 55000000000000000 \
  --network sepolia
```

Your output should look something like this:

> **NOTE: Output abbreviated**
>
> `setFinalityConfig` also prints the lane's current and updated rate limits (faster than finality where
> enabled, standard otherwise). Those sections are omitted here for brevity.

```bash filename="Terminal"
========================================
⏱️  Set Finality Config
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       Set finality config
========================================

  Current Finality Config: 0x00000000
  New Finality Config:     0x00000020
  Mode:                    BLOCK_DEPTH (32 blocks)

[Step 1] Setting finality config on Ethereum Sepolia
⏳ Tx: 0x280399cd3dce1815a30ca3763736c7cbc4a8018bdb3ba1b650e728479640f5b6
✅ Finality config set successfully!

[Step 2] Updating rate limits (faster than finality bucket) on Ethereum Sepolia → Arbitrum Sepolia
New Configuration:
  Outbound Enabled:  true
  Outbound Capacity: 5000000000000000000
  Outbound Rate:     50000000000000000
  Inbound Enabled:   true
  Inbound Capacity:  5500000000000000000
  Inbound Rate:      55000000000000000

⏳ Tx: 0xa69245f229f41313d597ea452fa7c9d8ead9e05bfe36aad5a997a6850d198ac5
✅ Rate limits updated successfully!

========================================
✅ Configuration Complete on Ethereum Sepolia!
========================================
Token Pool:      https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Finality Config: 0x00000020
Mode:            BLOCK_DEPTH (32 blocks)
========================================
```

#### Approach 2: Update the Faster Than Finality Bucket Directly

If the pool already allows faster than finality, you can update the faster than finality bucket directly using `updateRateLimiters` with `--fastfinality`:

```bash filename="Terminal"
npx hardhat updateRateLimiters \
  --destchain arbitrumSepolia \
  --fastfinality \
  --outboundcapacity 5000000000000000000 \
  --outboundrate 50000000000000000 \
  --inboundcapacity 5500000000000000000 \
  --inboundrate 55000000000000000 \
  --network sepolia
```

#### Verify the faster than finality bucket

Pass `--fastfinality` to `getCurrentRateLimits`. Each direction is shown independently: the faster than finality bucket for directions where it is enabled, and the standard bucket fallback for directions where it is not.

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain arbitrumSepolia \
  --fastfinality \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📊 Get Current Rate Limits
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View rate limits
Bucket:       Faster Than Finality (standard finality fallback per direction if not enabled)
========================================

Pool Version: v2

  Outbound [faster than finality]:
    Enabled:  true
    Capacity: 5000000000000000000
    Rate:     50000000000000000
    Tokens:   5000000000000000000
  Inbound [faster than finality]:
    Enabled:  true
    Capacity: 5500000000000000000
    Rate:     55000000000000000
    Tokens:   5500000000000000000

========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
========================================
```

#### Test a faster than finality transfer

After the pool allows faster than finality and the faster than finality bucket is configured, send a small transfer with numeric block depth (for example `--extra finality=32`):

```bash filename="Terminal"
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=1 \
  --receiver 0xYourReceiverAddress \
  --extra finality=32 \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Your output should look something like this:

```bash filename="Terminal"
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Hardhat keystore 'PRIVATE_KEY'
🚀 Sending message to 0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3 @ ethereum-testnet-sepolia-arbitrum-1 , tx => 0x5f1c2d3e4b5a69788796a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e , messageId => 0x3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2c4d
CCIP Explorer: https://ccip.chain.link/msg/0x3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2c4d
```

Re-run `getCurrentRateLimits` with `--fastfinality` to confirm the outbound faster than finality bucket changed after the transfer:

```bash filename="Terminal"
npx hardhat getCurrentRateLimits \
  --destchain arbitrumSepolia \
  --fastfinality \
  --network sepolia
```

> **NOTE: Faster Than Finality troubleshooting**
>
> 1. This test assumes the pool's allowed finality config allows numeric block depth. If the transfer reverts with `InvalidRequestedFinality(requestedFinality, allowedFinality)`, run `npx hardhat getFinalityConfig --network sepolia` and confirm the configured `BLOCK_DEPTH`,
>    then retry with `--extra finality=32` (or higher than the configured minimum).
> 2. If you request a numeric value lower than the configured minimum block depth, the token pool will revert.

> **CAUTION: Educational Example Disclaimer**
>
> Please note, this page contains community examples only — these are not Chainlink products or services and are not
> supported or maintained by Chainlink. This code represents an example of using a Chainlink product or service, and is
> intended for demonstration and educational purposes only. It is provided "AS IS" and "AS AVAILABLE" without warranties
> of any kind, may not have been audited, and may omit checks or error handling. Each party intending to use this
> example code does so entirely at their own risk and must perform its own audits, security and code review, key
> management, and testing before any production deployment and ensure the operation and performance of such code matches
> expectations. Neither Chainlink Labs nor the Chainlink Foundation deploys, operates, monitors, maintains or endorses
> any deployment of this code. Note that this is not a Chainlink product, feature or service, and there are no
> commitments made with respect to the code, including compatibility with future Chainlink releases. You should not rely
> on this code without first conducting your own technical, engineering, and security review. This code is also outside
> the scope of any Chainlink bug bounty programs. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node
> operators are responsible for outcomes due to errors in this example or how it is deployed or operated, or liable for
> any resulting claims or damages. Use of the Chainlink Network is subject to the Chainlink Foundation [Terms of
> Service](https://chain.link/terms), which provides important information and disclosures. By using this code, you
> acknowledge and agree to these terms.