DEV Community

ohmygod
ohmygod

Posted on

Solana's Permanent Delegate Burn Scam: How Token-2022 Extensions Power 2026's Largest Automated Rug Pull Factory — And a Detection Pipeline to Stop It

In March 2026, Solana's network degradation isn't coming from a protocol bug — it's coming from an industrial-scale scam token factory exploiting Token-2022's Permanent Delegate extension to burn victims' tokens seconds after purchase. RugCheck.xyz flags over 40% of new Solana tokens as using this extension. Here's how the attack works at the bytecode level, and a complete detection pipeline you can deploy today.

The Permanent Delegate Attack Flow

Token-2022 (SPL Token 2022) introduced token extensions — powerful primitives for compliance, privacy, and programmability. The PermanentDelegate extension grants a designated authority unconditional power to transfer or burn any holder's tokens without their signature.

The intended use case: regulatory compliance (freezing sanctioned addresses). The actual use in 2026: automated theft.

Attack Sequence (Step by Step)

1. Attacker deploys token via Token-2022 with PermanentDelegate = deployer wallet
2. Creates liquidity pool on Raydium/Orca with SOL pairing
3. Generates fake volume via wash trading bots (10-50 coordinated wallets)
4. Victims buy on DEX aggregators — token appears in their wallet
5. Within 1-60 seconds: Permanent Delegate calls Burn on victim's token account
6. Victim's balance → 0, but they already paid SOL
7. Attacker drains LP (sells remaining tokens for SOL)
8. Repeat with new mint address — entire cycle takes < 5 minutes
Enter fullscreen mode Exit fullscreen mode

The On-Chain Signature

Here's what the Permanent Delegate burn instruction looks like in Anchor/Rust:

use anchor_lang::prelude::*;
use anchor_spl::token_2022::{self, Token2022, BurnChecked};

// This is what the ATTACKER's program calls
pub fn burn_victim_tokens(ctx: Context<BurnVictim>, amount: u64, decimals: u8) -> Result<()> {
    // No victim signature required — Permanent Delegate has unconditional authority
    let cpi_accounts = BurnChecked {
        mint: ctx.accounts.mint.to_account_info(),
        from: ctx.accounts.victim_token_account.to_account_info(),
        authority: ctx.accounts.permanent_delegate.to_account_info(),  // attacker's key
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
    token_2022::burn_checked(cpi_ctx, amount, decimals)?;
    Ok(())
}

#[derive(Accounts)]
pub struct BurnVictim<'info> {
    pub mint: AccountInfo<'info>,
    /// CHECK: Victim's token account — no signature needed
    #[account(mut)]
    pub victim_token_account: AccountInfo<'info>,
    pub permanent_delegate: Signer<'info>,  // only the delegate signs
    pub token_program: Program<'info, Token2022>,
}
Enter fullscreen mode Exit fullscreen mode

The critical detail: no victim signature appears anywhere. The Permanent Delegate is the sole signer.

Why Existing Tools Miss It

Standard token safety checkers look for:

  • ❌ Mint authority (can mint new tokens) — Permanent Delegate doesn't need to mint
  • ❌ Freeze authority (can freeze accounts) — Permanent Delegate burns, not freezes
  • ❌ LP lock status — attacker can lock LP and still profit via the burn

The Permanent Delegate extension is distinct from mint/freeze authorities. A token can have mint authority revoked, freeze authority revoked, LP locked — and still rug via Permanent Delegate burn.

Detection Pipeline: 4 Layers

Layer 1: On-Chain Extension Scanner (TypeScript)

Query the mint account directly and decode Token-2022 extensions:

import { Connection, PublicKey } from '@solana/web3.js';
import { 
  getMint, getExtensionData, ExtensionType,
  TOKEN_2022_PROGRAM_ID 
} from '@solana/spl-token';

interface TokenRiskReport {
  mint: string;
  hasPermanentDelegate: boolean;
  delegateAddress: string | null;
  delegateIsDeployer: boolean;
  hasTransferFee: boolean;
  transferFeeBps: number;
  hasNonTransferableFlag: boolean;
  riskScore: number;  // 0-100
}

