Now that we have a mental model of how Solana works, it’s time to actually use it.
We’ve talked about accounts holding state, programs containing the logic, instructions telling those programs what to do, and transactions bringing those instructions together. Creating an SPL Token Mint is a good place to see all of those concepts working together.
In this part, we’ll create and initialize an SPL Token Mint on Solana Devnet, but more importantly, we’ll break down what is actually happening underneath the code.
So, what exactly is a Mint?
If I tell Solana to give someone 100 of a particular token, Solana first needs to know what that token is. What defines it? How divisible is it? How many units currently exist? Who has the authority to create more? That is where the Mint Account comes in.
A Mint Account represents a particular type of token on Solana. It stores information about that token such as its current supply, decimals, mint authority and optional freeze authority. It does not store how many tokens I personally own. That belongs somewhere else, which we’ll get to when we talk about Token Accounts and ATAs.
A simple way to separate the two is this: the Mint tells us what token exists, while a Token Account tells us how much of that token a particular owner holds. Before looking at any code, the complete process for creating our Mint looks like this:
- Connect to Solana Devnet
- Load our wallet
- Generate a new keypair for the Mint
- Calculate how much space a Mint Account needs
- Calculate the lamports required for the account
- Ask the System Program to create the account
- Ask the Token Program to initialize it as a Mint
- Put both instructions inside a transaction
- Sign the transaction
- Send and confirm it on Solana
There are quite a few SDK functions involved when implementing this, but underneath all that syntax, this is really what the entire spl_init.ts file is doing.
Starting with the wallet and Mint address
We first load our wallet and turn it into a signer. The wallet is important because someone has to pay the transaction fee and authorize the operations that require a signature.
const signer = await createKeyPairSignerFromBytes(
new Uint8Array(wallet)
);
The exact SDK function isn't the important part here. What matters is that we now have a signer that represents our wallet and can be used when constructing the transaction.
Next, we generate a new keypair for the Mint.
const mint = await generateKeyPairSigner();
There is an important distinction here. Generating this keypair does not create anything on Solana. At this point, we simply have a new keypair locally and a public address that we intend to use for our Mint. Solana does not have an account at that address yet. We still have to create one.
Creating the account before creating the Mint
This is where Solana's account model becomes important. Programs contain the logic, while accounts hold state. Since a Mint needs to store information like supply, decimals and authorities, that information needs somewhere to live.
Before creating the account, we calculate how much storage a Mint requires and how many lamports are needed for an account of that size.
const mintSpace = getMintSize();
const rent = await rpc
.getMinimumBalanceForRentExemption(BigInt(mintSpace))
.send();
Now we know the amount of space to allocate and how much to fund the account with.
The next instruction asks the System Program to create the account.
const createAccountIx = getCreateAccountInstruction({
payer: signer,
newAccount: mint,
lamports: rent,
space: mintSpace,
programAddress: TOKEN_PROGRAM_ADDRESS,
});
Reading this without focusing too much on the syntax makes it fairly straightforward. Our wallet is paying, mint is the new account we want to create, lamports funds it, space determines how much storage it gets, and TOKEN_PROGRAM_ADDRESS says that the Token Program will own the account.
That last part is important because on Solana, an account's owner is a program, not necessarily the person we might casually describe as owning something. The owner program is the program allowed to modify that account's data according to its rules.
At this stage, however, we have only described how the account should be created. We still haven't told the Token Program that this account should behave as a Mint.
Turning the account into a Mint
This is where the second instruction comes in.
const initializeMintIx = getInitializeMintInstruction({
mint: mint.address,
decimals: 6,
mintAuthority: signer.address,
freezeAuthority: signer.address,
});
The first instruction says, “create this account.” The second says, “initialize this account as a Mint.”
This is also where we configure some of the Mint's properties. With decimals: 6, one whole token can be represented as 1,000,000 base units. We also set our wallet as the mintAuthority, which gives it the authority to create new units of this token.
One distinction worth understanding here is Token Program ownership versus mint authority. The Token Program owns the Mint Account and enforces the rules around how its data can change. Our wallet being the mint authority simply means it has permission, under those rules, to authorize the creation of new token units. They are two completely different responsibilities.
Instructions are not transactions
Another useful distinction is that creating these instruction objects has still not changed anything on-chain.
An instruction is essentially a description of an operation we want a Solana program to perform. At this point, we have prepared two of them: one for the System Program to create the account and another for the Token Program to initialize it.
We then put both into a transaction, and their order matters. The System Program has to create the account before the Token Program can initialize it as a Mint.
Conceptually, the transaction looks like this:
Transaction
Instruction 1
System Program → Create Account
Instruction 2
Token Program → Initialize Mint
The transaction also needs a fee payer and a recent blockhash. The fee payer is the account paying the network fee, while the recent blockhash gives the transaction a validity window and helps prevent old transactions from being replayed indefinitely.
Once everything is assembled, the required signers sign the transaction. Signing is essentially authorization. It proves that the required keys approved the transaction.
Only after we send that signed transaction to Solana does the work we've been preparing actually happen on-chain.
The System Program creates the account first, then the Token Program initializes it as a Mint. Once the transaction is confirmed, we get our Mint address and transaction signature. The Mint address identifies our newly created token Mint, while the transaction signature identifies the transaction and can be used to inspect what happened on-chain.
Have we created any tokens yet?
No, and this is probably the most important distinction in the entire process.
Creating a Mint is not the same thing as minting tokens.
At this point, we have defined a token on Solana. It has an address, decimals and authorities, but its supply is still zero. We haven't created any actual token units or given them to anyone yet.
The way I like to think about the state we're currently in is:
Mint Account
What token exists? → Done
Token Supply
How many tokens have been created? → 0
Token Account
Who holds how many tokens? → Not created yet
So if I had to explain the whole spl_init.ts file in one sentence, it would be this:
We generated an address for our Mint, asked the System Program to create an account there, asked the Token Program to initialize that account as a Mint, then packaged those instructions into a transaction, signed it and sent it to Solana.
We now have the foundation for our token, but an address alone isn't very descriptive. We still need a way to associate information like a name, symbol and URI with it.
That is what we will treat next with metadata.
Top comments (0)