Summary
- The
Token Extensions Programhas all of the same functions as theToken Program, with addedextensions - These two token programs:
Token ProgramandToken Extensions Programuse separate addresses and are not directly compatible - Supporting both requires specifying the correct program ID in client-side functions
- By default, the SPL program library uses the original
Token Programunless another is specified - The
Token Extensions Programmay also be referred to as it’s technical spec nameToken22
Overview
TheToken Extensions Program enhances the original Token Program by
incorporating additional features known as extensions. These extensions are
designed to address specific scenarios that previously necessitated developers
to fork and alter the Nexis Native Chain Program Library, leading to split ecosystems and
challenges in adoption. The introduction of the Token Extensions Program allows
for these scenarios to be effectively handled.
Since the Token Program and Token Extensions Program are different onchain
programs, they are not interoperable. For example, a token minted with
Token Extensions Program may not be transferred with the Token Program. As a
result, we’ll have to support both programs in any client-side applications that
need to display or otherwise support all SPL tokens. This means we’ll want to
explicitly handle mints from both the original Token Program (address:
TOKEN_PROGRAM_ID) and the Extension Program (address:
TOKEN_2022_PROGRAM_ID).
Fortunately, the interfaces for the two programs remain consistent, allowing the
use of spl-token helper functions in either program by simply swapping the
program ID (the function uses the original Token Program by default if no
program ID is provided). Most of the time, end users are not concerned with the
specific token program being used. As such, implementing additional logic to
track, assemble, and merge details from both token varieties is essential to
guarantee a smooth user experience.
Lastly, “Token 22” is often used as the technical name. If you see someone refer
to the Token 22 Program, they are referring to the Token Extensions Program.
Differences between working with Token Program Tokens and Token Extensions Tokens
When interacting with mints and tokens, we need to be sure we’re using the correct Token Program. To create aToken Program mint, use Token Program; to
create a mint with extensions, use the Token Extensions Program.
Fortunately, the spl-token package makes it simple to do this. It provides
both the TOKEN_PROGRAM_ID and TOKEN_2022_PROGRAM_ID constants, along with
all of its helper functions for creating and minting tokens that take a program
ID as input.
spl-token defaults to using the TOKEN_PROGRAM_ID unless
otherwise specified. Make sure to explicitly pass the TOKEN_2022_PROGRAM_ID
for all function calls related to the Token Extensions Program. Otherwise, you
will get the following error: TokenInvalidAccountOwnerError. Considerations when working with both Token and Extension Tokens
Although the interfaces for both of these programs remain consistent, they are two different programs. The program IDs for these programs are unique and non-interchangeable, resulting in distinct addresses when utilized. If you want to support bothToken Program tokens and Token Extensions Program tokens,
you must add extra logic on the client side.
Associated Token Accounts (ATA)
An Associated Token Account (ATA) is a Token Account whose address is derived using the wallet’s public key, the token’s mint, and the token program. This mechanism provides a deterministic Token Account address for each mint per user. The ATA account is usually the default account for most holders. Fortunately, ATAs are handled the same way with both token programs. We can use the ATA helper functions for each token program by providing the desired program ID. If we want to use the Token Extensions Program when we callgetOrCreateAssociatedTokenAccount Extension Tokens, we can pass in
TOKEN_2022_PROGRAM_ID for the tokenProgramId parameter:
findProgramAddressSync function by providing the correct seeds:
How to fetch tokens
There is no difference between tokens made with Token Program or Token Extensions Program. That means, when it comes to fetching tokens, there is no difference between how we fetch Token Program or Token Extensions Program tokens. All we have to do is provide the correct token program:getTokenAccountsByOwner, and then call it twice, once with
TOKEN_PROGRAM_ID and another with TOKEN_2022_PROGRAM_ID.
It’s likely you’ll want to store and associate the token
program with the token upon fetching.
Check owning program
You may run into a scenario where you don’t know the token program for a given account. Fortunately,getParsedAccountInfo will allow us to determine the
owning program:
After you fetch the owning program, it may be a good idea
to save that owner and associate it with the mints/tokens you are
handling.
Lab - Add Extension Token support to a script
Let’s work through a holistic example where we add Token Extensions support to an existing script. This lab will lead us through the necessary adjustments and expansions to embrace the capabilities and nuances of both the original Token Program and its extension counterpart. By the end of this lab, we’ll have navigated the complexities of supporting these two distinct but related token systems, ensuring our script can interact smoothly with both.1. Clone the starter code
To get started, clone this lab’s repository and checkout thestarter branch. This branch contains a couple of helper files
and some boilerplate code to get you started.
npm install to install the dependencies.
2. Get familiar with the starter code
The starter code comes with the following files:print-helpers.tsindex.ts
print-helpers.ts file contains a function called printTableData,
which is designed to output data to the console in a structured format. This
function is capable of accepting any object as its argument, and it utilizes the
console.table method, a feature provided by NodeJS, to display the data in
an easily readable table format.
Lastly, index.ts contains our main script. It currently only creates a
connection and calls initializeKeypair to generate the keypair for payer.
3. Run validator node (Optional)
Optionally, you may want to run your own local validator instead of using devnet. This a good way around any issues with airdropping. In a separate terminal, run the following command:solana-test-validator. This
will run the node and also log out some keys and values. The value we need to
retrieve and use in our connection is the JSON RPC URL, which in this case is
http://127.0.0.1:8899. We then use that in the connection to specify to use
the local RPC URL.
Connection and the keypair path input to
initializeKeypair.
npm run start. You should see
the payer public key logged out in your terminal.
4. Create Token Program and Token Extensions Program mints
Let’s start by creating new token mints using both theToken Program and the
Token Extensions Program.
Create a new file called create-and-mint-token.ts. In this file we will create
a function called createAndMintToken. As the name suggests it will create a
mint, token account (ATA) and then mint some amount of tokens to that account.
Within this createAndMintToken function we will be call createMint,
getOrCreateAssociatedTokenAccount and mintTo. This function is designed to
be indifferent to the specific token program being used, allowing for the
creation of tokens from either the Token Program or the
Token Extensions Program. This capability is achieved by accepting a program
ID as a parameter, enabling the function to adapt its behavior based on the
provided ID.
Here are the arguments we’ll be passing into this function:
connection- The connection object to usetokenProgramId- The token program to point topayer- The keypair paying for the transactiondecimals- The number of decimals to include for the mintmintAmount- The amount of tokens to mint to the payer
- Create a new mint using
createMint - Fetch mint information using
getMint - Log mint information using
printTableData - Create an associated token account with
getOrCreateAssociatedTokenAccount - Log the address of the associated token account
- Mint tokens to the associated token account with
mintTo
createAndMintToken function looks
like:
5. Creating and minting tokens
Let’s now take our new function and invoke it twice within our main script inindex.ts. We’ll want a Token Program and Token Extensions Program token to
test against. So we’ll use our two different program IDs:
npm run start and see that both mints get created
and their info logged to the console.
6. Fetch Token Program and Token Extensions Program tokens
We can now fetch tokens using the wallet’s public key and the program ID. Let’s create a new filefetch-token-info.ts.
Within that new file, let’s create the fetchTokenInfo function. This function
will fetch the token account provided and return a new interface we’ll create
named TokenInfoForDisplay. This will allow us to format the returning data
nicely in our console. Again, this function will be agnostic about which token
program the account it from.
getTokenAccountsByOwner and mapping the results into our new
TokenInfoForDisplay interface.
To accomplish this the fetchTokenInfo function will need the following
parameters:
connection- The connection object to useowner- The wallet which owns the associated token accountsprogramId- The token program to point totype- EitherToken ProgramorToken Extensions Program; used for console logging purpose
index.ts, let’s add two separate
calls to this function, once for each program.
npm run start. You should now see all of the tokens the payer wallet owns.
7. Fetch Token Program and Token Extensions Program tokens without the program ID
Now let’s take a look at how we can retrieve the owning program from a given mint account. To do this we will create a new functionfetchTokenProgramFromAccount to
fetch-token-info.ts. This function will simply return us the programId of
the given mint.
To accomplish this we will call the getParsedAccountInfo function and return
the owning program from .value.owner.
The fetchTokenProgramFromAccount function will need the following parameters:
connection- The connection object to usemint- Public key of the mint account
index.ts:
npm run start again. You should see the same output as before - meaning
the expected token programs were correct.
That’s it! If you get stuck at any step, you can find the complete code in
this lab’s repository’s
solution branch.