Solana’s Token Program, Explained
Breaking down how fungible and non-fungible tokens work in Solana.
Understanding Solana tokens can be quite difficult if you come from an Ethereum background. In Ethereum, fungible tokens use the ERC20 standard, and non-fungible tokens use the ERC721 standard. Each ERC20 token has its own smart contract, and each NFT collection has its own ERC721 (or ERC1155) contract. Pretty simple stuff.
Things work quite differently in Solana land. Let’s get into it.
Solana’s Account Model
In order to understand tokens, you need to understand Solana’s account model. I highly recommend reading this wiki, especially if you come from an Ethereum background. Here’s a brief summary:
- An account either holds data (e.g. how many tokens you have) or is an executable program (i.e. a smart contract). I refer to the former as “data accounts” and the latter as “program accounts.” Importantly, unlike Ethereum, program accounts do not store state. All state is stored in data accounts.
- Each account contains the following fields 👇
- Each account has a unique address (similar to Ethereum). Most addresses are the public key of a keypair.
- Each account is owned by a program. By default, a newly created account is owned by a built-in program called the “System Program.” Only the owner of an account can modify it.
For more details, you can consult the wiki I mentioned above, or read a short primer on accounts that I wrote.
What is Solana’s Token Program?
Solana’s token program enables the following (for fungible and non-fungible tokens):
- Token minting
- Token transferring
- Token burning
Here’s hana on how this works:
one advantage of the program/account model is you can have one generic program that operates on various data. the best example of this is the spl token program. to create a new token, you dont need to deploy code like you do on ethereum. you create an account that can mint tokens, and more accounts that can receive them. the mint address uniquely determines the token type, and these are all passed as arguments to one static program instance
Basically, instead of deploying a new ERC20 smart contract for each new token, all you need to do is send an instruction to the token program. Depending on what instruction you send, the token program will mint/transfer/burn tokens.
If you don’t completely understand yet, don’t worry! Going through an example should make things much clearer, which is what we’ll do next.
Note: sometimes you’ll see Solana tokens referred to as “SPL tokens.” SPL stands for Solana Program Library, which is a set of Solana programs that the Solana team has deployed on-chain. SPL tokens are similar to ERC20 tokens, since every SPL token has a standard set of functionality.
How Does Solana’s Token Program Work?
The easiest way to understand this is by going through a few examples. Our examples will cover fungible tokens, and we’ll use the spl-token
command-line tool to interact with the token program (you can install it by running cargo install spl-token-cli
).
To reiterate, all spl-token
is doing is sending instructions to the token program. You can mimic the following behavior by using the JavaScript client, or by interacting with the token program via CPI in Rust.
Prerequisites
First, make sure you’ve installed the Solana CLI and spl-token
.
Next, run solana-keygen new -o ~/my_solana_wallet1.json
, and create a new environment variable called $SOLADDR1
that stores the resulting public key. Repeat this process, but with a second environment variable called $SOLADDR2
(and name the keypair file my_solana_wallet2.json
). We’ll use these addresses and keypair files later on to test out token minting and transferring.
Here’s what these values look like for me:
$ echo $SOLADDR1
3sdsSwWWjjGA7HpPBQfGaXRE2HqmdKicMXHRapqLAu4L
$ echo $SOLADDR2
ES2C1YPzNh5JjQu7DdxrveaPUHj9CnrRWSdrFo4ku5Zh
Note: many of the commands in this tutorial can be made shorter by running solana config set — keypair ~/my_solana_wallet1.json
, which sets the default client keypair. For example, this lets you leave off the --owner
flag for many of the following commands. I use the longer version of commands to make things clearer and more explicit.
Token Creation
Now that we’re all set up, we can use spl-token
. First, let’s create a new type of token.
$ spl-token create-token --mint-authority ~/my_solana_wallet1.json
Creating token 6ifRGEkJ6XmEjuqfTFNqAjjomUiDJTjRv4GHqBH6usWr
Creating a new type of token creates a new data account, which from now on we’ll refer to as a “mint account.” Each type of token is associated with exactly one mint account. The address of the mint account is 6ifRGEkJ6XmEjuqfTFNqAjjomUiDJTjRv4GHqBH6usWr
, but we’ll use $TOKEN1
instead to make things more readable. We can query for the account’s information as follows (don’t worry about all the gobbledygook at the end):
$ solana account $TOKEN1
Public Key: Aqf1rBKNQYgX1mjE64STwV3miEwXEe2ioZzD7n4vkpXk
Balance: 0.0014616 SOL
Owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Executable: false
Rent Epoch: 207
Length: 82 (0x52) bytes
0000: 01 00 00 00 2a b0 1a 64 bb c5 f0 df bf 57 d5 61 ....*..d.....W.a
0010: 56 a8 b8 85 8f a8 0b 09 f1 f1 a2 dc 4d 51 b3 63 V...........MQ.c
0020: 8f 72 bd e9 00 00 00 00 00 00 00 00 09 01 00 00 .r..............
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050: 00 00 ..
We can check out the owner (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
) using Solana Explorer 👇. The owner is the token program (a program account), which is responsible for creating new tokens, minting tokens, and transferring tokens.
Now let’s look at the mint account’s data, which is the stuff that comes after “Length: 82 (0x52) bytes” as shown above. It’s kind of hard to read plain hex data, so we can instead use the Solana Explorer:
Solana Explorer decodes the data and displays it in a human-readable format. Here’s what each of the fields means:
- Address—I hope this is self-explanatory 😛 This is just the mint account’s address.
- Current Supply—The number of tokens that have been minted. Since we just created the token, it is 0.
- Mint Authority—The public key of the keypair that is allowed to mint tokens (we specified this with the
--mint-authority
flag. If anyone else tries to mint tokens, it will fail. - Decimals—This dictates the smallest denomination of the token. For NFTs, it should be zero. Nine is the default.
Before we move on, here’s a simple diagram showing the accounts that are in play and how they’re related. The legend is stolen from paulx’s great blog:
- “Internal Solana relations” refers to the
owner
field that is set on every account, e.g. the owner that’s displayed when you runsolana account $TOKEN1
. - “User-space relations” refers to when a relation between two accounts is encoded in an account’s data, e.g. the “mint authority” field we saw above.
Token Account Creation
Before we can mint some tokens, we must first create a token account.
$ spl-token create-account $TOKEN1 --owner $SOLADDR1
Creating account 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
From now on, we’ll use $TOKENACCT1
instead of 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
, and we’ll refer to this account as a “token account.” You might be wondering—what’s a token account? Basically, it just stores how many tokens a particular user has, for a particular type of token. For example, if you own 10 of token1 and 5 of token2, you will have two token accounts.
Here’s what the token account looks like.
$ solana account $TOKENACCT1
Public Key: 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
Balance: 0.00203928 SOL
Owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Executable: false
Rent Epoch: 207
The token account has relations with three other accounts. Its internal owner is the token program, since the token program must be able to modify the account (e.g. add more tokens to it). Then, in user-space, its owner is $SOLADDR1
and its “mint,” or associated mint account, is $TOKEN1
.
Here’s how all the accounts are related now:
Token Minting
Finally, we can mint some tokens! This part is simpler—we just specify the mint account (which determines the “type” of token to mint), the amount of tokens to mint, and the account to give them to.
$ spl-token balance $TOKEN1 --owner $SOLADDR1
0$ spl-token mint $TOKEN1 10 $TOKENACCT1 --mint-authority ~/my_solana_wallet1.json
Minting 10 tokens
Token: Aqf1rBKNQYgX1mjE64STwV3miEwXEe2ioZzD7n4vkpXk
Recipient: 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak$ spl-token balance $TOKEN1 --owner $SOLADDR1
10
In addition to using spl-token balance
, you can also use spl-token accounts
to view how many of each token a certain account owns.
$ spl-token accounts --owner $SOLADDR1
Token Balance
---------------------------------------------------------------
Aqf1rBKNQYgX1mjE64STwV3miEwXEe2ioZzD7n4vkpXk 10
What happens if you try to mint tokens with a different value for --mint-authority
?
$ spl-token mint $TOKEN1 10 $TOKENACCT1 --mint-authority ~/my_solana_wallet2.json
Minting 10 tokens
Token: Aqf1rBKNQYgX1mjE64STwV3miEwXEe2ioZzD7n4vkpXk
Recipient: 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x4 [5 log messages]
The answer? It doesn’t work! This is because the “mint authority” field of the token account is set to a different account, and only that account is allowed to mint new tokens.
Token Transferring
Let’s try to transfer some tokens to $SOLADDR2
(which should have been created in the “Prerequisites” section).
$ spl-token transfer $TOKEN1 1 $SOLADDR2 --owner ~/my_solana_wallet1.json
Transfer 5 tokens
Sender: 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
Recipient: ES2C1YPzNh5JjQu7DdxrveaPUHj9CnrRWSdrFo4ku5Zh
Recipient associated token account: FFednTgQRKDbGYjXrXk8SWPfzSJW3Q8ApN5mGCpGdAtE
Error: Recipient's associated token account does not exist. Add `--fund-recipient` to fund their account
Uh oh, it failed 😨 The error message tells us why—we’re not allowed to transfer tokens unless the recipient has a token account.
There are two ways to fix this:
- Create a token account for the recipient and then transfer the tokens.
- Use the
--fund-recipient
flag. This makes the sender pay for creating the recipient’s token account.
We’ll go with approach #1.
$ spl-token create-account $TOKEN1 --owner $SOLADDR2
Creating account FFednTgQRKDbGYjXrXk8SWPfzSJW3Q8ApN5mGCpGdAtE$ spl-token transfer $TOKEN1 1 $SOLADDR2 --owner ~/my_solana_wallet1.json
Transfer 1 tokens
Sender: 9EYnoqiBQmJPR55db44cF4wkN1PD5D6vjxEz61r2Ujak
Recipient: ES2C1YPzNh5JjQu7DdxrveaPUHj9CnrRWSdrFo4ku5Zh
Recipient associated token account: FFednTgQRKDbGYjXrXk8SWPfzSJW3Q8ApN5mGCpGdAtE$ spl-token balance $TOKEN1 --owner $SOLADDR2
1
Nice, it worked! Let’s take a look at all the different accounts in play.
Wrapped SOL
Just like how ETH can be wrapped in an ERC20 token to form wETH, SOL can be wrapped in an SPL token.
$ spl-token wrap 1 ~/my_solana_wallet1.json
Wrapping 1 SOL into DXWzvX3RtZbito65G8ZgqmEzJM6Z6Fqy7NHkbpEmCUZD
Of course, you can unwrap the resulting SPL token to get back your SOL.
$ spl-token unwrap DXWzvX3RtZbito65G8ZgqmEzJM6Z6Fqy7NHkbpEmCUZD ~/my_solana_wallet1.json
Unwrapping DXWzvX3RtZbito65G8ZgqmEzJM6Z6Fqy7NHkbpEmCUZD
Amount: 1 SOL
Recipient: 3sdsSwWWjjGA7HpPBQfGaXRE2HqmdKicMXHRapqLAu4L
Wrapping SOL like this allows SOL to be easily exchanged for other SPL tokens.
Wait, What About NFTS?
Now that we’ve walked through how fungible tokens work, understanding non-fungible tokens should be easy. I’ll just let you read the docs, since I wouldn’t add much anyways. The main important points are:
- Use
--decimals 0
when creating the token, since there should only be one of them. - After minting one token, disable future minting. This ensures there will only ever be one token.
In practice, no one creates NFTs this way. Instead, most people use Candy Machine, which is a tool for uploading images and metadata and minting them as NFTs. I’ll explain how that works in a future post!
That’s it! By now, you should understand how SPL tokens work and how they differ from ERC20 tokens. To recap:
- A single program called the Token Program is able to create new token types, mint new tokens, and transfer tokens between accounts.
- Unlike ERC20 tokens, where each new type of token is associated with a new ERC20 smart contract, there is only one Token Program. However, each new type of token is associated with a new mint account.
- If a user wants to own some tokens of a particular type, they need to have a token account. In other words, if someone owns five different types of tokens, they will have five different token accounts.
- You can wrap SOL in an SPL token, which allows it to be easily exchanged for other SPL tokens.
- A non-fungible SPL token is a token with zero decimals that has minted one copy and disabled future minting.
If you want to see more of this kind of content, feel free to follow me on Twitter!