# Configure a sender allowlist with AdvancedPoolHooks using Hardhat
Source: https://docs.chain.link/ccip/evm/tutorials/cross-chain-tokens/configure-sender-allowlist-advanced-pool-hooks-hardhat
Last Updated: 2026-05-05

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

## Guide Versions

- [Foundry](/ccip/evm/tutorials/cross-chain-tokens/configure-sender-allowlist-advanced-pool-hooks-foundry)

- [Hardhat](/ccip/evm/tutorials/cross-chain-tokens/configure-sender-allowlist-advanced-pool-hooks-hardhat)

CCIP 2.0 token pools support an optional [`AdvancedPoolHooks`](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v2.0.0/chains/evm/contracts/pools/AdvancedPoolHooks.sol) contract being attached to them.

This contract can run certain checks:

1. before tokens are locked/burned (source chain)
2. before tokens are released/minted (destination chain)

If no hooks contract is attached, the token pool keeps its existing behavior.

This tutorial covers the **sender allowlist** use case: only approved addresses may initiate outbound transfers on the source chain. A sender not on the list reverts the source transaction with `SenderNotAllowed`. For the other capabilities, see the [AdvancedPoolHooks concept page](/ccip/concepts/cross-chain-token/advanced-pool-hooks) or [Enforce ACE policies on CCIP token transfers](/ccip/evm/tutorials/cross-chain-tokens/enforce-ace-policies-hardhat).

Hooks are enforced only when **both** of these aspects are configured:

- The token pool points to the `AdvancedPoolHooks` contract.
- The hooks contract authorizes that token pool as a caller.

Authorize the pool before attaching hooks so the first hook call does not revert. Hooks are configured **per token pool, per chain**, which means that:
you need to repeat this setup for any other pool where you want hooks enforced.

> **Note:** Remember. Your CCT token can be deployed on multiple chains, with each chain having its own token pool.

In this tutorial you will:

1. Review the current hooks state on your deployed token pool.
2. Send a baseline transfer (*with no hooks attached*). This should succeed.
3. Deploy an `AdvancedPoolHooks` contract with your deployer address on the allowlist.
4. Authorize the pool as a caller on the hooks contract and attach the hooks contract to the token pool.
5. Verify the hooks attachment, authorized callers, and allowlist state.
6. Remove your address from the allowlist.
7. Attempt a transfer (*expected to revert with `SenderNotAllowed(address)`*).
8. Detach the hooks contract.
9. Send a transfer again (*expected to succeed*).
10. Manage the allowlist over time (add, remove, and check addresses).

## 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 `npm` is unavailable, reinstall or update Node.js.

2. Install/Update Chainlink `ccip-cli`:

```bash filename="Terminal"
npm install -g @chainlink/ccip-cli
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 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_default_keystore_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
```

This tutorial uses a single wallet for every deployment, configuration update, and test transfer: the wallet stored in `KEYSTORE_NAME`.

