Token ACL (Access Control List) is a Solana program that enables compliant, permissioned tokens without sacrificing user experience. It implements sRFC37, 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:
- Enforce KYC/AML requirements
- Block sanctioned addresses
- 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 ││ │└─────────────────────────────────────────────────────┘
Educational Reference Implementation
This guide includes a complete working implementation you can run locally. The source code provides reference implementations for exploration and educational purposes.
The code of the ACL programs is available in the token-acl repository and the ABL Gate Program is available in the abl-gate-program repository.
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
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 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
- On-chain analytics - track all token movements
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.
Architecture Overview
Token ACL consists of three main components:
- Token ACL Program: The core program that manages freeze authority delegation and permissionless operations
- Gate Program: Custom logic that determines who can thaw/freeze (e.g., ABL Gate Program for allow/block lists)
- 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
-
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.
-
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., on-chain KYC verification, oracle-based sanctions checks, or integration with identity protocols).
-
Permissionless Operations: Users can thaw their own accounts without issuer intervention, as long as the Gate Program approves.
-
TokenMetadata Integration: Adding a
token_aclfield to your mint's metadata enables automatic detection by wallets and SDKs like@solana/token-helpers.
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.
ABL Gate Program Modes
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 on-chain 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.
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 |
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.
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:
-
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)
solana --version -
Node.js 18+ and pnpm
-
Local validator with required programs:
solana-test-validator \--clone TACLkU6CiCdkQN2MjoyDkVg2yAH9zkxiHDsiztQ52TP \--clone GEC5tu9eaZQrNS7ohERwZRqyvLvV8k2iVZqqt6VuwvJu \--clone GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz \--clone D2GUvBwbnkFu3R5s1rz5dcBJ81UsqY3nvHbLdeJLtSx5 \--url devnet \--reset
Complete Implementation
Step 1: Install Dependencies
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:
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 SDKimport {getCreateConfigInstruction,findMintConfigPda,getTogglePermissionlessInstructionsInstruction,findThawExtraMetasAccountPda} from "@token-acl/sdk";// ABL Gate Program SDKimport {getCreateListInstruction,getSetupExtraMetasInstruction,getAddWalletInstruction,findListConfigPda,findWalletEntryPda,ABL_PROGRAM_ADDRESS,Mode} from "@token-acl/abl-sdk";// TLV sizes for Token-2022 extensionsconst TYPE_SIZE = 2;const LENGTH_SIZE = 2;async function createTokenACLMint() {// Setup RPCconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");const sendAndConfirm = sendAndConfirmTransactionFactory({rpc,rpcSubscriptions});// Load your payer keypairconst payer = await loadKeypair("~/.config/solana/id.json");// Generate mint keypairconst mint = await generateKeyPairSigner();console.log(`🪙 Mint: ${mint.address}`);// TokenMetadata config - includes 'token_acl' for auto-detectionconst TOKEN_NAME = "Compliant Token";const TOKEN_SYMBOL = "COMP";const TOKEN_URI = "";const TOKEN_ACL_KEY = "token_acl";// Define extensionsconst defaultAccountStateExtension = extension("DefaultAccountState", {state: AccountState.Frozen});const metadataPointerExtension = extension("MetadataPointer", {authority: payer.address,metadataAddress: mint.address});const extensions = [defaultAccountStateExtension, metadataPointerExtension];// Calculate mint sizeconst 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 rentconst mintRent = await rpc.getMinimumBalanceForRentExemption(BigInt(totalSpace)).send();// Get extension pre-initialization instructionsconst extensionInstructions = getPreInitializeInstructionsForMintExtensions(mint.address,extensions);// Build transactionconst { 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 sendconst 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:
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:
// AllowAllEoas - All regular wallets can thaw automaticallyasync function setupAllowAllEoas(mintAddress: Address,mintConfigPda: Address,payer: TransactionSigner) {const listSeed = mintAddress; // Use mint as seedconst [listConfigPda] = await findListConfigPda({authority: payer.address,seed: listSeed});const createListIx = getCreateListInstruction({authority: payer,listConfig: listConfigPda,mode: Mode.AllowAllEoas, // All EOAs can thawseed: 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");}
Step 5: Enable Permissionless Thaw
Enable users to thaw their own accounts:
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:
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 SDKconst accountRetriever = async (addr: Address) => {return await fetchEncodedAccount(rpc, addr);};// The SDK handles all the complexity of fetching extra metasconst thawIx =await createThawPermissionlessIdempotentInstructionWithExtraMetas(payer, // authority (signer)userAta, // token account to thawmintAddress, // mintuserAddress, // token account ownerTOKEN_ACL_PROGRAM_ADDRESS, // Token ACL programaccountRetriever // 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:
import { createAndConfirmAssociatedTokenAccount } from "@solana/token-helpers";// This automatically includes thaw instruction if mint has 'token_acl' metadataconst { signature, associatedTokenAddress } =await createAndConfirmAssociatedTokenAccount(rpc,rpcSubscriptions,payer,user.address,mintAddress,true // idempotent);console.log(`✅ Account created AND thawed automatically!`);console.log(` ATA: ${associatedTokenAddress}`);
TokenMetadata Requirement
For @solana/token-helpers auto-detection to work, your mint must have:
- The
TokenMetadataextension initialized - An
additionalMetadatafield with keytoken_acland value set to the Gate Program address
Composite Allow + Block Lists
For maximum compliance control, combine allow and block lists:
async function setupCompositeLists(mintAddress: Address,mintConfigPda: Address,payer: TransactionSigner) {// Create ALLOW listconst 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 listconst 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 listsconst [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:
// Create allow listconst createListIx = getCreateListInstruction({authority: issuer,listConfig: allowListPda,mode: Mode.Allow,seed: mintAddress});// After KYC verification, add investorawait addToAllowList(allowListPda, kycVerifiedInvestor, issuer);
2. Sanctions Compliance
Use a Block list to prevent sanctioned addresses from receiving tokens:
// Create block listconst createListIx = getCreateListInstruction({authority: complianceOfficer,listConfig: blockListPda,mode: Mode.Block,seed: mintAddress});// Block sanctioned addressawait addToBlockList(blockListPda, sanctionedAddress, complianceOfficer);
3. Open Token with PDA Protection
Use AllowAllEoas to allow all regular wallets while blocking PDAs (smart contracts):
const createListIx = getCreateListInstruction({authority: payer,listConfig: listConfigPda,mode: Mode.AllowAllEoas, // Regular wallets OK, PDAs blockedseed: 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:
-
Security Audits: Get professional security audits of your implementation and any custom Gate Programs
-
Key Management: Use proper custody solutions for authority keys. Consider multi-sig for sensitive operations
-
Regulatory Compliance: Consult legal experts on securities regulations, KYC/AML requirements, and sanctions compliance
-
List Management: Build robust systems for managing allow/block lists, including:
- Automated sanctions screening integration
- KYC provider integration
- Audit logging
-
Monitoring: Implement monitoring for:
- Failed thaw attempts (potential compliance issues)
- List modifications
- Authority key usage
-
Disaster Recovery: Plan for key rotation, list recovery, and emergency freeze procedures
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.
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
# Install from crates.iocargo install token-acl-cli# Verify installationtoken-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
# Create a mint config (delegates freeze authority to Token ACL)token-acl create-config <MINT_ADDRESS> \--gating-program GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz
Enable Permissionless Thaw
# 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-freezetoken-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
# 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
# Creates associated token account and thaws it automaticallytoken-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
# Install from crates.iocargo 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
# 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
# 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 listallow-block-list remove-wallet <LIST_ADDRESS> <WALLET_ADDRESS>
Apply Lists to a Mint
# Apply a single list to a mintallow-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:
# ============================================================================# 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-detectionspl-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 commandsMINT=7KzLwpXMzKa8JiqYr2ookFjxLx1xMF4xM4YhVqPJpump# Initialize the token metadataspl-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 thawspl-token update-metadata $MINT token_acl GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz# Verify the token was created correctlyspl-token display $MINT# ============================================================================# STEP 3: Create Token ACL Config# ============================================================================# This transfers freeze authority from your wallet to the Token ACL MintConfig PDAtoken-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 complianceallow-block-list create-list --mode block# Output:# list_config: 5HnJkLmNoPqRsTuVwXyZ987654321defghijk# seed: 3AbCdEfGhIjKlMnOpQrStUvWxYz123456789# Save the block list addressBLOCK_LIST=5HnJkLmNoPqRsTuVwXyZ987654321defghijk# ============================================================================# STEP 5: Apply Lists to Mint# ============================================================================# Configure the block list to be used for this mint's permissionless operationsallow-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 freezetoken-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-outfileUSER_WALLET=$(solana address) # Uses your configured wallettoken-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 accountspl-token mint $MINT 1000 --recipient-owner $USER_WALLET# Verify balancespl-token balance $MINT
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.
spl-token update-metadata $MINT token_acl GATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz
Next Steps
-
Try the Workshop: Clone the token-acl repository and run the demo examples. Read through the implementation of the ACL and the ABL Gate Program.
-
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
-
Integrate with DeFi: Token ACL tokens are fully composable with DeFi protocols
-
Read the Specification: Review sRFC37 for the complete technical specification and join the sRFC37 discussion
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.