Summary #
- Burning tokens reduces the total supply of a token by removing them from circulation.
- Approving a delegate, allows another account to transfer or burn a specified amount of tokens from a token account while retaining original account ownership.
- Revoking a delegate, removes their authority to act on behalf of the token account owner.
- Each of these operations is facilitated through the
spl-token
library, utilizing specific functions for each action.
Lesson #
In this lesson, we'll cover burning tokens and delegation. You may not have a need for these in your own application, so if you're more interested in NFTs, feel free to skip ahead to creating NFTs with Metaplex!
Burn Tokens #
Burning tokens is the process of decreasing the token supply of a given token mint. Burning tokens removes the tokens from the given token account and from broader circulation.
To burn tokens using the spl-token
library, use the
burn()
function.
import { burn } from "@solana/spl-token";
const transactionSignature = await burn(
connection,
payer,
account,
mint,
owner,
amount,
);
The burn()
function requires the following arguments:
connection
: JSON-RPC connection to the cluster.payer
: The account responsible for paying transaction fees.account
: The token account from which tokens will be burned.mint
: The token mint associated with the token account.owner
: The owner of the token account.amount
: The number of tokens to burn.
Under the hood, the burn()
function creates a transaction using the
instruction obtained from
createBurnInstruction()
function.
import { PublicKey, Transaction } from "@solana/web3.js";
import { createBurnInstruction } from "@solana/spl-token";
async function buildBurnTransaction(
account: PublicKey,
mint: PublicKey,
owner: PublicKey,
amount: number,
): Promise<Transaction> {
const transaction = new Transaction().add(
createBurnInstruction(account, mint, owner, amount),
);
return transaction;
}
Approve Delegate #
Approving a delegate is the process of authorizing another account to transfer or burn tokens from a token account. The authority over the token account remains with the original owner. The maximum number of tokens a delegate can transfer or burn is defined when the owner approves the delegate. Only one delegate can be associated with a token account at a time.
To approve a delegate using the spl-token
library, use the
approve()
function.
const transactionSignature = await approve(
connection,
payer,
account,
delegate,
owner,
amount,
);
The approve()
function returns a TransactionSignature
that can be viewed on
Solana Explorer. It requires the following arguments:
connection
: The JSON-RPC connection to the cluster.payer
: The account of the payer for the transaction.account
: The token account to delegate tokens from.delegate
: The account authorized to transfer or burn tokens.owner
: The account of the owner of the token account.amount
: The maximum number of tokens the delegate can transfer or burn.
Under the hood, the approve()
function creates a transaction with instructions
obtained from the
createApproveInstruction()
function.
import { PublicKey, Transaction } from "@solana/web3.js";
import { createApproveInstruction } from "@solana/spl-token";
async function buildApproveTransaction(
account: PublicKey,
delegate: PublicKey,
owner: PublicKey,
amount: number,
): Promise<web3.Transaction> {
const transaction = new Transaction().add(
createApproveInstruction(account, delegate, owner, amount),
);
return transaction;
}
Revoke Delegate #
A previously approved delegate for a token account can be revoked. Once revoked, the delegate can no longer transfer tokens from the owner's token account. Any untransferred amount from the previously approved tokens will no longer be accessible by the delegate.
To revoke a delegate using the spl-token
library, use the
revoke()
function.
import { revoke } from "@solana/spl-token";
const transactionSignature = await revoke(connection, payer, account, owner);
The revoke()
function returns a TransactionSignature
that can be viewed on
Solana Explorer. This function requires the following arguments:
connection
: The JSON-RPC connection to the cluster.payer
: The account responsible for paying the transaction fees.account
: The token account from which to revoke the delegate authority.owner
: The account of the owner of the token account.
Under the hood, the revoke()
function generates a transaction using the
instructions from the
createRevokeInstruction()
function:
import { PublicKey, Transaction } from "@solana/web3.js";
import { createRevokeInstruction } from "@solana/spl-token";
async function buildRevokeTransaction(
account: PublicKey,
owner: PublicKey,
): Promise<web3.Transaction> {
const transaction = new Transaction().add(
createRevokeInstruction(account, owner),
);
return transaction;
}
Lab #
This lab extends the concepts covered in the previous lesson on the Token Program.
1. Delegating Tokens #
We will use the approve()
function from the spl-token
library to authorize a
delegate to transfer or burn up to 50 tokens from our token account.
Similar to the process of Transferring Tokens in the previous lab, you can add a second account on Devnet if desired or collaborate with a friend who has a Devnet account.
Create a new file named delegate-tokens.ts
. For this example, we are using the
System Program ID as a delegate for demonstration, but you can use an actual
address that you want to delegate.
import "dotenv/config";
import {
getExplorerLink,
getKeypairFromEnvironment,
} from "@solana-developers/helpers";
import {
Connection,
PublicKey,
clusterApiUrl,
SystemProgram,
} from "@solana/web3.js";
import { approve, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
const DEVNET_URL = clusterApiUrl("devnet");
const TOKEN_DECIMALS = 2;
const DELEGATE_AMOUNT = 50;
const MINOR_UNITS_PER_MAJOR_UNITS = 10 ** TOKEN_DECIMALS;
// Initialize connection and load user keypair
const connection = new Connection(DEVNET_URL);
const user = getKeypairFromEnvironment("SECRET_KEY");
console.log(`π Loaded keypair. Public key: ${user.publicKey.toBase58()}`);
// Replace this with your actual address
// For this example, we will be using System Program's ID as a delegate
const delegatePublicKey = new PublicKey(SystemProgram.programId);
// Substitute your token mint address
const tokenMintAddress = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE");
try {
// Get or create the user's token account
const userTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
user,
tokenMintAddress,
user.publicKey,
);
// Approve the delegate
const approveTransactionSignature = await approve(
connection,
user,
userTokenAccount.address,
delegatePublicKey,
user.publicKey,
DELEGATE_AMOUNT * MINOR_UNITS_PER_MAJOR_UNITS,
);
const explorerLink = getExplorerLink(
"transaction",
approveTransactionSignature,
"devnet",
);
console.log(`β
Delegate approved. Transaction: ${explorerLink}`);
} catch (error) {
console.error(
`Error: ${error instanceof Error ? error.message : String(error)}`,
);
}
Replace YOUR_TOKEN_MINT_ADDRESS_HERE
with your token mint address obtained
from the previous lesson
Token Program.
Run the script using npx esrun delegate-tokens.ts
. You should see:
π Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM
β
Delegate approved. Transaction: https://explorer.solana.com/tx/21tX6L7zk5tkHeoD7V1JYYW25VAWRfQrJPnxDcMXw94yuFbHxX4UZEgS6k6co9dBWe7PqFoMoWEVfbVA92Dk4xsQ?cluster=devnet
Open the Explorer link, you will see the βapproval information.
Delegate Tokens
2. Revoke Delegate #
Let's revoke the delegate
using the spl-token
library's revoke()
function.
Revoke will set the delegate for the token account to null and reset the delegated amount to 0.
Create a new file revoke-approve-tokens.ts
.
import "dotenv/config";
import {
getExplorerLink,
getKeypairFromEnvironment,
} from "@solana-developers/helpers";
import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js";
import { revoke, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
const DEVNET_URL = clusterApiUrl("devnet");
// Substitute your token mint address
const TOKEN_MINT_ADDRESS = "YOUR_TOKEN_MINT_ADDRESS_HERE";
const connection = new Connection(DEVNET_URL);
const user = getKeypairFromEnvironment("SECRET_KEY");
console.log(`π Loaded keypair. Public key: ${user.publicKey.toBase58()}`);
try {
const tokenMintAddress = new PublicKey(TOKEN_MINT_ADDRESS);
const userTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
user,
tokenMintAddress,
user.publicKey,
);
const revokeTransactionSignature = await revoke(
connection,
user,
userTokenAccount.address,
user.publicKey,
);
const explorerLink = getExplorerLink(
"transaction",
revokeTransactionSignature,
"devnet",
);
console.log(`β
Revoke Delegate Transaction: ${explorerLink}`);
} catch (error) {
console.error(
`Error: ${error instanceof Error ? error.message : String(error)}`,
);
}
Replace YOUR_TOKEN_MINT_ADDRESS_HERE
with your mint token address obtained
from the previous lesson
Token Program.
Run the script using npx esrun revoke-approve-tokens.ts
. You should see:
π Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM
β
Revoke Delegate Transaction: https://explorer.solana.com/tx/YTc2Vd41SiGiHf3iEPkBH3y164fMbV2TSH2hbe7WypT6K6Q2b3f31ryFWhypmBK2tXmvGYjXeYbuwxHeJvnZZX8?cluster=devnet
Open the Explorer link, you will see the revoke information.
Revoke Approve Tokens
3. Burn Tokens #
Finally, let's remove some tokens from circulation by burning them.
Use the spl-token
library's burn()
function to remove half of your tokens
from circulation. Now, call this function to burn 5 of the user's tokens.
Create a new file burn-tokens.ts
.
import "dotenv/config";
import {
getExplorerLink,
getKeypairFromEnvironment,
} from "@solana-developers/helpers";
import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js";
import { getOrCreateAssociatedTokenAccount, burn } from "@solana/spl-token";
const DEVNET_URL = clusterApiUrl("devnet");
const TOKEN_DECIMALS = 2;
const BURN_AMOUNT = 5;
// Substitute your token mint address
const TOKEN_MINT_ADDRESS = "YOUR_TOKEN_MINT_ADDRESS_HERE";
const connection = new Connection(DEVNET_URL);
const user = getKeypairFromEnvironment("SECRET_KEY");
console.log(`π Loaded keypair. Public key: ${user.publicKey.toBase58()}`);
try {
const tokenMintAccount = new PublicKey(TOKEN_MINT_ADDRESS);
const userTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
user,
tokenMintAccount,
user.publicKey,
);
const burnAmount = BURN_AMOUNT * 10 ** TOKEN_DECIMALS;
const transactionSignature = await burn(
connection,
user,
userTokenAccount.address,
tokenMintAccount,
user,
burnAmount,
);
const explorerLink = getExplorerLink(
"transaction",
transactionSignature,
"devnet",
);
console.log(`β
Burn Transaction: ${explorerLink}`);
} catch (error) {
console.error(
`Error: ${error instanceof Error ? error.message : String(error)}`,
);
}
Replace YOUR_TOKEN_MINT_ADDRESS_HERE
with your mint token address obtained
from the previous chapter
Token Program.
Run the script using npx esrun burn-tokens.ts
. You should see:
π Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM
β
Burn Transaction: https://explorer.solana.com/tx/5Ufipgvsi5aLzzcr8QQ7mLXHyCwBDqsPxGTPinvFpjSiARnEDgFiPbD2ZiaDkkmwKDMoQ94bf5uqF2M7wjFWcKuv?cluster=devnet
Open the Explorer link, you will see the burn information.
Burn Tokens
Well done! You've now completed the lab.
Push your code to GitHub and tell us what you thought of this lesson!