[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 PRIVATE_KEY
```

> **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. `PRIVATE_KEY`) is what you set as `KEYSTORE_NAME` in `.env`.
> 3. Use `npx hardhat keystore list` to check previously created keystores.

## Tutorial

### 1. Confirm prerequisites, addresses, and permissions

This tutorial assumes you have already deployed tokens and token pools and configured a working lane. If not, complete one of the registration tutorials first:

- [Burn & Mint (Hardhat)](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
- [Lock & Mint (Hardhat)](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)

Export the addresses for the chain you are configuring:

```bash filename="Terminal"
export ETHEREUM_SEPOLIA_TOKEN=0x...
export ETHEREUM_SEPOLIA_TOKEN_POOL=0x...
export ARBITRUM_SEPOLIA_TOKEN=0x...
export ARBITRUM_SEPOLIA_TOKEN_POOL=0x...
```

Use these values throughout the tutorial:

- `ETHEREUM_SEPOLIA_TOKEN_POOL` is the [`TokenPool`](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v2.0.0/chains/evm/contracts/pools/TokenPool.sol) v2 contract being configured.
- `POOL_HOOKS` is the deployed `AdvancedPoolHooks` contract, exported after deployment.
- `ETHEREUM_SEPOLIA_TOKEN` is the source-chain token that we will transfer across to the destination chain (Arbitrum Sepolia)

> **CAUTION: Funding requirements**
>
> - **Sepolia ETH**: the wallet `KEYSTORE_NAME` needs ETH on Sepolia to pay gas for
>   transactions.
> - **Your token on Sepolia**: the same wallet needs some `ETHEREUM_SEPOLIA_TOKEN` to transfer
>   the tokens across to the destination chain (Arbitrum Sepolia).

> **NOTE: One-wallet assumption**
>
> 1. This tutorial assumes the wallet stored in `KEYSTORE_NAME` is the owner of the token pool and will be
>    the owner of the deployed `AdvancedPoolHooks` contract. 2. These roles can be distributed across multiple
>    wallets if needed.

> **NOTE: Target the right contract**
>
> - `AdvancedPoolHooks` attach to a **TokenPool v2** contract. In Lock & Mint, do not
>   confuse the token pool with the `ERC20LockBox` (this tutorial configures token pool hooks only).
> - Hooks are configured per token pool, per chain. Repeat this setup for any other pool where you want hooks
>   enforced.

> **CAUTION: Common failure cases**
>
> - **Missing function selector:** the target pool is likely not a TokenPool v2 contract and does not
>   support advanced hooks.
> - **Wrong signer / wrong owner:** if a write task reverts with `OnlyCallableByOwner()`, the
>   wallet stored in `KEYSTORE_NAME` is not the current owner of that contract.
> - **AllowListNotEnabled():** the hooks contract was deployed with an empty allowlist, so allowlist mode
>   is disabled for that deployment.
> - **Hook call reverts:** confirm the token pool is listed as an authorized caller on the hooks contract
>   before attaching hooks.
> - **SenderNotAllowed(address):** the transfer sender is not allowlisted as the original outbound
>   sender.

### 2. Review current pool hooks state

Use the `getAdvancedPoolHooks` task to check whether an `AdvancedPoolHooks` contract is currently attached to your token pool.

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

View the hooks query task on GitHub.

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

Example output (no hooks attached):

```bash filename="Terminal"
========================================
🪝 Get Advanced Pool Hooks
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View pool hooks
========================================

No AdvancedPoolHooks contract is attached to this pool.
   Deploy one with:  npx hardhat deployAdvancedPoolHooks
   Attach it with:   npx hardhat updateAdvancedPoolHooks --newhook <address>

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

> **NOTE**
>
> 1. `getAdvancedPoolHooks()` is only available on TokenPool v2.0 and later. 2. If your token pool already
>    has hooks attached, you can detach them by following the 'Detach hooks' section, to follow the tutorial.

### 3. Send a baseline transfer (no hooks attached)

This step confirms your lane works before enabling hooks. Make sure the previous step shows no hooks attached.

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

Export the Sepolia router address for `ccip-cli`:

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

> **NOTE: Token amounts in this tutorial**
>
> 1. Earlier Hardhat tasks use smallest-unit amounts (wei-like). In the `ccip-cli` examples below we use a
>    compact, human-readable token amount (for example, `1.23`).
> 2. The wallet stored in `KEYSTORE_NAME` should have some of your tokens on Sepolia so it can send a
>    cross-chain transfer.

Send a baseline transfer to confirm if the cross chain transfer succeeds without any hooks attached.

```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.23 \
  --receiver 0xreceiveraddress \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Expected output (example):

```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
```

### 4. Deploy AdvancedPoolHooks

Use the `deployAdvancedPoolHooks` task to deploy a new hooks contract.

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

View the hooks deployment task on GitHub.

Deployment flags:

- `--allowlist`: CSV list of original outbound sender addresses allowed to initiate outbound transfers.
- `--authorizedcallers`: CSV list of token pool addresses permitted to invoke the hooks.

> **CAUTION: Allowlist enablement is permanent**
>
> 1. Allowlist mode is set at deployment time and **cannot be changed later**.
> 2. If you deploy with a non-empty `--allowlist`, the allowlist is permanently enabled.
> 3. If you deploy with an empty allowlist, calling `applyAllowListUpdates()` later will revert with `AllowListNotEnabled()`.

In this example, you will deploy hooks on Ethereum Sepolia with your deployer address on the allowlist, and authorize your token pool as a caller:

> **NOTE: Why we start by allowlisting the deployer**
>
> 1. You will first allowlist your deployer address so you can attach hooks without immediately blocking your own
>    transfers. 2. Later in the tutorial, you will remove your address from the allowlist to demonstrate an enforced
>    failure.

```bash filename="Terminal"
npx hardhat deployAdvancedPoolHooks \
  --allowlist 0xYourDeployerAddress \
  --authorizedcallers $ETHEREUM_SEPOLIA_TOKEN_POOL \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
🔒 Deploy Advanced Pool Hooks
========================================
Chain:        Ethereum Sepolia
Action:       Deploy pool hooks
========================================

Advanced Pool Hooks Parameters:
  Allowlist Enabled:           Yes
  Allowlist Size:              1
    [0] 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
  Threshold Amount:            Disabled (0)
  Policy Engine:               Disabled (0x0)
  Authorized Callers Enabled:  Yes
  Authorized Callers Size:     1
    [0] 0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9

[Step 1] Deploying AdvancedPoolHooks on Ethereum Sepolia
⏳ Deployment tx: 0x9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d
✅ AdvancedPoolHooks deployed successfully!

========================================
✅ Deployment Complete on Ethereum Sepolia!
========================================
AdvancedPoolHooks Address: 0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE

Deployment saved: deployments/advanced-pool-hooks/ETHEREUM_SEPOLIA/1746652800-AdvancedPoolHooks.json

Configuration Summary:
  Allowlist:          Enabled
  Threshold:          Disabled
  Policy Engine:      Disabled
  Authorized Callers: Enabled

Next Steps:
  1. When deploying a TokenPool, pass this hooks address as the 'poolHooks' parameter
  2. Attach to an existing pool: npx hardhat updateAdvancedPoolHooks --newhook <address> --network <network>
  3. Manage allowlist: npx hardhat updateAllowList --poolhooks <address> --add "0xAddr" --network <network>
========================================
```

Export the hooks address for subsequent commands:

```bash filename="Terminal"
export POOL_HOOKS=0xHooksAddress
```

> **NOTE: Deployment address recovery**
>
> - Deployment addresses are saved to `deployments/advanced-pool-hooks/`.
> - If you lose the session export, retrieve the address from the JSON file under the `POOL_HOOKS` key.

### 5. Authorize the pool and attach hooks

Authorize the token pool as a caller on the hooks contract before attaching hooks so the first hook call does not revert.

> **NOTE: Already authorized at deploy time?**
>
> If you passed `--authorizedcallers` when deploying the hooks contract (as shown in the previous step), your
> pool is already authorized. You can skip the authorization command and go directly to attaching the hooks.

**If you need to authorize the pool separately**, use the `updateAuthorizedCallers` task:

[updateAuthorizedCallers.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/authorized-callers/updateAuthorizedCallers.ts)

View the authorized callers task on GitHub.

```bash filename="Terminal"
npx hardhat updateAuthorizedCallers \
  --poolhooks $POOL_HOOKS \
  --add $ETHEREUM_SEPOLIA_TOKEN_POOL \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📝 Update Authorized Callers
========================================
Chain:        Ethereum Sepolia
Pool Hooks:   0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Action:       Update authorized callers
========================================

Adding 1 caller(s):
  [0] 0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9

[Step 1] Applying authorized caller updates on Ethereum Sepolia
⏳ Tx: 0xaaabbbcccdddeeefff000111222333444555666777888999aaabbbcccdddeeff

========================================
✅ Authorized callers updated on Ethereum Sepolia!
========================================
Pool Hooks:   https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Transaction:  0xaaabbbcccdddeeefff000111222333444555666777888999aaabbbcccdddeeff
========================================
```

**Now attach the hooks** to your token pool using the `updateAdvancedPoolHooks` task:

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

View the hooks update task on GitHub.

| Flag          | Required | Description                                                   |
| ------------- | -------- | ------------------------------------------------------------- |
| `--tokenpool` | No       | Token pool address (defaults to `{CHAIN}_TOKEN_POOL` env var) |
| `--newhook`   | Yes      | Address of the deployed `AdvancedPoolHooks` contract          |

```bash filename="Terminal"
npx hardhat updateAdvancedPoolHooks \
  --newhook $POOL_HOOKS \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
🔄 Update Advanced Pool Hooks
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       Update pool hooks
========================================

New Pool Hooks: 0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE

[Step 1] Updating AdvancedPoolHooks on Ethereum Sepolia
⏳ Tx: 0xbbccddeeff00112233445566778899aabbccddeeff00112233445566778899aa
✅ AdvancedPoolHooks updated successfully!

========================================
✅ Pool hooks updated on Ethereum Sepolia!
========================================
Token Pool:     https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
New Pool Hooks: https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
========================================
```

> **NOTE: Access control**
>
> - Updating authorized callers requires the hooks contract **owner**.
> - Attaching or detaching hooks requires the token pool **owner**.
> - If either command reverts with `OnlyCallableByOwner()`, the wallet stored in `KEYSTORE_NAME` is not the current owner of the target contract.

### 6. Verify the setup

Run the following read-only tasks to verify wiring. These checks confirm configuration state only; the next step attempts a transfer that is expected to revert.

**1. Confirm hooks are attached to the pool:**

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

Expected output should now show your hooks address:

```bash filename="Terminal"
========================================
🪝 Get Advanced Pool Hooks
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Action:       View pool hooks
========================================

✅ AdvancedPoolHooks:
   0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE

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

**2. Confirm the pool is an authorized caller on the hooks:**

[getAuthorizedCallers.ts](https://github.com/smartcontractkit/docs-cct-hardhat/blob/main/tasks/configure/authorized-callers/getAuthorizedCallers.ts)

View the authorized callers query task on GitHub.

```bash filename="Terminal"
npx hardhat getAuthorizedCallers \
  --poolhooks $POOL_HOOKS \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
🔎 Get Authorized Callers
========================================
Chain:        Ethereum Sepolia
Pool Hooks:   0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Action:       View authorized callers
========================================

Authorized Callers count: 1
  [0] 0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9

========================================
Pool Hooks:   https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
========================================
```

**3. View the current allowlist:**

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

View the allowlist query task on GitHub.

```bash filename="Terminal"
npx hardhat getAllowList \
  --poolhooks $POOL_HOOKS \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
🔎 Get AllowList
========================================
Chain:        Ethereum Sepolia
Pool Hooks:   0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Action:       View allowlist
========================================

AllowList count: 1
  0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
========================================
Pool Hooks:   https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
========================================
```

**4. (Optional): Check if a particular sender is allowlisted:**

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

View the allowlist check task on GitHub.

```bash filename="Terminal"
npx hardhat isAllowListed \
  --poolhooks $POOL_HOOKS \
  --checkaddress 0xsenderaddress \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
🔎 Is AllowListed?
========================================
Chain:         Ethereum Sepolia
Pool Hooks:    0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Check Address: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Action:        Check allowlist
========================================

✅ Address IS allowlisted.
========================================
Pool Hooks:   https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
========================================
```

### 7. Remove your address from the allowlist

In this step, you remove your own address from the hooks allowlist. This sets up the next transfer to fail.

> **NOTE: Admin access vs transfer permissions**
>
> Updating the allowlist is admin-only (`onlyOwner`) and is not gated by allowlist membership. Even if you
> are the hooks admin, your transfers will still revert unless your sender address is on the allowlist.

Remove your deployer address from the allowlist:

```bash filename="Terminal"
npx hardhat updateAllowList \
  --poolhooks $POOL_HOOKS \
  --remove "0xYourDeployerAddress" \
  --network sepolia
```

Verify that your address is no longer allowlisted:

```bash filename="Terminal"
npx hardhat isAllowListed \
  --poolhooks $POOL_HOOKS \
  --checkaddress 0xYourDeployerAddress \
  --network sepolia
```

### 8. Attempt a transfer (expected failure)

This step attempts a token transfer and is expected to fail because you removed your sender address from the allowlist. Hooks are configured per token pool, per chain, although this does not attach hooks on any destination-chain pool.

```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.23 \
  --receiver 0xYourDeployerAddress \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Expected result: the send transaction reverts. You should see an error that includes `SenderNotAllowed(address)`.

If this step does **not** fail as expected, make sure:

- Hooks are attached (`getAdvancedPoolHooks` shows `POOL_HOOKS`).
- Your sender address is **NOT** on the allowlist (previous step returns NOT allowlisted).
- The token pool is an authorized caller on the hooks (`getAuthorizedCallers` includes `ETHEREUM_SEPOLIA_TOKEN_POOL`).

### 9. Detach hooks

Detach hooks to disable enforcement. This sets the pool hooks address back to the zero address.

```bash filename="Terminal"
npx hardhat updateAdvancedPoolHooks \
  --newhook 0x0000000000000000000000000000000000000000 \
  --network sepolia
```

Verify hooks are detached:

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

### 10. Send a transfer again (expected success)

With hooks detached, the token pool returns to its default behavior and your transfer should succeed again.

```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.23 \
  --receiver 0xYourDeployerAddress \
  --wallet hardhat:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ARBITRUM_SEPOLIA_RPC_URL"
```

Expected output (example):

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

### 11. Manage the allowlist

Use the `updateAllowList` task to add or remove addresses from the allowlist. Only the hooks contract **owner** can modify the allowlist.

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

View the allowlist update task on GitHub.

| Flag          | Required | Description                                            |
| ------------- | -------- | ------------------------------------------------------ |
| `--poolhooks` | Yes (v2) | Address of the `AdvancedPoolHooks` contract            |
| `--add`       | No       | Comma-separated addresses to add to the allowlist      |
| `--remove`    | No       | Comma-separated addresses to remove from the allowlist |

**Add an address:**

```bash filename="Terminal"
npx hardhat updateAllowList \
  --poolhooks $POOL_HOOKS \
  --add "0xYourDeployerAddress" \
  --network sepolia
```

Your output should look something like this:

```bash filename="Terminal"
========================================
📝 Update AllowList
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Pool Hooks:   0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
Action:       Update allowlist
========================================

[Step 1] Updating allowlist on Ethereum Sepolia
⏳ Tx: 0xccddeeff00112233445566778899aabbccddeeff00112233445566778899aabb
✅ AllowList updated successfully!

========================================
✅ Allowlist updated on Ethereum Sepolia!
========================================
Token Pool:   https://sepolia.etherscan.io/address/0x147D4625b71f58D7CFA54BB705DC7741B94e0CE9
Pool Hooks:   https://sepolia.etherscan.io/address/0xA11c0FFeE0bAdA55CAfEBaBeDeaDBeEfF00DBABE
========================================
```

**Remove an address:**

```bash filename="Terminal"
npx hardhat updateAllowList \
  --poolhooks $POOL_HOOKS \
  --remove "0xYourDeployerAddress" \
  --network sepolia
```

**Verify the updated allowlist** by re-running the `getAllowList` or `isAllowListed` tasks from the previous step.

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