NoLimitNodes
PricingDocsBlogAbout
SupportContact
Log in
Blog/Engineering

Build a Solana Token Risk Scanner: Mint Authority, Freeze Authority, Metadata and Creator Wallets

Mint authority left set. Freeze authority sitting there. Mutable metadata. Build a Python scanner that catches all four token risk signals before you buy.

N
NoLimitNodes Engineering
Infrastructure Team
Aug 2, 202610 min read
On this page +
  • 01What the Token Program Exposes
  • 02Fetching Mint Account Data
  • 03Checking Authority Fields
  • 04Verifying Metadata
  • 05Scoring Creator Wallets
  • 06Full Scanner Script
  • 07Frequently Asked Questions

Token launched. Four hours later, liquidity was gone. The mint authority was never revoked. Whoever held that keypair could call MintTo at any point and inflate supply without limit. One on-chain read would've caught it.

Three more signals sit in the same account. None of them require an API key to read. Most scanners skip them.

This article builds a Python scanner that checks all four before you touch a token.

What the Token Program Exposes#

Every Solana token has a mint account controlled by the SPL Token program. That account stores a fixed 82-byte structure called MintInfo. No decoding library needed. It's raw binary with a documented layout.

The fields that matter:

BytesSizeFieldTypeRisk signal
0–34mint_authority_optionu32no
4–3532mint_authorityPubkeyYES if option = 1
36–438supplyu64no
441decimalsu8no
451is_initializedboolno
46–494freeze_authority_optionu32no
50–8132freeze_authorityPubkeyYES if option = 1

The option field is a COption tag. Zero means the authority is revoked. One means it's active and the 32-byte pubkey that follows is the holder. Two fields. Four bytes each. That's all you need to read.

MintInfo 82-byte layout showing mint authority, freeze authority, supply, decimals, and is_initialized fields with risk indicators

Fetching Mint Account Data#

Every mint account is readable with a single RPC call. Public endpoints do not require an API key for one-off reads, though their normal rate limits still apply.

fetch_mint.py
python
import base64, struct, requests

def fetch_mint_data(mint_address: str, rpc_url: str) -> bytes:
    payload = {
        "jsonrpc": "2.0", "id": 1,
        "method": "getAccountInfo",
        "params": [mint_address, {"encoding": "base64"}]
    }
    r = requests.post(rpc_url, json=payload).json()
    data_b64 = r["result"]["value"]["data"][0]
    return base64.b64decode(data_b64)

The response includes an owner field. For standard SPL tokens it's TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA. If you're seeing TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb instead, it's a Token-2022 mint. The base 82-byte layout is the same, but Token-2022 appends extension data after it. Check the owner before you parse.

The returned bytes are exactly 82 for a clean SPL token. Verify len(data) >= 82 before you unpack.

Checking Authority Fields#

Two fields. Both follow the same COption pattern. Both live in the first and last third of the 82-byte account.

check_authorities.py
python
def check_authorities(data: bytes) -> dict:
    mint_opt   = struct.unpack_from("<I", data, 0)[0]
    mint_auth  = data[4:36].hex() if mint_opt == 1 else None

    freeze_opt  = struct.unpack_from("<I", data, 46)[0]
    freeze_auth = data[50:82].hex() if freeze_opt == 1 else None

    return {
        "mint_authority":   mint_auth,
        "mint_auth_risk":   "SET"     if mint_opt   == 1 else "REVOKED",
        "freeze_authority": freeze_auth,
        "freeze_auth_risk": "SET"     if freeze_opt == 1 else "REVOKED",
    }

mint_auth_risk: SET means whoever holds that keypair can call MintTo right now. No timelock. No warning. They can double supply in a single transaction. Your position dilutes instantly.

freeze_auth_risk: SET means whoever holds that keypair can call FreezeAccount on your token balance. You can't send it. You can't sell it. The SOL in your wallet is untouched but the token balance is locked until they call ThawAccount.

Both being REVOKED means neither instruction can ever be called again. That's the only state worth calling safe.

Verifying Metadata#

Metaplex stores token metadata at a program-derived address. Three seeds: the byte string "metadata", the Token Metadata program ID, and the mint pubkey. The resulting PDA holds the token's name, symbol, URI, and two flags that matter: is_mutable and update_authority.

parse_metadata.py
python
import struct
from base58 import b58encode

TOKEN_METADATA_PROGRAM = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