async function scanTokenExtensions(
  connection: Connection,
  mintAddress: PublicKey
): Promise<TokenRiskReport> {
  const mintInfo = await getMint(
    connection, mintAddress, 'confirmed', TOKEN_2022_PROGRAM_ID
  );

  let report: TokenRiskReport = {
    mint: mintAddress.toBase58(),
    hasPermanentDelegate: false,
    delegateAddress: null,
    delegateIsDeployer: false,
    hasTransferFee: false,
    transferFeeBps: 0,
    hasNonTransferableFlag: false,
    riskScore: 0,
  };

  // Check Permanent Delegate extension
  try {
    const delegateData = getExtensionData(
      ExtensionType.PermanentDelegate, 
      mintInfo.tlvData
    );
    if (delegateData) {
      report.hasPermanentDelegate = true;
      report.delegateAddress = new PublicKey(delegateData.slice(0, 32)).toBase58();
      report.riskScore += 60;  // Highest single risk factor

      // Check if delegate == mint authority (deployer pattern)
      if (mintInfo.mintAuthority && 
          report.delegateAddress === mintInfo.mintAuthority.toBase58()) {
        report.delegateIsDeployer = true;
        report.riskScore += 20;
      }
    }
  } catch {}

  // Check Transfer Fee extension (hidden fee = potential drain)
  try {
    const feeData = getExtensionData(
      ExtensionType.TransferFeeConfig, 
      mintInfo.tlvData
    );
    if (feeData) {
      report.hasTransferFee = true;
      const feeBps = feeData.readUInt16LE(0);
      report.transferFeeBps = feeBps;
      if (feeBps > 500) report.riskScore += 15;  // >5% fee is suspicious
    }
  } catch {}

  return report;
}
Enter fullscreen mode Exit fullscreen mode

Layer 2: Deployer Wallet Profiling (Python)

Scam factories reuse deployer wallets. Profile them:

import asyncio
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient

async def profile_deployer(rpc_url: str, deployer: str) -> dict:
    """Score deployer wallet for scam factory patterns."""
    client = AsyncClient(rpc_url)
    deployer_key = Pubkey.from_string(deployer)

    # Get all token mints created by this wallet
    sigs = await client.get_signatures_for_address(deployer_key, limit=200)

    token_mints = []
    burn_txs = 0
    lp_creates = 0

    for sig_info in sigs.value:
        tx = await client.get_transaction(
            sig_info.signature, max_supported_transaction_version=0
        )
        if not tx.value:
            continue

        logs = tx.value.transaction.meta.log_messages or []
        for log in logs:
            if "InitializeMint2" in log:
                token_mints.append(sig_info.signature)
            if "Burn" in log and "PermanentDelegate" in str(logs):
                burn_txs += 1
            if "InitializePool" in log or "initialize" in log.lower():
                lp_creates += 1

    risk_indicators = {
        "deployer": deployer,
        "tokens_created": len(token_mints),
        "burn_via_delegate": burn_txs,
        "lp_pools_created": lp_creates,
        "is_factory": len(token_mints) > 5 and burn_txs > 0,
        "factory_score": min(100, len(token_mints) * 8 + burn_txs * 15),
    }

    await client.close()
    return risk_indicators
Enter fullscreen mode Exit fullscreen mode

Layer 3: Real-Time Burn Monitor (TypeScript + WebSocket)

Catch Permanent Delegate burns as they happen:

import { Connection, PublicKey, LogsFilter } from '@solana/web3.js';

function monitorDelegateBurns(rpcWsUrl: string) {
  const connection = new Connection(rpcWsUrl, 'confirmed');

  // Subscribe to Token-2022 program logs
  const TOKEN_2022 = new PublicKey(
    'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
  );

  connection.onLogs(TOKEN_2022, (logs) => {
    const logStr = logs.logs.join('\n');

    // Detect Burn instruction where signer != token owner
    if (logStr.includes('Instruction: BurnChecked') || 
        logStr.includes('Instruction: Burn')) {

      // Parse accounts from the instruction
      // Account[0] = token account (victim)
      // Account[1] = mint
      // Account[2] = authority (should be owner, but delegate if scam)

      console.log(`⚠️ DELEGATE BURN DETECTED`);
      console.log(`  TX: ${logs.signature}`);
      console.log(`  Slot: ${logs.context?.slot}`);

      // Alert pipeline: webhook, Telegram bot, or Forta agent
      alertWebhook({
        type: 'permanent_delegate_burn',
        signature: logs.signature,
        timestamp: Date.now(),
        logs: logs.logs,
      });
    }
  }, 'confirmed');

  console.log('🔍 Monitoring Token-2022 burns via Permanent Delegate...');
}

