惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog
博客园 - Franky
IT之家
IT之家
V
Visual Studio Blog
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
爱范儿
爱范儿
Hugging Face - Blog
Hugging Face - Blog
宝玉的分享
宝玉的分享
博客园 - 叶小钗
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
罗磊的独立博客
小众软件
小众软件
Jina AI
Jina AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Three Token-2022 Mints in One Week: Fees, Yield, and Soul...
Gopichand · 2026-06-12 · via DEV Community

Gopichand

If you have built middleware in Web2, you already understand Token-2022 extensions.
The old SPL token program is like a plain Express router. Token-2022 is the same
router with a plugin system baked in. You opt a single mint into behaviors — fees,
interest, transfer locks — at creation time, and the protocol enforces them forever.
No forking, no custom Rust, no deploying your own program. Just flags.

I spent Days 50–54 of my #100DaysOfSolana challenge shipping three mints that each
demonstrate one of those behaviors. Here is what I built, the exact commands I ran,
and when you would actually reach for each extension.


Mint 1 — Transfer Fee (Days 50 & 51)

Mint: HxDYFvcXnLuy4VdxXCooUXrch8DZW34oUteQ6N2EFxEr
Explorer: View on Devnet
Extension: TransferFeeConfig

spl-token create-token \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --decimals 6 \
  --transfer-fee-basis-points 100 \
  --transfer-fee-maximum-fee 1000000

--transfer-fee-basis-points 100 means 1% of every transfer is withheld before
the recipient gets credited. The withheld amount sits in the recipient's token
account until a privileged instruction sweeps it to the treasury.

When would you use this?
Creator royalties on a community token. A protocol fee on a stablecoin. A DAO
treasury skim that funds development every time the token changes hands. The key
insight: the fee is enforced by the Token-2022 program itself, not by your API.
Nobody can route around it by calling the program directly.

On Day 51 I ran the full lifecycle — transfer, inspect the withheld amount, then
sweep it back:

spl-token transfer $MINT 1000 $RECIPIENT --expected-fee 10
spl-token withdraw-withheld-tokens $MY_TA $RECIPIENT_TA

The --expected-fee 10 flag is a safety assertion. If the mint's fee math
doesn't produce exactly 10 tokens withheld, the instruction aborts. It is the
on-chain equivalent of an idempotency check.


Mint 2 — Transfer Fee + Interest Stacked (Day 52)

Mint: A6TAeNgxBVwYna8NqQVmBpQzjVYKoZA3e68yMvoVVUva
Explorer: View on Devnet
Extensions: TransferFeeConfig + InterestBearingConfig

spl-token create-token \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --decimals 6 \
  --transfer-fee-basis-points 100 \
  --transfer-fee-maximum-fee 1000000 \
  --interest-rate 5000

One command, two TLV entries, two completely different mechanics.

What the interest extension does — and does NOT do:

This is the subtlety most tutorials skip. The InterestBearingConfig extension
does not mint new tokens. The raw amount stored on-chain never changes between
transactions. What changes is the UI amount — the number your wallet displays.

The formula is: UI amount = raw_amount × e^(rate × time_elapsed)

The network clock and the rate stored on the mint are all the CLI needs to compute
a growing display number every time you query. I proved this by reading the balance
twice with a 30-second sleep between them, with zero transactions in between:

spl-token accounts $MINT --verbose | awk 'NR==3'
sleep 30
spl-token accounts $MINT --verbose | awk 'NR==3'
# Output:
# 999032.271358
# 999032.762062   ← +0.49 tokens, no transaction fired

Think of it like a savings account display ticker. The number on screen grows.
The ledger entry does not change until a real transaction touches it.

Two extensions, zero conflict: TransferFeeConfig operates on raw amounts at
transfer time. InterestBearingConfig operates on the display layer at query time.
They are orthogonal, which is why they compose cleanly on a single mint.


Mint 3 — Non-Transferable / Soul-Bound (Day 54)

Mint: BQzJeZVZgkSvAYPj9f1M1apv56P7kggAgPNc9uSq7T5m
Explorer: View on Devnet
Extension: Non-transferable

spl-token create-token --program-2022 --enable-non-transferable

I minted one badge token to myself, then deliberately tried to send it to a throwaway wallet. Here is the exact runtime rejection:
Program log: Instruction: TransferChecked
Program log: Transfer is disabled for this mint
Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x25

Error 0x25 is decimal 37, the NonTransferable entry in the Token-2022 error
enum. The rejection came from the validator, not from the CLI or the RPC layer.
There is no way to call the program "around" the extension. The rule lives on the
asset.

spl-token display $MINT
# Extensions
#   Non-transferable

When would you use this?
Completion certificates. Event attendance proofs. KYC credentials tied to a
specific wallet. Anything where the point is that the credential belongs to the
holder and cannot be resold or delegated.

In Web2 you would enforce this with a database constraint or an API check. The
risk is that anyone who can reach the database directly can break the rule. On
Solana the rule is in the program. The program is in the validator. There is no
around.


What surprised me

I expected the stacking to be complicated. It wasn't. Two flags at creation time,
two TLV entries in the same byte buffer, and the CLI just prints both in the
Extensions block of spl-token display. The Token-2022 design is genuinely
composable in a way that feels like it was designed by someone who got burned by
non-composable systems before.

The interest extension surprised me most. I came in expecting it to mint tokens.
It doesn't. It's a view function disguised as a balance. Once I understood that,
I stopped thinking of it as a financial primitive and started thinking of it as
a display configuration. That reframe changed how I'd use it in a real product —
probably for a points or loyalty system where the raw backing supply is fixed but
the displayed "value" grows over time.

If I were building a real product today, I'd reach for TransferFeeConfig for any
token that needs sustainable protocol revenue, and NonTransferable for any
credential or badge that should be identity-bound. The interest extension I'd hold
for a specific display-layer use case where the raw supply needs to stay auditable
but the user-facing number should grow.

All code and terminal output is in my public build log:
👉 https://github.com/gopichandchalla16/100-days-of-solana

Day 55 of #100DaysOfSolana