def parse_metadata(data: bytes) -> dict:
    # Fixed prefix: 1 byte key + 32 bytes update_authority + 32 bytes mint
    update_authority = b58encode(data[1:33]).decode()
    offset = 65

    name_len = struct.unpack_from("<I", data, offset)[0]
    offset += 4
    name = data[offset:offset + name_len].decode("utf-8").rstrip("\x00")
    offset += name_len

    sym_len = struct.unpack_from("<I", data, offset)[0]
    offset += 4 + sym_len

    uri_len = struct.unpack_from("<I", data, offset)[0]
    offset += 4 + uri_len

    offset += 2  # seller_fee_basis_points

    creators_opt = data[offset]; offset += 1
    creators = []
    if creators_opt == 1:
        count = struct.unpack_from("<I", data, offset)[0]; offset += 4
        for _ in range(count):
            addr     = b58encode(data[offset:offset + 32]).decode(); offset += 32
            verified = bool(data[offset]); offset += 1
            share    = data[offset]; offset += 1
            creators.append({"address": addr, "verified": verified, "share": share})

    offset += 1  # primary_sale_happened
    is_mutable = bool(data[offset])

    return {
        "name":          name,
        "update_authority": update_authority,
        "is_mutable":    is_mutable,
        "metadata_risk": "MUTABLE" if is_mutable else "IMMUTABLE",
        "creators":      creators,
    }

is_mutable: True means the name, symbol, and image URI can all be changed by whoever controls update_authority. Projects have used this to swap a token's branding post-launch. If you're scanning at the time of purchase, the metadata you see may not be the metadata you hold tomorrow.

An update_authority pointing to a recognisable dead address (1111...1111) or a locked program means the metadata's frozen in practice even if is_mutable is still true. Flag both and let the context decide.

Scoring Creator Wallets#

The Metaplex creators array holds up to five entries. Each has an address, a verified boolean, and a share percentage. Only check verified creators. An unverified entry can be added by the update_authority without the creator's consent. If it's unverified, it's not reliable for attribution.

For each verified creator, three things to check:

Wallet age. Pull the creator's first transaction timestamp. Fresh wallets created days before a token launch are a pattern.

Prior launches. How many tokens has this wallet deployed? One or two is normal. Fifteen is a signal.

Rug history. For each prior token: did liquidity leave within seven days of launch? Three or more tokens with early liquidity removal puts the creator in flagged territory.

score_creator.py
python
def score_creator(creator_address: str, token_creations: list, wallet_transfers: list) -> dict:
    tokens_created = [t for t in token_creations if t["creator"] == creator_address]

    rugged = []
    for token in tokens_created:
        mint    = token["mint"]
        outflows = [
            tx for tx in wallet_transfers
            if tx["mint"] == mint
            and tx["direction"] == "out"
            and tx["days_since_launch"] <= 7
        ]
        total_out = sum(tx["amount"] for tx in outflows)
        if token["initial_liquidity"] > 0:
            if total_out > token["initial_liquidity"] * 0.9:
                rugged.append(mint)

    risk = "FLAGGED" if len(rugged) >= 3 else "CLEAN"
    return {
        "creator":        creator_address,
        "tokens_created": len(tokens_created),
        "rugged_count":   len(rugged),
        "creator_risk":   risk,
    }

Running this at scale means you need two data sources indexed by creator address. NLN Token Creations covers every token launch on Solana with the deploying wallet, initial supply, program attribution, and launch timestamp. Cross-reference with Wallet Transfers to measure SOL and SPL outflows per token per day after launch. The Token Creations data includes decimals, mint address, and the creating transaction signature, so you can build a creator reputation index without hitting the RPC for each token individually.

Creator wallet network graph showing connections to five token launches, three marked as rugged, triggering a creator flagged verdict

Full Scanner Script#

One function. Four checks. A verdict on the way out.

token_risk_scanner.py
python
import base64, struct, requests
from base58 import b58encode

RPC_URL = "https://api.mainnet-beta.solana.com"

def fetch_account(address: str) -> bytes:
    r = requests.post(RPC_URL, json={
        "jsonrpc": "2.0", "id": 1,
        "method": "getAccountInfo",
        "params": [address, {"encoding": "base64"}]
    }).json()
    return base64.b64decode(r["result"]["value"]["data"][0])

def check_authorities(data: bytes) -> dict:
    mint_opt   = struct.unpack_from("<I", data, 0)[0]
    freeze_opt = struct.unpack_from("<I", data, 46)[0]
    return {
        "mint_auth_risk":   "SET" if mint_opt   == 1 else "REVOKED",
        "freeze_auth_risk": "SET" if freeze_opt == 1 else "REVOKED",
    }