async function alertWebhook(data: any) {
  await fetch(process.env.ALERT_WEBHOOK!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
}
Enter fullscreen mode Exit fullscreen mode

Layer 4: Pre-Trade Safety Gate (Anchor Program)

Enforce on-chain — reject swaps involving tokens with Permanent Delegate:

use anchor_lang::prelude::*;
use spl_token_2022::extension::{BaseStateWithExtensions, StateWithExtensions};
use spl_token_2022::state::Mint as Mint2022;

/// Verifies a Token-2022 mint has no dangerous extensions before allowing a swap
pub fn verify_safe_token(mint_info: &AccountInfo) -> Result<()> {
    let mint_data = mint_info.try_borrow_data()?;

    // Parse as Token-2022 mint with extensions
    let mint_state = StateWithExtensions::<Mint2022>::unpack(&mint_data)
        .map_err(|_| error!(ErrorCode::InvalidMint))?;

    // CRITICAL: Reject tokens with Permanent Delegate
    if mint_state.get_extension::<spl_token_2022::extension::permanent_delegate::PermanentDelegate>().is_ok() {
        return Err(error!(ErrorCode::DangerousPermanentDelegate));
    }

    // Also flag: TransferFee > 10% (hidden fee drain)
    if let Ok(fee_config) = mint_state
        .get_extension::<spl_token_2022::extension::transfer_fee::TransferFeeConfig>() 
    {
        let fee_bps: u16 = fee_config.newer_transfer_fee.transfer_fee_basis_points.into();
        if fee_bps > 1000 {
            return Err(error!(ErrorCode::ExcessiveTransferFee));
        }
    }

    Ok(())
}

#[error_code]
pub enum ErrorCode {
    #[msg("Token has Permanent Delegate extension — potential burn rug")]
    DangerousPermanentDelegate,
    #[msg("Token has >10% transfer fee — potential hidden drain")]
    ExcessiveTransferFee,
    #[msg("Invalid mint account")]
    InvalidMint,
}
Enter fullscreen mode Exit fullscreen mode

CI/CD Integration: GitHub Actions

Add this to your Solana project's CI pipeline:

name: Token Extension Security Scan
on:
  pull_request:
    paths: ['programs/**', 'tests/**']

jobs:
  extension-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check for Permanent Delegate usage
        run: |
          echo "🔍 Scanning for Permanent Delegate patterns..."
          # Flag any code that initializes PermanentDelegate extension
          if grep -rn "PermanentDelegate\|permanent_delegate" programs/; then
            echo "⚠️ WARNING: PermanentDelegate extension detected"
            echo "Review required: ensure delegate authority has proper governance"
            # Don't auto-fail — legitimate compliance use exists
            echo "::warning::PermanentDelegate extension found — manual review required"
          fi

          # Flag unconditional burn patterns (no multisig/timelock)
          if grep -rn "burn_checked\|BurnChecked" programs/ | grep -v "timelock\|governance\|multisig"; then
            echo "⚠️ Burn instruction without governance guard detected"
          fi

      - name: Run Anchor tests with extension checks
        run: |
          anchor test 2>&1 | tee test-output.log
          # Verify tests cover extension validation
          if ! grep -q "DangerousPermanentDelegate\|verify_safe_token" test-output.log; then
            echo "::warning::No extension safety tests detected in test suite"
          fi
Enter fullscreen mode Exit fullscreen mode

10-Point Token-2022 Extension Security Checklist

Before interacting with any Token-2022 token:

# Check Tool
1 Permanent Delegate exists? If yes → who controls it? spl-token display <mint>
2 Delegate == deployer wallet? (rug factory pattern) On-chain scanner
3 Transfer Fee > 5%? (hidden drain) Extension decoder
4 Transfer Hook points to unverified program? getExtensionData()
5 Deployer created >5 tokens in past 7 days? (factory) Wallet profiler
6 Any Permanent Delegate burns in token history? Transaction scanner
7 LP locked? (not sufficient alone — check #1 first) LP lock checker
8 Mint authority revoked? (still rugable via delegate) Mint account data
9 Non-Transferable flag set? (honeypot variant) Extension decoder
10 Token age < 24h + high volume? (pump phase) DEX analytics

Critical insight: Checks 7 and 8 alone are not sufficient. A token can have LP locked and mint authority revoked while still being a rug via Permanent Delegate burn. Always start with check #1.

The Broader Pattern: Extension Abuse Beyond Permanent Delegate

Token-2022's extension model creates a new attack surface taxonomy:

  • Permanent Delegate → unauthorized burn/transfer (this article)
  • Transfer Fee → hidden tax up to 100% on every transfer
  • Transfer Hook → arbitrary program execution on every transfer (can revert sells = honeypot)
  • Non-Transferable → tokens that can never be sold (lock-in scam)
  • Confidential Transfer → obscured amounts that hide whale dumps

Each extension was designed for legitimate use. Each has been weaponized. The security community needs extension-aware tooling as a first-class primitive, not an afterthought.

Conclusion

The Permanent Delegate burn scam isn't a bug — it's a feature being weaponized at industrial scale. In Q1 2026 alone, conservative estimates put losses from Token-2022 extension abuse at $50M+ across thousands of individual victims.

The defense isn't complicated: scan extensions before you swap. The four-layer pipeline above — on-chain scanner, deployer profiler, real-time monitor, and on-chain safety gate — covers detection from pre-trade to post-trade.

Every Solana DEX aggregator, wallet, and trading bot should be checking Token-2022 extensions by default. Until they do, the factory keeps running.


This is part of the DeFi Security Research series. Code examples are educational — audit thoroughly before production use.

Top comments (0)