Permissioned Tokens with Token ACL (sRFC37)

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:

  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 │
│ │
└─────────────────────────────────────────────────────┘

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?

AspectTraditional FrozenToken ACL
Account ActivationManual (minutes/days)Instant (self-service)
User ExperiencePoorSeamless
Compliance ControlFullFull
Sanctions BlockingManualAutomatic via Gate Program
Integration EffortHighLow (SDK available)
ComposabilityLimitedFull (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:

AspectToken ACLTransfer Hooks
When Logic RunsOnly on freeze/thaw operationsOn every transfer
Transfer OverheadNone - transfers are standardExtra CUs + accounts on each transfer
Account DependenciesOnly during account activationRequired on every transfer transaction
DeFi ComposabilityFull - protocols work normallyLimited - many protocols blacklist
Best ForKYC/AML, sanctions, allow/block listsRoyalties, custom transfer validation
Complexity for UsersLow - one-time thaw operationHigher - 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:

  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., on-chain 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.

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:

ModeDescriptionUse Case
AllowAllEoasAll regular wallets (non-PDAs) can thawOpen tokens with PDA blocking
AllowOnly wallets on the allow list can thawKYC-required tokens
BlockAll wallets EXCEPT those on block list can thawSanctions compliance
CompositeCombine allow + block listsFull 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.

ProgramAddress
Token ACLTACLkU6CiCdkQN2MjoyDkVg2yAH9zkxiHDsiztQ52TP
ABL Gate ProgramGATEzzqxhJnsWF6vHRsgtixxSB8PaQdcqGEVTEHWiULz

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)

    solana --version
  2. Node.js 18+ and pnpm

  3. 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 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:

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 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");
}

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

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}`);

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

Composite Allow + Block Lists

For maximum compliance control, combine allow and block lists:

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:

// 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:

// 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):

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

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.io
cargo install token-acl-cli
# Verify installation
token-acl --version

Token ACL Commands

CommandDescription
create-configCreates a new mint config (transfers freeze authority)
delete-configDeletes a mint config
set-authoritySets the authority of a mint config
set-gating-programSets the gating program for a mint config
set-instructionsEnable/disable permissionless thaw/freeze
thawThaws a token account (authority required)
freezeFreezes a token account (authority required)
thaw-permissionlessThaws a token account permissionlessly
freeze-permissionlessFreezes a token account permissionlessly
create-ata-and-thaw-permissionlessCreates 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-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

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

# 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

CommandDescription
create-listCreates a new allow/block list
delete-listDeletes a list
add-walletAdds a wallet to a list
remove-walletRemoves a wallet from a list
apply-lists-to-mintConfigures 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 list
allow-block-list remove-wallet <LIST_ADDRESS> <WALLET_ADDRESS>

Apply Lists to a Mint

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

OptionDescription
-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, --verboseShow 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-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

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

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

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

Quản lý bởi

© 2026 Solana Foundation.
Đã đăng ký bản quyền.
Kết nối
Permissioned Tokens with Token ACL (sRFC37) | Solana