def parse_metadata(data: bytes) -> dict:
    update_authority = b58encode(data[1:33]).decode()
    offset = 65
    name_len = struct.unpack_from("<I", data, offset)[0]; offset += 4
    name = data[offset:offset + name_len].decode("utf-8").rstrip("\x00"); offset += name_len
    sym_len  = struct.unpack_from("<I", data, offset)[0]; offset += 4 + sym_len
    uri_len  = struct.unpack_from("<I", data, offset)[0]; offset += 4 + uri_len
    offset  += 2
    creators_opt = data[offset]; offset += 1
    creators = []
    if creators_opt == 1:
        count = struct.unpack_from("<I", data, offset)[0]; offset += 4
        for _ in range(count):
            addr     = b58encode(data[offset:offset + 32]).decode(); offset += 32
            verified = bool(data[offset]); offset += 1
            share    = data[offset]; offset += 1
            creators.append({"address": addr, "verified": verified, "share": share})
    offset += 1  # primary_sale_happened
    is_mutable = bool(data[offset])
    return {"name": name, "update_authority": update_authority,
            "is_mutable": is_mutable,
            "metadata_risk": "MUTABLE" if is_mutable else "IMMUTABLE",
            "creators": creators}

def scan_token(mint_address: str) -> dict:
    mint_data = fetch_account(mint_address)
    auth      = check_authorities(mint_data)

    # Fetch metadata PDA (derive address using solders or solana-py)
    # meta = parse_metadata(fetch_account(metadata_pda))
    # For this example, stub the metadata result:
    meta = {"metadata_risk": "MUTABLE", "creators": []}

    flags = sum([
        auth["mint_auth_risk"]    == "SET",
        auth["freeze_auth_risk"]  == "SET",
        meta["metadata_risk"]     == "MUTABLE",
        any(c.get("creator_risk") == "FLAGGED" for c in meta["creators"]),
    ])

    verdict = ["LOW", "MEDIUM", "HIGH", "CRITICAL"][min(flags, 3)]
    result  = {**auth, **meta, "flags": flags, "verdict": verdict}

    print(f"\nToken:            {mint_address}")
    print(f"Mint authority:   {auth['mint_auth_risk']}")
    print(f"Freeze authority: {auth['freeze_auth_risk']}")
    print(f"Metadata:         {meta['metadata_risk']}")
    print(f"Flags: {flags}/4   Verdict: {verdict}\n")
    return result

if __name__ == "__main__":
    import sys
    scan_token(sys.argv[1])

Verdict logic: 0 flags means LOW. One flag means MEDIUM. Two means HIGH. Three or four means CRITICAL. Run it before you buy. Run it again a week later if you're still holding. is_mutable can flip while you sleep.

Animated risk matrix showing four token risk signals checked row by row with a combined verdict score

Frequently Asked Questions#

What is mint authority on a Solana token?

Mint authority is a public key stored in the token's MintInfo account that has permission to call the MintTo instruction. If it's set, whoever controls that keypair can create new tokens and increase the total supply at any time. If it's been revoked, the supply is permanently fixed. Checking whether mint authority is set or revoked is the first thing to do before buying any Solana token.

What does freeze authority mean and why is it dangerous?

Freeze authority is a public key that can call the FreezeAccount instruction on any token holder's account. A frozen account can't send or receive that token. If a token project retains freeze authority, it can lock your balance without your consent. Most legitimate tokens revoke freeze authority after launch. If it's still set, that's a significant risk signal.

How do I check if mint authority has been revoked?

Fetch the token's mint account using getAccountInfo with base64 encoding. The raw account data is 82 bytes. Bytes 0 through 3 contain the mint_authority_option field: 0 means revoked and 1 means set. If it's 1, bytes 4 through 35 contain the active authority public key. The Python script in this article does this automatically.

What is Metaplex metadata and how does mutable metadata create risk?

Metaplex metadata is stored at a program-derived address associated with each token mint. It contains the token's name, symbol, image URI, and a flag called is_mutable. When that flag is true, the project can change the name, symbol, and image after launch. This is a risk signal because the token's identity can be swapped without your knowledge.

Can I trust a token if mint authority is set to a multisig?

A multisig is safer than a single keypair because minting requires multiple signers to agree. However, it's still a risk signal because the authority is active. Your scanner should flag it as SET and note that the authority is a multisig address, then let the user decide. Fully revoked means no one can mint more, regardless of how many keys are required.

What is update_authority in Metaplex metadata?

The update_authority is the public key that can modify the Metaplex metadata account. If is_mutable is true, whoever controls that key can change the token's name, symbol, and URI. If the update authority points to a live keypair rather than a dead address or a locked program, that's a flag. Projects that want immutable metadata set is_mutable to false after launch.

How do I detect if a creator wallet has rugged before?

Pull the creators array from the Metaplex metadata. For each verified creator, check how many tokens they've launched and whether liquidity was removed from those tokens within a few days of launch. Three or more tokens with early liquidity removal is a strong rug signal. This check requires historical token creation data and wallet transfer history indexed by creator address.

What is the difference between SPL Token and Token-2022 for risk scanning?

SPL Token mint accounts are 82 bytes with a fixed layout. Token-2022 retains that base mint layout and can append extension data after the first 82 bytes. The account's owner identifies which program controls it: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA for SPL Token and TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb for Token-2022. Check the owner and handle any Token-2022 extensions your risk model needs.

