T11 · Token Programs
Why is a Solana token not its own standalone contract?
- Capabilities practiced
- Builder
- Hands on
- Create a Mint on Devnet, create an ATA for each of two wallets, and complete a transfer.
- AI Lab
- Have AI explain Token-2022 extensions, then check which extensions cause common wallets to display incorrectly.
A Real-World Problem
On the EVM, you know the token-launch process: write a contract, deploy it, get an address, and keep user balances in its mapping. Your token is your code.
You try the same thing on Solana and encounter three surprises.
First, you deploy no program. One transaction creates the token and sets its supply, decimals, and mint authority. You wrote zero lines of code.
Second, you try sending 100 tokens to a friend and it fails: the recipient does not yet have an account for this token. You must open one for them first, paying a deposit.
Third, you want custom transfer logic, such as taking 1% from every payment. You cannot find anywhere to write it.
These surprises point to one question: where does the token logic run? If it is not your code, whose is it?
Thought Experiment
Compare two ways to issue tokens.
Approach A: every token opens its own bank.
You build a bank (deploy a contract) and put a ledger inside (a mapping) recording each balance. A thousand tokens means a thousand banks, ledgers, and nearly identical pieces of code.
A wallet supporting a thousand tokens must, in principle, understand a thousand banks. Fortunately their interfaces resemble one another because of the standard in T9. But resemblance is voluntary, which led to the three incidents in T9.
Approach B: the whole world uses one banking program.
No individual banks. Every token uses the same program. A token is just issuance data: supply, decimals, mint authority, and freeze authority. A person’s balance is a separate account belonging to that person, containing the token identity, owner, and amount.
Follow this model to its consequences; it contains the whole chapter:
- A wallet that understands one program understands every token. Display, transfer, and delegation use one interface, with no adapter per token.
- Token authors have nowhere to add custom logic. Transfer code belongs to the shared program. So T9’s fee-on-transfer and self-changing balance behaviors cannot exist in this basic model.
- Balance accounts must be created explicitly. Each is separate, so someone must create it and pay a storage deposit. That explains why the recipient needs an account before a transfer.
- Custom behavior requires extension points in the shared program. This led to a newer token program and its extension system.
Together, points 2 and 4 are the interesting part: Solana’s shared program removes an entire class of T9 problems, while extensions deliberately let some of them back in.
Your Decision
You want to airdrop tokens to 500 new users, most of whom have no account for this token. What do you do?
What Happens
All four designs answer the same question: who pays the storage deposit, and who receives it?
| Design | Who pays? | Who owns the deposit? | Failure risk | Best use |
|---|---|---|---|---|
| Transfer directly | No one | — | High if recipient has no account | Known existing accounts |
| Open for recipient | You | User | Low | Push distribution requiring high conversion |
| User opens own | User | User | Low | Claim-based distribution |
| Idempotent creation in same transaction | Whomever you choose | User | Lowest | Default for most cases |
Remember this separately:
The same storage cost is burned once in Gas by a sender on the EVM, but becomes a refundable deposit owned by an account holder on Solana.
The issue is not simply which chain is cheaper. The cost is placed in a different location and can be used by different people. The “claim, sell, close, and recover deposit” pattern does not exist on the EVM because that money was burned at the start.
For any Solana distribution, put this on the risk list: who ultimately receives the deposit you paid?
Build a Model
Two Account Types
Solana tokens have two kinds of account. Both are ordinary accounts as described in T10, owned by the shared token program.
Mint account (one per token)
├─ supply Current total
├─ decimals Precision
├─ mintAuthority Who can mint; can be set to “none” permanently
└─ freezeAuthority Who can freeze holders' accounts; can be set to “none”
Token account (one per person, per token)
├─ mint Which token this account is for
├─ owner Who owns the account
├─ amount Balance
├─ delegate Who is authorized to spend, and how much
└─ state Normal / frozenThe most important convention is the associated account. A person’s account for a token has an address derived from “holder address + token program + Mint address.” This uses the derived-address mechanism from T10: anyone can calculate where a person’s account for a particular token should be, without querying.
Wallets list your token balances by filtering token accounts whose owner is you.
Compare with ERC20, Line by Line
This table is the chapter’s central product. Return to it more than once:
| Question | ERC20 | Solana token |
|---|---|---|
| Where is the code? | Each token has its own | One program shared across the network |
| How do you issue a token? | Deploy a contract | Submit a transaction creating a Mint account |
| Where are balances? | In the token contract’s mapping | In separate holder accounts |
| Does a recipient need preparation? | No | Yes, an account and deposit |
| Can transfer logic be customized? | Yes, causing the T9 traps | Not in the basic version; limited extensions in the new one |
| Approval model | approve allowance | delegate, with only one per account at a time |
| Who pays storage costs? | Sender burns Gas | Account creator pays a refundable deposit |
| Name, icon, other metadata | In the contract | Provided by another program or extension |
| Minting authority | Logic in contract code | A field on the Mint account that can be relinquished |
| Freezing a holder | Token must implement a blacklist | Native support if freezeAuthority exists |
The last two rows deserve attention: minting and freezing are native program powers recorded as data fields anyone can inspect. On EVM you must read source code to find such back doors. Here, reading the Mint account tells you immediately whether mintAuthority and freezeAuthority are empty.
This is an underappreciated advantage: permissions expressed as data are easier to audit than permissions hidden in code.
The Newer Token Program and Extensions
The shared program gives consistency but also rigidity: no one can add features individually. A newer token program keeps the same model while allowing you to enable selected extensions when creating a Mint.
Common extensions include transfer fees, nontransferable tokens, new accounts frozen by default, metadata stored on the Mint, interest accrual, and hooks that call an external program during transfer.
Pause and notice what happened:
Transfer fees, changing balances, and external code during transfers are precisely the kinds of behavior that broke integrators in T9.
What changed? These behaviors are declared explicitly on the account rather than hidden in code. An integrator can read the enabled extensions before accepting a token and decide whether to support it. A fact that required reading source in T9 becomes a field you can inspect programmatically.
Risk has changed form, not disappeared:
- Wallets and protocols must adapt to each extension. A token using an uncommon one may display incorrectly or not at all.
- Transfer hooks mean a transfer executes external code. Treat that as an untrusted external call, just like the callback token in T9.
- Old integrators do not understand these extensions. Code built on older assumptions can misaccount for fee extensions.
The conclusion: a shared program removes unpredictable behavior, while extensions restore customization in a declared, inspectable form. Inspectable does not mean safe; it makes inspection possible.
What It Is Called
A program shared by all tokens. It defines creation, minting, transfer, delegation, freezing, and burning.
Because tokens share it, wallets and protocols adapt once. Token authors create data rather than deploying code.
The issuance account for a token: supply, decimals, mint authority, and freeze authority.
It is the token’s “address.” When people say “the address of a token,” they mean its Mint account address.
An account for one person’s holdings of one token, recording mint, owner, balance, delegate, and freeze status.
A person holding N tokens has N such accounts, each requiring a rent-exemption deposit.
The token account derived from “holder + token,” with an address anyone can calculate.
It gives a deterministic answer to “where is this person’s account for this token?” without a query or index. It is one of the most successful practical uses of a PDA.
Two fields on a Mint account. They can be surrendered permanently by setting them to “none.”
When researching any Solana token, read these fields first. If mint authority remains, supply can change. If freeze authority remains, the issuer can stop any holder from moving tokens.
An extension of the newer token program that calls a specified external program during transfer.
It restores custom transfer behavior and the risk that “a transfer executes untrusted code.” Integrate it as an external call, not as an ordinary transfer.
Hands-on
Do everything in the command line, without writing code. The goal is to make the account model tangible in your own hands.
Switch to Devnet and request faucet funds.
solana config set --url devnet
solana address
solana airdrop 2
solana balanceStay on Devnet and never use a wallet with real assets. Generate a separate key file for this exercise.
Create a Mint.
spl-token create-token --decimals 6Record the Mint address in the output. What happened here matters: you created an account; you did not deploy any program.
Now inspect it:
solana account <MINT address>
spl-token display <MINT address>Check who currently holds mintAuthority and freezeAuthority: you do.
Create your token account and mint tokens.
spl-token create-account <MINT address>
spl-token mint <MINT address> 1000
spl-token balance <MINT address>When creating the account, note the deposit shown by the command, then compare solana balance before and after. That money has not vanished. It sits in the account you just created.
Generate a second wallet and transfer to it.
solana-keygen new --no-bip39-passphrase -o ./wallet2.json
solana address -k ./wallet2.jsonFirst try a transfer without extra parameters and observe the failure:
spl-token transfer <MINT address> 10 <WALLET2 address>The error says the recipient has no account. That is the second surprise from the opening.
Retry with the option to create the recipient’s account:
spl-token transfer <MINT address> 10 <WALLET2 address> --fund-recipientThis succeeds, and your SOL balance falls again because you paid the recipient’s deposit.
Verify that an associated account address is calculated, not looked up.
spl-token address --token <MINT address> --owner <WALLET2 address> --verbose
spl-token accounts --owner <WALLET2 address>You know their token-account address even though they did nothing. That is the value of a derived address.
Relinquish mint authority, then try minting again.
spl-token authorize <MINT address> mint --disable
spl-token mint <MINT address> 1The second command fails. Run spl-token display again and mintAuthority is empty.
This field is the first thing others will inspect when researching your token. You just made a public and irreversible commitment.
Repeat with the newer token program. Start by inspecting options:
spl-token create-token --helpFind the options for the newer program and for enabling extensions. Create a second token with one extension, such as a transfer fee, complete the same process, then import both tokens into a common wallet and compare how they display.
Wallet support varies considerably. What you see here is exactly what the AI Lab asks you to verify.
AI Lab
Use three steps. The third is the real output of the lab.
Step one:
List the extensions provided by the newer Solana token program. For each, explain what
behavior it changes, when it must be enabled, and whether it can later be disabled.
Mark any extension whose existence you are uncertain about.
Step two:
Which extensions break integrations written for the old assumptions? Explain exactly
which step fails and how the failure appears.
Step three:
Compare these extensions with nonstandard implementations in the ERC20 world.
Which are two forms of the same problem, and which are unique to Solana?Step one will look comprehensive. Search the official documentation for every item. Models are good at enumerating features but may include invented ones. The invented names often sound reasonable because they fit a perceived gap.
Step two is usually better because it requires reasoning rather than exact recall. Check facts requiring precise memory; use models for synthesis and inference. You saw this boundary in the AI Lab of T9. This is a second example.
Step three is for your own understanding. Compare the two systems and you will see different arrangements of the same trade-offs: EVM permits custom behavior by default and converges through ecosystem conventions; Solana begins with uniformity and exposes explicit extensions. Which you prefer depends on your priorities, but integrators on both chains must determine what a token actually does before accepting it.
Then verify practically: create several tokens with different extensions on Devnet, import them into two or three common wallets, and compare screenshots. Claims about current wallet support need direct testing, because that support changes constantly.
Verify these points yourself after using AI
- You found every extension it named in official documentation; models can invent plausible extension names
- For each wallet-support claim, you tested a real wallet on Devnet
- Did it suggest extensions are on by default? They must be selected explicitly when a Mint is created
- You checked every CLI flag it gave against --help for your installed version
- Did it distinguish extensions that change the amount received in a transfer from those that do not?
- Did it compare extensions with nonstandard ERC20 implementations? Ask it to if it did not
Real-World Case
When supporting withdrawals of a Solana token, an exchange called transfer directly without handling recipients who lacked an account for that token.
Many withdrawals failed. Users retried, support was overwhelmed, and some systems even treated failure as success, breaking reconciliation.
Before any Solana transfer out, ask whether the recipient has an account and who creates one if not. EVM transfers do not pose this question.
A token enables a transfer-fee extension. A protocol that records the sent amount receives less than it records.
The fee-on-transfer token in T9 produced exactly the same failure, on a different chain with a different representation.
This time, the protocol could have detected the extension by reading the Mint account before accepting the token. The ability to check existed; someone still had to do it.
Some token issuers retain freeze authority, letting them prevent any holder from transferring at any time.
For a regulated stablecoin, that may be necessary. For a project claiming decentralization, it is a back door many users overlook.
The good news: it is public and quickly inspectable. Read the Mint account and check whether freezeAuthority exists. That belongs in the first step of researching any Solana token.
Once a token account’s balance reaches zero, it can be closed and its deposit refunded to its owner.
In an airdrop where a project pays to open user accounts, scripts systematically exploit this: claim, sell, close, and collect the deposits the project paid. The more people claim, the more SOL the project loses.
The lesson is familiar: who ultimately receives the deposit you paid? Include this in any distribution cost model.
Change One Variable
Supply can change at any time. A valuation, distribution, or vesting promise based on “fixed supply” remains a claim rather than a guarantee.
This is not necessarily bad; protocols may need ongoing issuance for incentives. But it must be disclosed clearly, not left for users to discover by reading an account.
When researching a token, inspect this before any other onchain metric.
The issuer can stop any holder’s balance from moving, including funds deposited into a DeFi protocol.
For integrators, that adds a risk: your liquidation path may be frozen precisely when you need it most. This is the same class of risk as the pausable token in T9, but here it is a field rather than code.
The pool receives less than it records, leaking a little value on every trade.
The solution is the same as in T9: account using the balance difference, not the declared amount. Chain and model changed, but the underlying problem is still “declared amount differs from amount received.”
The account disappears and the deposit is refunded. A later transfer to that user again faces “recipient has no account.”
A system that cached “this user’s token-account address” and assumed it would always exist can fail. Check for account existence on each transfer or use idempotent creation.
Accounts can be created and destroyed. That state does not exist in the EVM mapping mental model.
Takeaway Questions
Why do we need tokens? This chapter offers a structural answer: in this model, a token needs no code of its own. It is data interpreted by a shared program. Many things you assume need a contract may really need only a widely recognized data format.
Who pays? The answer here is concrete: every token account’s rent-exemption deposit has a payer and a beneficiary, who may differ. List this as a separate cost in every distribution, airdrop, or incentive model.
Who bears the risk? Mint authority, freeze authority, and transfer hooks are controlled by issuers while holders bear the consequences. The good news is that all three are data readable in one query in this model. The bad news is that most people never check.
Chapter Check
One program shared by all tokens already implements transfer, minting, delegation, freezing, and burning.
Issuing a new token means creating an account holding issuance data, which the shared program interprets. Since you wrote no code, you cannot insert custom logic into its basic behavior.
The benefit is consistent behavior and one wallet integration; the cost is that new capabilities must be added to the shared program.
A balance lives in an independent account, not a row in a token contract’s ledger. That account occupies onchain storage and needs a deposit or it may be reclaimed.
The deposit belongs to the account owner and can be refunded when the account closes. It is locked collateral, not a fee.
In practice, combine “create if missing” and transfer in one transaction, and decide who funds the deposit.
There is no fundamental difference. An ATA follows an address convention: it is derived from a holder and a token.
Its value is predictability. Anyone can calculate where a person’s account for a token should be without querying. Wallets, protocols, and scripts use it by default.
A person can create more than one account for the same token, but only one is the ATA; they must track the others themselves.
Fully uniform behavior and customizable behavior pull in opposite directions. Systems must choose a point between them.
The shared program initially chooses uniformity, preventing custom token features. Extensions open controlled, explicitly declared opportunities for customization.
The crucial difference: T9’s behaviors can hide in source code, while here they are account fields that software can check. Easier discovery is progress, but support decisions and risk still belong to integrators.
There is no single prescribed answer. Start with:
- Is
mintAuthorityempty? If not, who holds it, and is it a multisig? - Is
freezeAuthorityempty? Can the issuer freeze your pool? - Does it use the old or newer token program, and which extensions are enabled?
- Is there a transfer fee? Does your accounting use declared amounts or balance differences?
- Is there a transfer hook? Who wrote the called program, and what can it do?
- What are its decimals, and does your calculation hard-code them?
- Who provides metadata, and can they change it?
- What fraction do the largest holders own? Is most supply held by one account?
Compare this with the ERC20 checklist at the end of T9. Both ask the same underlying questions. A different chain does not remove the questions.
Key idea
A token is a program shared by everyone, plus a set of accounts owned by individual holders.