Written by
0xSolanaGirl
Published on
June 1, 2024
Copy link

Plug and Play Token Extensions

Introduction

Token Extensions make it easy to create programmable tokens on Solana. Developers can combine over a dozen Mint and Account Extensions using the new Token Extensions program. By completing the tutorial, you will create and interact with fungible and non-fungible tokens, interest-bearing tokens, frozen tokens, tokens you have absolute authority over (including moving them out of accounts that don’t belong to you), and closing mint accounts to recover funds. 

Plug and Play Token Extensions is a tutorial with interactive Solana Playground examples to help you learn Token Extensions using TypeScript to create the following tokens:

The code used for this tutorial is adapted from the Solana Developer Guide. Scripts to create Token Extensions can be found in the Additional Resources section. 

Token Extensions Account

Introducing new instructions, Token Extensions are built on top of the Token Program. Accounts created with both programs are identical for an initial length until the Token program instructions end and Token Extensions instructions begin. Those unfamiliar with account data can read about account space in an Introduction to Anchor. In short, accounts on Solana must be created with space explicitly allocated for data to be stored on-chain. With the space, we can calculate the amount of lamports needed to make the account rent exempt.

This is what it looks like to create a mint account with extensions. To allocate space for the Token Extensions, use the function getMintLen to get the size of the mint account with extensions. In the NonFungibleToken.ts example, get the mint length of the MetadataPointer and MintCloseAuthority Extension types.

NonFungibleToken.ts line 54:


  // Size of Mint Account with extension
  const mintLen = getMintLen([
    ExtensionType.MetadataPointer,
    ExtensionType.MintCloseAuthority,
  ]);

The Metadata and Metadata Pointer extensions allow you to store and point to metadata without relying on third-party protocols. TokenMetadata is an interface from @solana/spl-token-metadata with fields such as updateAuthority, mint, name, symbol, uri, and additionalMetadata. The metadata length is found using the pack function on the metadata object. 

NonFungibleToken.ts line 60:


  // Metadata to store in Mint Account
  const metaData: TokenMetadata = {
    updateAuthority: updateAuthority,
    mint: mint,
    name: "Solana Soldier",
    symbol: "SOLD",
    uri: "https://shdw-drive.genesysgo.net/G1Tzt42SDqCV3x9vPY5X826foA8fEk8BR4bB5wARh75d/OIG2.jpg",
    additionalMetadata: [["armor", null]],
  };
  // Size of metadata
  const metadataLen = pack(metaData).length;
  // Size of MetadataExtension 2 bytes for type, 2 bytes for length
  const metadataExtension = TYPE_SIZE + LENGTH_SIZE;

Add the mint length, metadata extension, and metadata length to find the minimum lamports required for the Mint Account.

NonFungibleToken.ts line 75:


  // Minimum lamports required for Mint Account
  const lamports = await connection.getMinimumBalanceForRentExemption(
    mintLen + metadataExtension + metadataLen
  );

Token Extensions Instructions

Solana libraries like @solana/spl-token, and @solana/spl-token-metadata add TypeScript support for the Token Extensions program. Begin by creating an account with the right amount of space and rent-exempt lamports using SystemProgram from @solana/web3.js.

NonFungibleToken.ts line 80:


  // Instruction to invoke System Program to create new account
  const createAccountInstruction = SystemProgram.createAccount({
    fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
    newAccountPubkey: mint, // Address of the account to create
    space: mintLen, // Amount of bytes to allocate to the created account
    lamports, // Amount of lamports transferred to created account
    programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
  });

Next, we can create instructions to specify the mint. The following instructions must be executed before calling initializeMint. The pre-mint Instructions used in Plug and Play are:

Metadata accounts can be initialized and updated independently of initializeMint. However, the Metadata Pointer must be initialized before mint. The order of instructions in the transaction decides the order they are executed. Create a mint account, initialize metadata pointer, and then mint close authority with the public key of the mint. Then, initialize the mint and the metadata accounts and update additional metadata.

NonFungibleToken.ts line 135:


  transaction = new Transaction().add(
    createAccountInstruction,
    initializeMetadataPointerInstruction,
    initializeMintCloseAuthorityInstruction,
    initializeMintInstruction,
    initializeMetadataInstruction,
    updateFieldInstruction
  );

The files nonFungibleToken.ts, soulboundToken.ts, interestBearingToken.ts, and delegateToken.ts follow the same structure for creating tokens, with the difference being the extensions and instructions used. 

