Stablecoins on Solana: mint registry, decimals, and Token-2022 extensions
Every major Solana stablecoin — mint addresses, decimals, token program, and the Token-2022 extensions they ship. With code to detect each at runtime.
Every Solana stablecoin looks the same in your wallet — a token balance, a transfer, a swap. Under the hood, they diverge wildly: different token programs, different decimals, different Token-2022 extensions. If you're integrating against more than one, the differences matter the first time you hit a hook that rejects your transfer or a confidential balance you can't read.
This is the technical sheet: which mint is which, what program owns it, what extensions it carries, and code to detect everything at runtime.
The mints
The canonical mint addresses for the major Solana stablecoins:
// SPL Token (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)
const USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // 6 decimals
const USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" // 6 decimals
const FDUSD = "9zNQRsGLjNKwCUU5Gq5LR8beUCPzQMVMqKAi3SSZh54u" // 6 decimals (verify)
// Token-2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb)
const PYUSD = "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo" // 6 decimals
const USDG = "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH" // 6 decimals (verify)
const USDY = "A1KLoBrKBde8Ty9qtNQUtq3C2ortoC3u7twggz7sEto6" // 6 decimals
const AUSD = "AUSD1jCcaVrbfDNQyhrZkjnB4xPPquYAR9TgRgkkw5dT" // verify mint
const MXNB = "MXNeFcZjVe1q1KGZFNZBJVZpe6QFEhNGvL3uHpKjDfx" // 6 decimals (verify)Verify these against the issuer's documentation before production use — issuers occasionally migrate mints and the ecosystem catalog lags. Authoritative sources: Circle (USDC), stablesonsolana.com, and each issuer's docs.
Detecting which program owns a mint
A mint's owner tells you whether to use SPL Token or Token-2022 program IDs in your instructions:
import { Connection, PublicKey } from "@solana/web3.js"
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"
async function detectTokenProgram(conn: Connection, mint: PublicKey) {
const info = await conn.getAccountInfo(mint)
if (!info) throw new Error("mint not found")
if (info.owner.equals(TOKEN_PROGRAM_ID)) return "spl-token"
if (info.owner.equals(TOKEN_2022_PROGRAM_ID)) return "token-2022"
throw new Error("not a recognised token mint")
}Always do this dynamically. A new stablecoin landing on Solana in 2026 is almost certainly Token-2022; older ones may still be SPL Token. Hard-coding the program ID is a bug waiting to happen.
Enumerating extensions on a Token-2022 mint
Token-2022 mints append extension bytes after the base mint data. The SDK gives you a typed view:
import {
getMint,
getExtensionTypes,
ExtensionType,
TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token"
const mint = await getMint(conn, PYUSD, "confirmed", TOKEN_2022_PROGRAM_ID)
const extensions = getExtensionTypes(mint.tlvData)
// extensions might include:
// ExtensionType.MetadataPointer
// ExtensionType.TokenMetadata
// ExtensionType.PermanentDelegate
// ExtensionType.DefaultAccountState
// ExtensionType.TransferHook
// ExtensionType.MintCloseAuthority
// ExtensionType.ConfidentialTransferMint
// ExtensionType.InterestBearingConfig
// ExtensionType.GroupPointer / GroupMemberPointer
// ExtensionType.TransferFeeConfig
for (const ext of extensions) {
console.log(ExtensionType[ext], "→ enabled on this mint")
}What each major stablecoin actually uses
Common extension shapes you'll see, by issuer pattern:
USDC, USDT, FDUSD (SPL Token): no extensions — these predate Token-2022 and stayed on the legacy program. Plain mint authority + freeze authority, both held by the issuer.
PYUSD, USDG (Token-2022, regulated USD): typically MetadataPointer + TokenMetadata (on-mint metadata, no Metaplex hop) + PermanentDelegate (compliance forced-transfer) + DefaultAccountState: Frozen + freeze authority for KYC gating. Some also wire TransferHook for live KYC attestation.
USDY (Token-2022, yieldcoin): InterestBearingConfig for the auto-accruing balance, plus the same compliance extensions as PYUSD-class stables.
AUSD (Token-2022, revenue-share): similar compliance shape to PYUSD plus a TransferHook Ondo uses for distribution-partner revenue attribution.
MXNB and other local-currency stables: match the PYUSD shape but commission-aware TransferFeeConfig is increasingly common on emerging-market stables.
Transferring a Token-2022 stablecoin with extensions
Use the extensions-aware helper rather than the legacy transfer. The helper handles TransferHook account resolution and TransferFee math automatically:
import {
transferCheckedWithFee,
transferCheckedWithTransferHook,
TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token"
// For a TransferFee mint:
const sig = await transferCheckedWithFee(
conn, payer, source, mint, destination, owner,
/* amount */ 1_000_000n, // 1 PYUSD (6 decimals)
/* decimals */ 6,
/* fee */ await calculateFee(conn, mint, 1_000_000n),
[], { commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID,
)
// For a TransferHook mint (resolves hook program + extra accounts):
const sig2 = await transferCheckedWithTransferHook(
conn, payer, source, mint, destination, owner,
1_000_000n, 6, [],
{ commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID,
)Calling the wrong helper (legacy transfer against a PYUSD mint with a hook) will fail at the validator with Custom program error: 0x1 or a hook-specific code, depending on the extension. Always branch on detected extensions.
Reading a holder's balance the right way
For an interest-bearing stable (USDY), the raw token account balance is the principal, not the displayed amount:
import { amountToUiAmountForMintWithoutSimulation } from "@solana/spl-token"
// Wrong: shows principal, ignores accrued interest
const raw = await conn.getTokenAccountBalance(tokenAccount)
console.log(raw.value.uiAmountString)
// Right: queries the mint's interest config and renders accrued balance
const displayAmount = await amountToUiAmountForMintWithoutSimulation(
conn, USDY, BigInt(raw.value.amount),
)
console.log(displayAmount) // e.g. "100.0421" instead of "100.0000"Reference table
Stable | Program | Decimals | Likely extensions
--------|-------------|----------|----------------------------------------------
USDC | SPL Token | 6 | (none) — mint+freeze authority only
USDT | SPL Token | 6 | (none)
FDUSD | SPL Token | 6 | (none)
PYUSD | Token-2022 | 6 | MetadataPointer, TokenMetadata,
| | | PermanentDelegate, DefaultAccountState (Frozen)
USDG | Token-2022 | 6 | similar to PYUSD
USDY | Token-2022 | 6 | InterestBearingConfig + compliance shape
AUSD | Token-2022 | 6 | TransferHook + compliance shape
MXNB | Token-2022 | 6 | compliance + sometimes TransferFeeConfigReferences
- Token-2022 extensions reference
- stablesonsolana.com — live mint registry and dominance
- Solscan stablecoin leaderboard
- Token-2022 extensions field guide — the per-extension deep dive
Integration heuristic: detect the program at runtime, enumerate extensions, branch on the ones that change semantics. Everything else is plumbing.