---
date: 2026-01-12T00:00:00Z
difficulty: advanced
title: "Permissioned Tokens with Token ACL (sRFC37)"
description:
  "Learn how to implement compliant, permissioned tokens on Solana using Token
  ACL and the ABL Gate Program. Enable allow/block lists without sacrificing
  user experience."
tags:
  - token extensions
  - token 2022
  - ts
  - web3js
keywords:
  - token acl
  - permissioned tokens
  - allow list
  - block list
  - compliance
  - sanctions
  - kyc
  - aml
  - frozen accounts
  - permissionless thaw
---

<Embed url="https://www.youtube.com/embed/mMaU8fZXZoE" />

Token ACL (Access Control List) is a Solana program that enables compliant,
permissioned tokens without sacrificing user experience. It implements
[sRFC37](https://github.com/solana-foundation/SRFCs/discussions/2), allowing
enterprises to create tokens with allow/block list functionality while
maintaining the seamless UX that users expect.

## The Problem

Enterprises need compliant tokens that can:

1. Enforce KYC/AML requirements
2. Block sanctioned addresses
3. Restrict token transfers to approved parties

The traditional approach uses Token-2022's `DefaultAccountState` extension to
create accounts in a **frozen state**, requiring manual intervention to thaw
each account:

```
┌─────────────────────────────────────────────────────┐
│           TRADITIONAL FROZEN TOKENS                 │
├─────────────────────────────────────────────────────┤
│                                                     │
│   1. User creates token account                     │
│      └─> Account is FROZEN ❄️                       │
│                                                     │
│   2. User contacts issuer support                   │
│      └─> "Please whitelist my wallet"               │
│                                                     │
│   3. Issuer manually verifies KYC                   │
│      └─> Delays, friction, poor UX                  │
│                                                     │
│   4. Issuer thaws account                           │
│      └─> Finally can receive tokens                 │
│                                                     │
│   ❌ Bad UX - users wait hours/days                 │
│                                                     │
└─────────────────────────────────────────────────────┘
```

This creates significant friction and defeats the promise of instant,
permissionless blockchain transactions.

## The Solution

Token ACL enables **permissionless thaw** - users can automatically thaw their
own accounts if they meet the criteria defined by a Gate Program:

```
┌─────────────────────────────────────────────────────┐
│              TOKEN ACL FLOW                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│   1. User creates token account                     │
│      └─> Account is FROZEN ❄️                       │
│                                                     │
│   2. User calls permissionless thaw                 │
│      └─> Token ACL checks Gate Program              │
│                                                     │
│   3. Gate Program validates user                    │
│      ├─> On allow list? ✅ THAW                     │
│      ├─> On block list? ❌ STAY FROZEN              │
│      └─> AllowAllEoas mode? ✅ THAW                 │
│                                                     │
│   4. Account thawed instantly!                      │
│      └─> User can receive tokens immediately        │
│                                                     │
│   ✅ Great UX - instant, self-service               │
│                                                     │
└─────────────────────────────────────────────────────┘
```

<Callout type="info" title="Educational Reference Implementation">

This guide includes a complete working implementation you can run locally. The
[source code](https://github.com/Woody4618/access-control-list-acl-demo)
provides reference implementations for exploration and educational purposes.

The code of the ACL programs is available in the
[token-acl repository](https://github.com/solana-foundation/token-acl) and the
ABL Gate Program is available in the
[abl-gate-program repository](https://github.com/solana-foundation/abl-gate-program).

**Important**: The ABL (Allow Block List) Gate Program used in this guide is a
**reference implementation**. While it's audited and production-ready, issuers
are free to create custom Gate Programs that better fit their specific
compliance needs. You are only bound by the Token ACL specification (sRFC37),
not this particular Gate Program design.

**Do NOT use this code directly in production** without:

- Comprehensive security audits
- Proper key management systems
- Regulatory compliance review
- Legal consultation

</Callout>

## Why Token ACL?

| Aspect             | Traditional Frozen    | Token ACL                  |
| ------------------ | --------------------- | -------------------------- |
| Account Activation | Manual (minutes/days) | Instant (self-service)     |
| User Experience    | Poor                  | Seamless                   |
| Compliance Control | Full                  | Full                       |
| Sanctions Blocking | Manual                | Automatic via Gate Program |
| Integration Effort | High                  | Low (SDK available)        |
| Composability      | Limited               | Full (works with DeFi)     |

## Token ACL vs Transfer Hooks

Both Token ACL and [Transfer Hooks](/docs/tokens/extensions/transfer-hook) are
Token-2022 solutions for adding custom logic to tokens, but they serve different
purposes and have different trade-offs:

| Aspect                   | Token ACL                             | Transfer Hooks                           |
| ------------------------ | ------------------------------------- | ---------------------------------------- |
| **When Logic Runs**      | Only on freeze/thaw operations        | On **every** transfer                    |
| **Transfer Overhead**    | None - transfers are standard         | Extra CUs + accounts on each transfer    |
| **Account Dependencies** | Only during account activation        | Required on every transfer transaction   |
| **DeFi Composability**   | Full - protocols work normally        | Limited - many protocols blacklist       |
| **Best For**             | KYC/AML, sanctions, allow/block lists | Royalties, custom transfer validation    |
| **Complexity for Users** | Low - one-time thaw operation         | Higher - every transfer needs extra data |

### When to Use Token ACL

Choose Token ACL when you need to control **who can hold** your token:

- KYC/AML compliance - verify holders before they can receive tokens
- Sanctions screening - block specific addresses
- Accredited investor restrictions - limit token holders to verified parties
- PDA blocking - prevent smart contracts from holding tokens

### When to Use Transfer Hooks

Choose Transfer Hooks when you need to control **how tokens move**:

- NFT royalties - charge fees on every transfer
- Transfer restrictions - limit transfer amounts or frequency
- Custom transfer logic - execute code on every movement
- Onchain analytics - track all token movements

<Callout type="info" title="Complementary Solutions">

Token ACL and Transfer Hooks can be used together. For example, you could use
Token ACL to control who can hold your token (compliance) while using Transfer
Hooks for royalty enforcement on each transfer.

</Callout>

## Architecture Overview

Token ACL consists of three main components:

1. **Token ACL Program**: The core program that manages freeze authority
   delegation and permissionless operations
2. **Gate Program**: Custom logic that determines who can thaw/freeze (e.g., ABL
   Gate Program for allow/block lists)
3. **MintConfig**: Per-mint configuration that stores settings and delegates
   freeze authority

```
┌─────────────────────────────────────────────────────────────────┐
│                    TOKEN ACL ARCHITECTURE                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────────┐      delegates       ┌─────────────────┐     │
│   │  Token Mint  │ ──────────────────→  │   MintConfig    │     │
│   │  (Token-22)  │   freeze authority   │   (Token ACL)   │     │
│   └──────────────┘                      └────────┬────────┘     │
│                                                  │              │
│                                                  │ calls        │
│                                                  ▼              │
│   ┌──────────────┐     validates        ┌─────────────────┐     │
│   │     User     │ ◄─────────────────── │  Gate Program   │     │
│   │   (wallet)   │                      │  (ABL/Custom)   │     │
│   └──────────────┘                      └─────────────────┘     │
│                                                  │              │
│                                         ┌────────┴────────┐     │
│                                         │                 │     │
│                                    ┌────▼────┐      ┌─────▼───┐ │
│                                    │ Allow   │      │ Block   │ │
│                                    │ Lists   │      │ Lists   │ │
│                                    └─────────┘      └─────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### Key Concepts

1. **Freeze Authority Delegation**: When you create a Token ACL config, the
   mint's freeze authority is transferred to the MintConfig PDA. This allows
   Token ACL to manage freeze/thaw operations.

2. **Gate Programs**: External programs that implement the allow/block logic.
   The ABL (Allow Block List) Gate Program is a **reference implementation** -
   issuers can build custom Gate Programs with different logic (e.g., onchain
   KYC verification, oracle-based sanctions checks, or integration with identity
   protocols).

3. **Permissionless Operations**: Users can thaw their own accounts without
   issuer intervention, as long as the Gate Program approves.

4. **TokenMetadata Integration**: Adding a `token_acl` field to your mint's
   metadata enables automatic detection by wallets and SDKs like
   `@solana/token-helpers`.

<Callout type="info" title="Auto-Detection with TokenMetadata">

When you add a `token_acl` field to your mint's TokenMetadata extension pointing
to the Gate Program address, SDKs like `@solana/token-helpers` can automatically
detect Token ACL mints and include thaw instructions when creating token
accounts.

</Callout>

## ABL Gate Program Modes

<Callout type="info" title="ABL is a Reference Implementation">

The ABL Gate Program shown here is a **reference implementation** that covers
common allow/block list use cases. However, **you are not locked into this
design**. The Token ACL specification (sRFC37) defines only the interface
between Token ACL and Gate Programs - you can create custom Gate Programs with:

- Integration with onchain identity/KYC protocols
- Oracle-based real-time sanctions screening
- Multi-sig approval workflows
- Time-based or conditional access rules
- Any other custom compliance logic

The only requirement is implementing the Gate Program interface defined in
sRFC37.

</Callout>

The ABL (Allow Block List) Gate Program supports several modes:

| Mode           | Description                                     | Use Case                      |
| -------------- | ----------------------------------------------- | ----------------------------- |
| `AllowAllEoas` | All regular wallets (non-PDAs) can thaw         | Open tokens with PDA blocking |
| `Allow`        | Only wallets on the allow list can thaw         | KYC-required tokens           |
| `Block`        | All wallets EXCEPT those on block list can thaw | Sanctions compliance          |
| Composite      | Combine allow + block lists                     | Full compliance setup         |

<Callout type="caution" title="Block List Precedence">

When using composite lists, the **block list always takes precedence**. A wallet
on both the allow list AND block list will NOT be able to thaw.

</Callout>

## Program Addresses

To make it easy, the programs are already deployed on devnet. You can use the
following addresses. Mainnet release will follow after the audits.

| Program          | Address                                        |
| ---------------- | ---------------------------------------------- |
| Token ACL        | `TACLkU6CiCdkQN2MjoyDkVg2yAH9zkxiHDsiztQ52TP`  |
| ABL Gate Program | `GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz` |

## Prerequisites

To run the examples locally make sure to clone the programs into your local
validator:

1. **Solana CLI**

   (For running it locally use 2.x, NOT 3.x - there's a known issue with
   Token-2022 metadata at the moment, which would fail at the step of adding
   additional metadata)

   ```bash
   solana --version
   ```

2. **Node.js 18+** and **pnpm**

3. **Local validator** with required programs:
   ```bash
    solana-test-validator \
    --clone TACLkU6CiCdkQN2MjoyDkVg2yAH9zkxiHDsiztQ52TP \
    --clone GEC5tu9eaZQrNS7ohERwZRqyvLvV8k2iVZqqt6VuwvJu \
    --clone GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz \
    --clone D2GUvBwbnkFu3R5s1rz5dcBJ81UsqY3nvHbLdeJLtSx5 \
    --url devnet \
    --reset
   ```

## Complete Implementation

### Step 1: Install Dependencies

```bash
pnpm add @solana/kit @solana-program/token-2022 @solana-program/system \
  @solana-program/compute-budget @token-acl/sdk @token-acl/abl-sdk \
  @solana/spl-token-metadata @solana/web3.js ws
```

### Step 2: Create a Token with Token ACL

Here's a complete example that creates a compliant token with Token ACL:

```typescript
import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
  generateKeyPairSigner,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  lamports
} from "@solana/kit";

import { getCreateAccountInstruction } from "@solana-program/system";
import { getSetComputeUnitLimitInstruction } from "@solana-program/compute-budget";

import {
  TOKEN_2022_PROGRAM_ADDRESS,
  getInitializeMintInstruction,
  getInitializeTokenMetadataInstruction,
  getUpdateTokenMetadataFieldInstruction,
  tokenMetadataField,
  AccountState,
  getMintSize,
  getPreInitializeInstructionsForMintExtensions,
  extension
} from "@solana-program/token-2022";

import { pack } from "@solana/spl-token-metadata";
import { PublicKey } from "@solana/web3.js";

// Token ACL SDK
import {
  getCreateConfigInstruction,
  findMintConfigPda,
  getTogglePermissionlessInstructionsInstruction,
  findThawExtraMetasAccountPda
} from "@token-acl/sdk";

// ABL Gate Program SDK
import {
  getCreateListInstruction,
  getSetupExtraMetasInstruction,
  getAddWalletInstruction,
  findListConfigPda,
  findWalletEntryPda,
  ABL_PROGRAM_ADDRESS,
  Mode
} from "@token-acl/abl-sdk";

// TLV sizes for Token-2022 extensions
const TYPE_SIZE = 2;
const LENGTH_SIZE = 2;

async function createTokenACLMint() {
  // Setup RPC
  const rpc = createSolanaRpc("http://localhost:8899");
  const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
  const sendAndConfirm = sendAndConfirmTransactionFactory({
    rpc,
    rpcSubscriptions
  });

  // Load your payer keypair
  const payer = await loadKeypair("~/.config/solana/id.json");

  // Generate mint keypair
  const mint = await generateKeyPairSigner();
  console.log(`🪙  Mint: ${mint.address}`);

  // TokenMetadata config - includes 'token_acl' for auto-detection
  const TOKEN_NAME = "Compliant Token";
  const TOKEN_SYMBOL = "COMP";
  const TOKEN_URI = "";
  const TOKEN_ACL_KEY = "token_acl";

  // Define extensions
  const defaultAccountStateExtension = extension("DefaultAccountState", {
    state: AccountState.Frozen
  });
  const metadataPointerExtension = extension("MetadataPointer", {
    authority: payer.address,
    metadataAddress: mint.address
  });
  const extensions = [defaultAccountStateExtension, metadataPointerExtension];

  // Calculate mint size
  const baseMintSize = getMintSize(extensions);
  const metadataForSizing = {
    mint: new PublicKey(mint.address),
    name: TOKEN_NAME,
    symbol: TOKEN_SYMBOL,
    uri: TOKEN_URI,
    additionalMetadata: [[TOKEN_ACL_KEY, ABL_PROGRAM_ADDRESS]] as [
      string,
      string
    ][]
  };
  const metadataLen = pack(metadataForSizing).length;
  const totalSpace = baseMintSize + metadataLen + TYPE_SIZE + LENGTH_SIZE;

  // Get rent
  const mintRent = await rpc
    .getMinimumBalanceForRentExemption(BigInt(totalSpace))
    .send();

  // Get extension pre-initialization instructions
  const extensionInstructions = getPreInitializeInstructionsForMintExtensions(
    mint.address,
    extensions
  );

  // Build transaction
  const { value: blockhash } = await rpc.getLatestBlockhash().send();
  const createMintTx = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayer(payer.address, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
    (tx) =>
      appendTransactionMessageInstructions(
        [
          getSetComputeUnitLimitInstruction({ units: 400_000 }),
          getCreateAccountInstruction({
            payer,
            newAccount: mint,
            lamports: lamports(mintRent),
            space: baseMintSize,
            programAddress: TOKEN_2022_PROGRAM_ADDRESS
          }),
          ...extensionInstructions,
          getInitializeMintInstruction({
            mint: mint.address,
            decimals: 6,
            mintAuthority: payer.address,
            freezeAuthority: payer.address
          }),
          getInitializeTokenMetadataInstruction({
            metadata: mint.address,
            updateAuthority: payer.address,
            mint: mint.address,
            mintAuthority: payer,
            name: TOKEN_NAME,
            symbol: TOKEN_SYMBOL,
            uri: TOKEN_URI
          }),
          getUpdateTokenMetadataFieldInstruction({
            metadata: mint.address,
            updateAuthority: payer,
            field: tokenMetadataField("Key", [TOKEN_ACL_KEY]),
            value: ABL_PROGRAM_ADDRESS
          })
        ],
        tx
      )
  );

  // Sign and send
  const signedTx = await signTransactionMessageWithSigners(createMintTx);
  await sendAndConfirm(signedTx, { commitment: "confirmed" });

  console.log("✅ Mint created with TokenMetadata");
  return mint.address;
}
```

### Step 3: Create Token ACL Config

After creating the mint, create the Token ACL configuration:

```typescript
async function createTokenACLConfig(
  mintAddress: Address,
  payer: TransactionSigner
) {
  const [mintConfigPda] = await findMintConfigPda({ mint: mintAddress });
  console.log(`📋 MintConfig PDA: ${mintConfigPda}`);

  const createConfigIx = getCreateConfigInstruction({
    payer: payer.address,
    authority: payer,
    mint: mintAddress,
    mintConfig: mintConfigPda,
    gatingProgram: ABL_PROGRAM_ADDRESS
  });

  const { value: blockhash } = await rpc.getLatestBlockhash().send();
  const tx = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayer(payer.address, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
    (tx) => appendTransactionMessageInstructions([createConfigIx], tx)
  );

  const signedTx = await signTransactionMessageWithSigners(tx);
  await sendAndConfirm(signedTx, { commitment: "confirmed" });

  console.log("✅ Token ACL config created");
  console.log("   Freeze authority transferred to MintConfig PDA");

  return mintConfigPda;
}
```

### Step 4: Setup ABL Gate Program

Create an ABL list and setup extra metas:

<Tabs items={["AllowAllEoas Mode", "Allow List Mode", "Block List Mode"]}>

<Tab value="AllowAllEoas Mode">

```typescript
// AllowAllEoas - All regular wallets can thaw automatically
async function setupAllowAllEoas(
  mintAddress: Address,
  mintConfigPda: Address,
  payer: TransactionSigner
) {
  const listSeed = mintAddress; // Use mint as seed
  const [listConfigPda] = await findListConfigPda({
    authority: payer.address,
    seed: listSeed
  });

  const createListIx = getCreateListInstruction({
    authority: payer,
    listConfig: listConfigPda,
    mode: Mode.AllowAllEoas, // All EOAs can thaw
    seed: listSeed
  });

  const [thawExtraMetasPda] = await findThawExtraMetasAccountPda(
    { mint: mintAddress },
    { programAddress: ABL_PROGRAM_ADDRESS }
  );

  const setupMetasIx = getSetupExtraMetasInstruction({
    authority: payer,
    tokenAclMintConfig: mintConfigPda,
    mint: mintAddress,
    extraMetas: thawExtraMetasPda,
    lists: [listConfigPda]
  });

  // Send transaction with both instructions...
  console.log("✅ ABL list created with AllowAllEoas mode");
}
```

</Tab>

<Tab value="Allow List Mode">

```typescript
// Allow List - Only whitelisted wallets can thaw
async function setupAllowList(
  mintAddress: Address,
  mintConfigPda: Address,
  payer: TransactionSigner
) {
  const listSeed = mintAddress;
  const [listConfigPda] = await findListConfigPda({
    authority: payer.address,
    seed: listSeed
  });

  const createListIx = getCreateListInstruction({
    authority: payer,
    listConfig: listConfigPda,
    mode: Mode.Allow, // Only allow-listed wallets
    seed: listSeed
  });

  // Setup extra metas...
  console.log("✅ Allow list created - only whitelisted wallets can thaw");
}

// Add wallet to allow list
async function addToAllowList(
  listConfigPda: Address,
  walletToAdd: Address,
  authority: TransactionSigner
) {
  const [walletEntryPda] = await findWalletEntryPda({
    listConfig: listConfigPda,
    walletAddress: walletToAdd
  });

  const addWalletIx = getAddWalletInstruction({
    authority,
    listConfig: listConfigPda,
    walletEntry: walletEntryPda,
    wallet: walletToAdd
  });

  // Send transaction...
  console.log(`✅ Added ${walletToAdd} to allow list`);
}
```

</Tab>

<Tab value="Block List Mode">

```typescript
// Block List - All wallets can thaw EXCEPT blocked ones
async function setupBlockList(
  mintAddress: Address,
  mintConfigPda: Address,
  payer: TransactionSigner
) {
  const listSeed = mintAddress;
  const [listConfigPda] = await findListConfigPda({
    authority: payer.address,
    seed: listSeed
  });

  const createListIx = getCreateListInstruction({
    authority: payer,
    listConfig: listConfigPda,
    mode: Mode.Block, // Block listed wallets
    seed: listSeed
  });

  // Setup extra metas...
  console.log("✅ Block list created - blocked wallets cannot thaw");
}

// Add wallet to block list (e.g., sanctioned address)
async function addToBlockList(
  listConfigPda: Address,
  walletToBlock: Address,
  authority: TransactionSigner
) {
  const [walletEntryPda] = await findWalletEntryPda({
    listConfig: listConfigPda,
    walletAddress: walletToBlock
  });

  const addWalletIx = getAddWalletInstruction({
    authority,
    listConfig: listConfigPda,
    walletEntry: walletEntryPda,
    wallet: walletToBlock
  });

  // Send transaction...
  console.log(`🚫 Added ${walletToBlock} to block list`);
}
```

</Tab>

</Tabs>

### Step 5: Enable Permissionless Thaw

Enable users to thaw their own accounts:

```typescript
async function enablePermissionlessThaw(
  mintConfigPda: Address,
  authority: TransactionSigner
) {
  const toggleIx = getTogglePermissionlessInstructionsInstruction({
    authority,
    mintConfig: mintConfigPda,
    thawEnabled: true,
    freezeEnabled: false // Optional: enable permissionless freeze too
  });

  // Send transaction...
  console.log("✅ Permissionless thaw enabled");
}
```

### Step 6: User Thaws Their Account

Users can now thaw their own accounts using the SDK:

```typescript
import {
  createThawPermissionlessIdempotentInstructionWithExtraMetas,
  TOKEN_ACL_PROGRAM_ADDRESS
} from "@token-acl/sdk";
import { fetchEncodedAccount } from "@solana/kit";

async function userThawsAccount(
  mintAddress: Address,
  userAta: Address,
  userAddress: Address,
  payer: TransactionSigner
) {
  // Account retriever function for the SDK
  const accountRetriever = async (addr: Address) => {
    return await fetchEncodedAccount(rpc, addr);
  };

  // The SDK handles all the complexity of fetching extra metas
  const thawIx =
    await createThawPermissionlessIdempotentInstructionWithExtraMetas(
      payer, // authority (signer)
      userAta, // token account to thaw
      mintAddress, // mint
      userAddress, // token account owner
      TOKEN_ACL_PROGRAM_ADDRESS, // Token ACL program
      accountRetriever // account fetcher
    );

  // Send transaction signed by payer...
  console.log("✅ Account thawed permissionlessly!");
}
```

## Using @solana/token-helpers for Auto-Thaw

The `@solana/token-helpers` SDK can automatically detect Token ACL mints and
include thaw instructions:

```typescript
import { createAndConfirmAssociatedTokenAccount } from "@solana/token-helpers";

// This automatically includes thaw instruction if mint has 'token_acl' metadata
const { signature, associatedTokenAddress } =
  await createAndConfirmAssociatedTokenAccount(
    rpc,
    rpcSubscriptions,
    payer,
    user.address,
    mintAddress,
    true // idempotent
  );

console.log(`✅ Account created AND thawed automatically!`);
console.log(`   ATA: ${associatedTokenAddress}`);
```

<Callout type="info" title="TokenMetadata Requirement">

For `@solana/token-helpers` auto-detection to work, your mint must have:

1. The `TokenMetadata` extension initialized
2. An `additionalMetadata` field with key `token_acl` and value set to the Gate
   Program address

</Callout>

## Composite Allow + Block Lists

For maximum compliance control, combine allow and block lists:

```typescript
async function setupCompositeLists(
  mintAddress: Address,
  mintConfigPda: Address,
  payer: TransactionSigner
) {
  // Create ALLOW list
  const allowListSeed = /* unique seed for allow list */;
  const [allowListPda] = await findListConfigPda({
    authority: payer.address,
    seed: allowListSeed,
  });

  const createAllowListIx = getCreateListInstruction({
    authority: payer,
    listConfig: allowListPda,
    mode: Mode.Allow,
    seed: allowListSeed,
  });

  // Create BLOCK list
  const blockListSeed = /* unique seed for block list */;
  const [blockListPda] = await findListConfigPda({
    authority: payer.address,
    seed: blockListSeed,
  });

  const createBlockListIx = getCreateListInstruction({
    authority: payer,
    listConfig: blockListPda,
    mode: Mode.Block,
    seed: blockListSeed,
  });

  // Setup extra metas with BOTH lists
  const [thawExtraMetasPda] = await findThawExtraMetasAccountPda(
    { mint: mintAddress },
    { programAddress: ABL_PROGRAM_ADDRESS }
  );

  const setupMetasIx = getSetupExtraMetasInstruction({
    authority: payer,
    tokenAclMintConfig: mintConfigPda,
    mint: mintAddress,
    extraMetas: thawExtraMetasPda,
    lists: [allowListPda, blockListPda], // Both lists!
  });

  // Send transaction...
  console.log("✅ Composite lists created");
  console.log("   - Allow list: Only whitelisted users can thaw");
  console.log("   - Block list: Blocked users can NEVER thaw");
}
```

### Composite List Behavior

```
┌─────────────────────────────────────────────────────┐
│           COMPOSITE LIST LOGIC                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│   User tries to thaw:                               │
│                                                     │
│   1. Check BLOCK list first                         │
│      └─> On block list? ❌ DENY (always)            │
│                                                     │
│   2. Check ALLOW list                               │
│      └─> On allow list? ✅ ALLOW                    │
│      └─> Not on allow list? ❌ DENY                 │
│                                                     │
│   Key insight: Block list ALWAYS wins!              │
│                                                     │
└─────────────────────────────────────────────────────┘
```

## Use Cases

### 1. Security Tokens (KYC Required)

Use an **Allow list** to ensure only KYC-verified investors can hold tokens:

```typescript
// Create allow list
const createListIx = getCreateListInstruction({
  authority: issuer,
  listConfig: allowListPda,
  mode: Mode.Allow,
  seed: mintAddress
});

// After KYC verification, add investor
await addToAllowList(allowListPda, kycVerifiedInvestor, issuer);
```

### 2. Sanctions Compliance

Use a **Block list** to prevent sanctioned addresses from receiving tokens:

```typescript
// Create block list
const createListIx = getCreateListInstruction({
  authority: complianceOfficer,
  listConfig: blockListPda,
  mode: Mode.Block,
  seed: mintAddress
});

// Block sanctioned address
await addToBlockList(blockListPda, sanctionedAddress, complianceOfficer);
```

### 3. Open Token with PDA Protection

Use **AllowAllEoas** to allow all regular wallets while blocking PDAs (smart
contracts):

```typescript
const createListIx = getCreateListInstruction({
  authority: payer,
  listConfig: listConfigPda,
  mode: Mode.AllowAllEoas, // Regular wallets OK, PDAs blocked
  seed: mintAddress
});
```

### 4. Full Enterprise Compliance

Combine **Allow list + Block list** for complete control:

- Allow list: KYC-verified investors
- Block list: Sanctioned addresses, terminated employees, etc.

## Production Considerations

Before deploying to production:

1. **Security Audits**: Get professional security audits of your implementation
   and any custom Gate Programs

2. **Key Management**: Use proper custody solutions for authority keys. Consider
   multi-sig for sensitive operations

3. **Regulatory Compliance**: Consult legal experts on securities regulations,
   KYC/AML requirements, and sanctions compliance

4. **List Management**: Build robust systems for managing allow/block lists,
   including:
   - Automated sanctions screening integration
   - KYC provider integration
   - Audit logging

5. **Monitoring**: Implement monitoring for:
   - Failed thaw attempts (potential compliance issues)
   - List modifications
   - Authority key usage

6. **Disaster Recovery**: Plan for key rotation, list recovery, and emergency
   freeze procedures

<Callout type="caution" title="Solana CLI Version">

Token ACL with TokenMetadata requires **Solana CLI 2.x**. There's a known issue
with CLI 3.x that breaks the TokenMetadata auto-expansion feature. Always verify
your CLI version before deploying.

</Callout>

## Command-Line Interface (CLI)

Both Token ACL and the ABL Gate Program provide CLIs for managing configs and
lists without writing code. This is useful for operations teams.

### Token ACL CLI

The Token ACL CLI manages mint configurations and freeze/thaw operations.

#### Installation

```bash
# Install from crates.io
cargo install token-acl-cli

# Verify installation
token-acl --version
```

#### Token ACL Commands

| Command                              | Description                                            |
| ------------------------------------ | ------------------------------------------------------ |
| `create-config`                      | Creates a new mint config (transfers freeze authority) |
| `delete-config`                      | Deletes a mint config                                  |
| `set-authority`                      | Sets the authority of a mint config                    |
| `set-gating-program`                 | Sets the gating program for a mint config              |
| `set-instructions`                   | Enable/disable permissionless thaw/freeze              |
| `thaw`                               | Thaws a token account (authority required)             |
| `freeze`                             | Freezes a token account (authority required)           |
| `thaw-permissionless`                | Thaws a token account permissionlessly                 |
| `freeze-permissionless`              | Freezes a token account permissionlessly               |
| `create-ata-and-thaw-permissionless` | Creates ATA and thaws in one command                   |

#### Create a Token ACL Config

```bash
# Create a mint config (delegates freeze authority to Token ACL)
token-acl create-config <MINT_ADDRESS> \
  --gating-program GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz
```

#### Enable Permissionless Thaw

```bash
# Enable permissionless thaw only (recommended for most use cases)
# - Users can self-service unfreeze after passing gate checks
# - Only authority can freeze accounts (security best practice)
token-acl set-instructions --enable-thaw --disable-freeze <MINT_ADDRESS>

# Enable both permissionless thaw AND freeze
# Use case: Allow anyone to freeze blocked users, or users to self-freeze
token-acl set-instructions --enable-thaw --enable-freeze <MINT_ADDRESS>

# Disable all permissionless operations (authority-only mode)
token-acl set-instructions --disable-thaw --disable-freeze <MINT_ADDRESS>
```

#### Thaw/Freeze Operations

```bash
# Thaw an account permissionlessly (user self-service)
token-acl thaw-permissionless <MINT_ADDRESS> <TOKEN_ACCOUNT_ADDRESS>

# Thaw using authority (issuer operation)
token-acl thaw <MINT_ADDRESS> <TOKEN_ACCOUNT_ADDRESS>

# Freeze using authority (compliance enforcement)
token-acl freeze <MINT_ADDRESS> <TOKEN_ACCOUNT_ADDRESS>
```

#### Create ATA and Thaw in One Command

```bash
# Creates associated token account and thaws it automatically
token-acl create-ata-and-thaw-permissionless --mint <MINT_ADDRESS> --owner <WALLET_ADDRESS>
```

### ABL Gate CLI (`allow-block-list`)

The ABL Gate CLI manages allow/block lists and wallet entries.

#### Installation

```bash
# Install from crates.io
cargo install token-acl-gate-cli

# Verify installation (binary is named 'allow-block-list')
allow-block-list --version
```

#### ABL Gate Commands

| Command               | Description                            |
| --------------------- | -------------------------------------- |
| `create-list`         | Creates a new allow/block list         |
| `delete-list`         | Deletes a list                         |
| `add-wallet`          | Adds a wallet to a list                |
| `remove-wallet`       | Removes a wallet from a list           |
| `apply-lists-to-mint` | Configures which lists apply to a mint |

#### Create a List

```bash
# Create an ALLOW list (only whitelisted wallets can thaw)
allow-block-list create-list --mode allow

# Create a BLOCK list (blocked wallets cannot thaw)
allow-block-list create-list --mode block

# Create an ALLOW-ALL-EOAs list (all regular wallets can thaw)
allow-block-list create-list --mode allow-all-eoas
```

The command outputs the `list_config` PDA address and `seed` - **save these!**

#### Manage Wallets on Lists

```bash
# Add wallet to a list (works for both allow and block lists)
allow-block-list add-wallet <LIST_ADDRESS> <WALLET_ADDRESS>

# Remove wallet from a list
allow-block-list remove-wallet <LIST_ADDRESS> <WALLET_ADDRESS>
```

#### Apply Lists to a Mint

```bash
# Apply a single list to a mint
allow-block-list apply-lists-to-mint <MINT_ADDRESS> <LIST_ADDRESS>

# Apply multiple lists (e.g., allow + block for composite compliance)
allow-block-list apply-lists-to-mint <MINT_ADDRESS> <ALLOW_LIST> <BLOCK_LIST>
```

### CLI Global Options

Both CLIs support these options:

| Option                  | Description                           |
| ----------------------- | ------------------------------------- |
| `-u, --url <URL>`       | RPC URL (default: from Solana config) |
| `-k, --payer <KEYPAIR>` | Payer keypair file or hardware wallet |
| `-C, --config <PATH>`   | Solana config file path               |
| `-v, --verbose`         | Show additional information           |

### Complete CLI Workflow Example

Here's a complete workflow using all CLIs to set up a compliant token from
scratch:

```bash
# ============================================================================
# STEP 1: Configure Solana CLI
# ============================================================================
solana config set --url localhost

# ============================================================================
# STEP 2: Create Token22 Mint with Metadata + DefaultAccountState Extensions
# ============================================================================

# Create the mint with:
# - Token-2022 program
# - Freeze authority enabled
# - Default account state = frozen (all new accounts start frozen)
# - Metadata extension with token_acl field for auto-detection
spl-token create-token \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --enable-freeze \
  --default-account-state frozen \
  --enable-metadata

# Output:
# Creating token 7KzLwpXMzKa8JiqYr2ookFjxLx1xMF4xM4YhVqPJpump
# Address: 7KzLwpXMzKa8JiqYr2ookFjxLx1xMF4xM4YhVqPJpump

# Save the mint address for use in subsequent commands
MINT=7KzLwpXMzKa8JiqYr2ookFjxLx1xMF4xM4YhVqPJpump

# Initialize the token metadata
spl-token initialize-metadata $MINT "Compliant Token" "COMP" "https://example.com/metadata.json"

# Add the token_acl field for wallet auto-detection
# This tells wallets/SDKs which gate program to use for thaw
spl-token update-metadata $MINT token_acl GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz

# Verify the token was created correctly
spl-token display $MINT

# ============================================================================
# STEP 3: Create Token ACL Config
# ============================================================================

# This transfers freeze authority from your wallet to the Token ACL MintConfig PDA
token-acl create-config $MINT \
  --gating-program GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz

# Output:
# ✅ Config created for mint 7KzLwpXMzKa8JiqYr2ookFjxLx1xMF4xM4YhVqPJpump
# MintConfig PDA: 9xYzAbCdEfGhIjKlMnOpQrStUvWxYz123456789abc

# ============================================================================
# STEP 4: Create ABL Lists
# ============================================================================

# Create a block list for sanctions compliance
allow-block-list create-list --mode block

# Output:
# list_config: 5HnJkLmNoPqRsTuVwXyZ987654321defghijk
# seed: 3AbCdEfGhIjKlMnOpQrStUvWxYz123456789

# Save the block list address
BLOCK_LIST=5HnJkLmNoPqRsTuVwXyZ987654321defghijk

# ============================================================================
# STEP 5: Apply Lists to Mint
# ============================================================================

# Configure the block list to be used for this mint's permissionless operations
allow-block-list apply-lists-to-mint $MINT $BLOCK_LIST

# ============================================================================
# STEP 6: Enable Permissionless Thaw
# ============================================================================

# Allow users to thaw their own accounts (if not on block list)
# --enable-thaw: Users can self-service unfreeze after passing gate checks
# --disable-freeze: Only authority can freeze
token-acl set-instructions --enable-thaw --disable-freeze $MINT

# ============================================================================
# STEP 7: Manage Block List (Compliance Operations)
# ============================================================================

# To fully block a user, you need TWO steps:
# 1. Add to block list (prevents future thawing)
# 2. Freeze their token account (stops current usage)

# Step 7a: Add wallet to block list
# Replace with actual wallet address to block (must be valid base58 pubkey)
allow-block-list add-wallet $BLOCK_LIST <WALLET_TO_BLOCK>

# Step 7b: Freeze their existing token account (if they have one)
# This requires the token account address, not the wallet address
# spl-token address --verbose --token $MINT to get the token account address
# token-acl freeze <TOKEN_ACCOUNT_ADDRESS>

# Note: Adding to block list alone only prevents them from THAWING.
# If their account is already thawed, they can still use it until you freeze it!

# Later, if sanctions are lifted:
# 1. Remove from block list
# allow-block-list remove-wallet $BLOCK_LIST <WALLET_ADDRESS>
# 2. User can then thaw their account again

# ============================================================================
# STEP 8: User Creates Account and Thaws
# ============================================================================

# A user can now create their token account and thaw it in one command
# Use your own wallet or generate one: solana-keygen new --no-outfile
USER_WALLET=$(solana address)  # Uses your configured wallet
token-acl create-ata-and-thaw-permissionless --mint $MINT --owner $USER_WALLET

# Output:
# ✅ Created ATA: 8AbCdEfGhIjKlMnOpQrStUvWxYz123456789xyz
# ✅ Thawed successfully!

# ============================================================================
# STEP 9: Mint Tokens to User
# ============================================================================

# Now the issuer can mint tokens to the user's thawed account
spl-token mint $MINT 1000 --recipient-owner $USER_WALLET

# Verify balance
spl-token balance $MINT
```

<Callout type="info" title="Token Metadata for Auto-Detection">

Adding the `token_acl` metadata field is crucial for wallet integration. When
wallets like Phantom or SDKs like `@solana/token-helpers` see this field, they
automatically include thaw instructions when creating token accounts.

```bash
spl-token update-metadata $MINT token_acl GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz
```

</Callout>

## Next Steps

1. **Try the Workshop**: Clone the
   [token-acl repository](https://github.com/Woody4618/access-control-list-acl-demo)
   and run the demo examples. Read through the implementation of the
   [ACL](https://github.com/solana-foundation/token-acl) and the
   [ABL Gate Program](https://github.com/solana-foundation/token-acl-gate).

2. **Build Custom Gate Programs**: The ABL Gate Program is just a reference
   implementation. Build your own Gate Program to integrate with your existing
   compliance infrastructure, identity providers, or implement custom logic that
   fits your specific requirements

3. **Integrate with DeFi**: Token ACL tokens are fully composable with DeFi
   protocols

4. **Read the Specification**: Review
   [sRFC37](https://github.com/solana-foundation/token-acl/blob/main/srfc37.md)
   for the complete technical specification and join the
   [sRFC37 discussion](https://github.com/solana-foundation/SRFCs/discussions/2)

## Conclusion

Token ACL (sRFC37) provides a powerful solution for enterprises that need
compliant, permissioned tokens without sacrificing the user experience that
makes blockchain valuable. Key benefits:

- **Instant Activation**: Users can self-service thaw their accounts
- **Full Compliance Control**: Allow lists, block lists, or custom logic
- **Flexible Gate Programs**: Use the reference ABL implementation or build
  custom Gate Programs that integrate with your compliance infrastructure
- **Seamless Integration**: SDKs handle complexity automatically
- **Composable**: Works with existing DeFi protocols
- **Audited**: Production-ready programs deployed on mainnet

The combination of Token-2022's `DefaultAccountState` extension with Token ACL's
permissionless operations creates a new paradigm for compliant token issuance on
Solana.