The process begins with importing all the necessary functions from the requisite Solana libraries, defining a payer, and generating a mint address. Then, find the space and lamports needed for the mint address with the desired token extensions. Create instructions using the @solana/spl-token and @solana/spl-token-metadata libraries, add them to a transaction, send the transaction to the network, and validate the results. 

Token operations like transfer and burn can be used on different token extensions. Tokens with the Metadata Extension can use the createUpdateFieldInstruction and createRemoveKeyInstruction instructions to update or remove metadata, respectively. 

Plug and Play 

In Solana Playground, the client contains the necessary functions to interact with Token Extensions. For example, the Soulbound token uses the Non-Transferable and Metadata Extensions to create a soulbound NFT. Functions to create and interact with the tokens are available in the same file and can be imported (in the case of Solana Playground, you must copy-paste the code instead of importing it). 

The quest.ts file allows you to play the Solana Soldier. Alternatively, run each file containing token extensions to see their specific functionality. The Playground Wallet needs Devnet SOL to run the quest. The official Solana Faucet is also available to airdrop Devnet SOL.

The quest starts with creating a Solana Soldier, an NFT where the payer (pg.wallet.keypair) will have the mint close authority – allowing the payer to close and reclaim SOL from the mint account. This mechanic is useful for applications to return SOL from no longer active accounts.

Solana Soldier NFT for Token Extensions Tutorial

After creating a Solana Soldier, use their mint address to update the NFT’s metadata. Let’s update the soldier’s armor to iron. The getMetadata, updateMetadata, and removeMetadata functions can be found in the NonFungibleToken.ts file starting from line 167. The functions can be called for mint accounts using the Metadata Extension. The metadata functions will only be included in the NonFungibleToken.ts file for simplicity.

Next, it’s time to start leveling up. An interest-bearing token will represent the level of the Solana Soldier. From InterestBearingToken.ts, import createInterestBearingToken and use the instruction to create the token. The interest on the account will grow according to the rate set in InterestBearingToken.ts on line 43. The amountToUiAmount function can be used to determine the amount of tokens with accrued interest. The interest accumulates over time, and we can call the amountToUiAmount to check on the Solana Soldier’s level.

Once the soldier starts to level, they need to receive a mission. The mission is represented by a soulbound token – The soldier cannot transfer it (try to catch the error by calling the transfer function), and the only way to get rid of the mission is to burn it. Instead of abandoning the mission, let’s check it out instead. Hint, check the metadata!

Solana Soldier's Soulbound Mission

Once you’ve read the mission, the Plug and Play tutorial will deploy an army to a token address. The Solana Soldier must be able to summon the army back to the payer after the tokens are deployed. Token Extensions make this possible by introducing the delegate authority. The delegate authority can transfer and burn tokens regardless of who owns the tokens. Set the permanent delegate to a keypair you have access to, this keypair needs to sign for burning and transferring tokens.

Delegate tokens will represent the Solana Army. Create delegate tokens with createDelegateTokens from DelegateToken.ts line 33 to the enemy’s token account. When the tokens are minted, the Solana Army and the enemy engage in a hypothetical battle. After the battle, recall the soldiers from the enemy’s account using the transferDelegateTokens function found in DelegateTokens.ts line 176.

Solana Army Delegated Tokens Withdrawl

Notice that not all soldiers were recalled from the enemy’s address. Let’s assume they’ve perished. Use the burn function from @solana/spl-token to burn the tokens left over in the enemy’s token account (sourceTokenAccount). 

With the success of the battle, Solana Soldier’s quest comes to an end. The closeAccount function from @solana/spl-token was used to close the account and return rent. Token Extensions not discussed in this article include confidential transfers, transfer fees, and transfer hooks. Almost all token extensions can be mixed and matched, with a few exceptions like transfer hooks and non-transferable tokens. The reference Solana Playgrounds for the Token Extensions covered in this tutorial are linked in Additional Resources. 

Conclusion

Inspired by CryptoZombies, this Plug and Play tutorial covered the basics of creating popular Token Extensions with a combination of extensions. This tutorial shows the composability of Token Extensions through a hands-on gaming experience. The examples can be Plug and Play or a guide for developers writing their own tokens. From sizing up the accounts to ordering instructions, the examples provide a streamlined process for creating accounts with Token Extensions.  If you found the gamified learning experience to be educational, please share feedback in our Discord or tag @heliuslabs on X.

Additional Resources