How many bytes is a Solana MintInfo account?

A standard SPL Token MintInfo account is 82 bytes. It contains a 4-byte option tag plus 32-byte pubkey for mint authority, an 8-byte u64 for supply, a 1-byte u8 for decimals, a 1-byte boolean for is_initialized, and a 4-byte option tag plus 32-byte pubkey for freeze authority. Token-2022 mints can be larger because they append extension data after the base 82 bytes.

Can freeze authority actually freeze my wallet?

It freezes your token account for that specific mint, not your entire wallet. A frozen token account can't send or receive that token. Your SOL and other tokens are unaffected. But if you hold a token whose freeze authority is active and the project uses it, you can't move your balance until it is unfrozen. For any meaningful position, freeze risk is worth checking.

What is a verified creator in Metaplex metadata?

Each entry in the Metaplex creators array has a verified boolean. If verified is true, that wallet signed to verify its association with the metadata, proving control of the creator address. Unverified creators can be added by the update authority without the creator's knowledge, so they're not reliable for attribution. Only check verified creators when scoring creator reputation.

What Python libraries do I need to build this scanner?

The core scanner needs requests for RPC calls, plus the standard-library base64 and struct modules for decoding account data. For PDA derivation, use either solders or solana-py. The base58 package handles public-key encoding. No heavy dependencies are required for the authority checks.

///

If the scanner surfaces a flagged creator, the next step is historical depth. NLN Token Creations covers every token launch on Solana indexed by the deploying wallet, with initial supply, program attribution, and launch timestamp. NLN Wallet Transfers covers all SOL and SPL transfers so you can measure per-token liquidity outflows by day. Together they let you score any creator's history without scraping block explorers one transaction at a time.

#solana#token-risk#mint-authority#freeze-authority#metaplex#python#rug-detection#on-chain-analysis#spl-token#token-scanner
N
NoLimitNodes Engineering
Infrastructure Team

The team that runs our RPC, WebSocket, gRPC, and streaming fleet. We write about what we operate: validators, Geyser pipelines, and the request paths in between.

On this page
  • 01What the Token Program Exposes
  • 02Fetching Mint Account Data
  • 03Checking Authority Fields
  • 04Verifying Metadata
  • 05Scoring Creator Wallets
  • 06Full Scanner Script
  • 07Frequently Asked Questions
↑ back to top
///Read next
EngineeringJul 24, 2026

Solana Indexer Architecture: Backfill History and Switch to Live gRPC Without Missing Slots

Build a Solana indexer that backfills history and switches to live gRPC without missing slots. Covers the no-gap, no-duplicate, and monotonic invariants with full Python implementation.

#solana#indexer#backfill
11 min read
EngineeringJul 21, 2026

Solana VPS vs VDS vs Bare Metal: What Should Your Trading Bot Run On?

CPU steal, network jitter, and disk I/O spikes kill trading bots silently. Measure all three on your current host and find out which server tier actually fixes them.

#solana#vps#vds
10 min read
← Older
Solana Shredstream Guide: Shreds, Turbine, Forks, and Bots
Run it yourself

Every benchmark in this blog runs against our public endpoints.

Spin up an RPC, WebSocket, or gRPC endpoint in under a minute. Flat pricing, no request caps. Reproduce the numbers for your own workload.

See pricing

Ready to get started?

Choose a plan and start building in under 30 seconds.

Talk to Sales
NoLimitNodes

Solana RPC infrastructure built for performance and scale.

RPC Access
  • HTTP RPC
  • WebSocket
  • gRPC
  • Shredstream
Infrastructure
  • Compute Platform
  • VPS
  • VDS
  • Bare Metal
  • Geyser Plugin Hosting
Enhanced Streams
  • PumpFun
  • PumpSwap
  • Raydium
  • Orca
  • Meteora
  • System Events
  • Browse All →
Program Streams
  • PumpFun
  • PumpSwap
  • Raydium CLMM
  • Orca Whirlpool
  • Meteora DLMM
  • Jupiter Swap
  • Jupiter Perps
  • Kamino Lending
  • Browse All 37 →
Trading
  • EZWallet
Analytics
  • Historical Datasets
  • Historical Raw Blocks
Company
  • About
Resources
  • Pricing
  • Custom Development
  • Documentation
  • Blog
  • System Status
  • Support
  • Contact Sales
Compare
  • Yellowstone gRPC vs LaserStream
  • Triton vs Helius
  • Raydium API vs Helius
  • PumpSwap API vs Bitquery
  • QuickNode Streams vs NLN
  • All comparisons →
Legal
  • Terms & Conditions
  • Privacy Policy
© 2026 CLR3 Inc., operating as NoLimitNodes. Registered in Ontario, Canada. All rights reserved.solana mainnet