Most token risk tools on Solana rely on proprietary black-box APIs that return an arbitrary numerical risk score. For engineers building trading terminals, swap routers, or execution pipelines, depending on an external API for pre-trade safety introduces unnecessary RPC latency, rate limits, and failure modes.
Token security on Solana is not abstract. The security characteristics of any SPL token are explicitly encoded in the on-chain account data of its mint account. By parsing these byte payloads directly via RPC before constructing execution instructions, developers can deterministically detect rug vectors like active mint authorities, freeze flags, and restrictive Token-2022 extensions.
The SPL Mint Account Structure
A standard SPL Token Mint account occupies exactly 82 bytes of storage. The binary layout is defined strictly by the SPL Token program specification:
-
mintAuthorityOption(u32, 4 bytes): Indicator whether a mint authority exists (1) or was revoked (0). -
mintAuthority(Pubkey, 32 bytes): The address capable of minting new supply. -
supply(u64, 8 bytes): Total token supply currently in circulation. -
decimals(u8, 1 byte): Number of base 10 decimals for representation. -
isInitialized(bool, 1 byte): Flag confirming account initialization. -
freezeAuthorityOption(u32, 4 bytes): Indicator whether a freeze authority exists (1) or was revoked (0). -
freezeAuthority(Pubkey, 32 bytes): The address capable of freezing user token accounts.
When evaluating token safety programmatically, two fields matter above all: mintAuthority and freezeAuthority. If mintAuthorityOption is non-zero, the holder of mintAuthority can dilute token supply mid-trade. If freezeAuthorityOption is non-zero, the designated key can invoke FreezeAccount on any user associated token account (ATA), rendering funds un-transferable.
Token-2022 Extensions and New Attack Vectors
With the adoption of Token-2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb), simple 82-byte checks are no longer sufficient. Token-2022 introduces extension pointers stored as Type-Length-Value (TLV) data appended directly after the standard mint layout bytes.
Engineers evaluating Token-2022 mints must parse TLV structures to detect critical security implications:
-
Permanent Delegate (
ExtensionType.PermanentDelegate): Grants a master account full authority to transfer or burn tokens from any wallet without user signature. -
Transfer Fee (
ExtensionType.TransferFeeConfig): Sets dynamic fee percentages on every token transfer. Dynamic fees set to maximum effectively act as honeypots. -
Non-Transferable Tokens (
ExtensionType.NonTransferable): Enforces soulbound mechanics, preventing any secondary market transfer. -
Default Account State (
ExtensionType.DefaultAccountState): Can force newly initialized ATAs into a frozen state by default.
Implementation: Programmatic Mint Inspection
To inspect a token prior to routing or wallet signing, read the raw account data using @solana/spl-token and analyze its state:
import { Connection, PublicKey } from '@solana/web3.js';
import { getMint, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, getExtensionTypes, ExtensionType } from '@solana/spl-token';
interface TokenRiskProfile {
isMintable: boolean;
isFreezable: boolean;
hasPermanentDelegate: boolean;
hasTransferFee: boolean;
isNonTransferable: boolean;
programId: string;
}
async function inspectTokenSecurity(
connection: Connection,
mintAddress: PublicKey
): Promise<TokenRiskProfile> {
const accountInfo = await connection.getAccountInfo(mintAddress);
if (!accountInfo) throw new Error('Mint account not found');
const programId = accountInfo.owner;
const isToken2022 = programId.equals(TOKEN_2022_PROGRAM_ID);
const mintData = await getMint(
connection,
mintAddress,
'confirmed',
programId
);
let hasPermanentDelegate = false;
let hasTransferFee = false;
let isNonTransferable = false;
if (isToken2022 && accountInfo.data.length > 82) {
const extensions = getExtensionTypes(mintData.tlvData);
hasPermanentDelegate = extensions.includes(ExtensionType.PermanentDelegate);
hasTransferFee = extensions.includes(ExtensionType.TransferFeeConfig);
isNonTransferable = extensions.includes(ExtensionType.NonTransferable);
}
return {
isMintable: mintData.mintAuthority !== null,
isFreezable: mintData.freezeAuthority !== null,
hasPermanentDelegate,
hasTransferFee,
isNonTransferable,
programId: programId.toBase58(),
};
}
Routing Logic and Risk Assertion
Once the TokenRiskProfile is constructed, transaction pipelines enforce execution policies before submitting route requests to aggregators:
function assertTokenSafety(profile: TokenRiskProfile): void {
if (profile.isFreezable) {
throw new Error('Execution blocked: Active freeze authority detected');
}
if (profile.hasPermanentDelegate) {
throw new Error('Execution blocked: Permanent delegate extension active');
}
if (profile.isNonTransferable) {
throw new Error('Execution blocked: Token marked as non-transferable');
}
}
Performing these checks client-side or at the RPC edge removes third-party API dependencies and guarantees sub-millisecond safety evaluations. At Verixia (verixiaapps.com), pre-execution safety pipelines parse these exact on-chain account layouts to shield users from honeypots and authority exploits before transactions ever reach Jupiter routing or wallet signing prompts.
Written by the team at Verixia, a Solana swap interface routing through Jupiter.
Top comments (0)