diff --git a/content/courses/tokens-and-nfts/token-program-advanced.md b/content/courses/tokens-and-nfts/token-program-advanced.md index b979dd803..aea2a8649 100644 --- a/content/courses/tokens-and-nfts/token-program-advanced.md +++ b/content/courses/tokens-and-nfts/token-program-advanced.md @@ -1,5 +1,5 @@ --- -title: Token Burning and Delegation +title: Token burning and Delegation objectives: - Understand why and how to burn tokens - Allow a token holder to allocate a limited amount of tokens to another @@ -8,34 +8,20 @@ description: "How to burn tokens, and approve/revoke token delegations on Solana." --- -### 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 +Finally, we'll cover burning tokens, and delegation. You may not use these in +your own application, so if you're really excited about NFTs, feel free to skip +to [creating NFTs with Metaplex](/content/courses/tokens-and-nfts/nfts-with-metaplex.md)! -#### Burn Tokens +### 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()`](https://solana-labs.github.io/solana-program-library/token/js/functions/burn.html#burn) -function. +To burn tokens using the `spl-token` library, use the `burn` function. ```typescript import { burn } from "@solana/spl-token"; @@ -52,19 +38,17 @@ const transactionSignature = await burn( ); ``` -The `burn()` function requires the following arguments: +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. +- `connection` - the JSON-RPC connection to the cluster +- `payer` - the account of the payer for the transaction +- `account` - the token account to burn tokens from +- `mint` - the token mint associated with the token account +- `owner` - the account of the owner of the token account +- `amount` - the amount of tokens to burn -Under the hood, the `burn()` function creates a transaction using the -instruction obtained from -[`createBurnInstruction()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createBurnInstruction.html#createBurnInstruction) -function. +Under the hood, the `burn` function creates a transaction with instructions +obtained from the `createBurnInstruction` function: ```typescript import { PublicKey, Transaction } from "@solana/web3.js"; @@ -84,16 +68,16 @@ async function buildBurnTransaction( } ``` -#### Approve Delegate +### 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. +or burn tokens from a token account. When using a delegate, the authority over +the token account remains with the original owner. The maximum amount of tokens +a delegate may transfer or burn is specified at the time the owner of the token +account approves the delegate. Note that there can only be one delegate account +associated with a token account at any given time. -To approve a delegate using the `spl-token` library, use the -[`approve()`](https://solana-labs.github.io/solana-program-library/token/js/functions/approve.html#approve) +To approve a delegate using the `spl-token` library, you use the `approve` function. ```typescript @@ -107,20 +91,18 @@ const transactionSignature = await approve( ); ``` -The `approve()` function returns a `TransactionSignature` that can be viewed on -Solana Explorer. It requires the following arguments: +The `approve` function returns a `TransactionSignature` that can be viewed on +Solana Explorer. The `approve` function 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. +- `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 the owner is authorizing to transfer or burn tokens +- `owner` - the account of the owner of the token account +- `amount` - the maximum number of tokens the delegate may transfer or burn -Under the hood, the `approve()` function creates a transaction with instructions -obtained from the -[`createApproveInstruction()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createApproveInstruction.html#createApproveInstruction) -function. +Under the hood, the `approve` function creates a transaction with instructions +obtained from the `createApproveInstruction` function: ```typescript import { PublicKey, Transaction } from "@solana/web3.js"; @@ -131,7 +113,7 @@ async function buildApproveTransaction( delegate: PublicKey, owner: PublicKey, amount: number, -): Promise { +): Promise { const transaction = new Transaction().add( createApproveInstruction(account, delegate, owner, amount), ); @@ -140,15 +122,14 @@ async function buildApproveTransaction( } ``` -#### Revoke Delegate +### 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. +A previously approved delegate for a token account can be later revoked. Once a +delegate is revoked, the delegate can no longer transfer tokens from the owner's +token account. Any remaining amount left untransferred from the previously +approved amount can no longer be transferred by the delegate. -To revoke a delegate using the `spl-token` library, use the -[`revoke()`](https://solana-labs.github.io/solana-program-library/token/js/functions/revoke.html#revoke) +To revoke a delegate using the `spl-token` library, you use the `revoke` function. ```typescript @@ -157,27 +138,25 @@ 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: +The `revoke` function returns a `TransactionSignature` that can be viewed on +Solana Explorer. The `revoke` 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. +- `connection` - the JSON-RPC connection to the cluster +- `payer` - the account of the payer for the transaction +- `account` - the token account to revoke the delegate authority from +- `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()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createRevokeInstruction.html#createRevokeInstruction) -function: +Under the hood, the `revoke` function creates a transaction with instructions +obtained from the `createRevokeInstruction` function: ```typescript import { PublicKey, Transaction } from "@solana/web3.js"; -import { createRevokeInstruction } from "@solana/spl-token"; +import { revoke } from "@solana/spl-token"; async function buildRevokeTransaction( account: PublicKey, owner: PublicKey, -): Promise { +): Promise { const transaction = new Transaction().add( createRevokeInstruction(account, owner), ); @@ -188,259 +167,232 @@ async function buildRevokeTransaction( ### Lab -This lab extends the concepts covered in the previous lesson on the -[Token Program](/content/courses/tokens-and-nfts/token-program.md). +This lab extends the lab from the +[previous chapter](/content/courses/tokens-and-nfts/token-program.md). -#### 1. Delegating Tokens +#### 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. +Let's use `approve` from `spl-token` to authorize a delegate to transfer or burn +up to 50 tokens from our token account. -Similar to the process of -[Transferring Tokens](/content/courses/tokens-and-nfts/token-program.md#transferring-tokens) -in the previous lab, you can -[add a second account on Devnet](/content/courses/intro-to-solana/intro-to-cryptography.md) -if desired or collaborate with a friend who has a Devnet account. +Just like +[Transferring Tokens](/content/courses/tokens-and-nfts/token-program.md) in the +previous lab, you can +[add a second account on devnet](/content/courses/intro-to-solana/intro-to-cryptography.md) +if you like, or find 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. +Create a new file `delegate-tokens.ts`. We use the system program account as the +delegate here for demonstration. -```typescript filename="delegate-tokens.ts" +```typescript import "dotenv/config"; import { getExplorerLink, getKeypairFromEnvironment, } from "@solana-developers/helpers"; +import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js"; 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); + approve, + getOrCreateAssociatedTokenAccount, + revoke, +} from "@solana/spl-token"; + +const connection = new Connection(clusterApiUrl("devnet")); + const user = getKeypairFromEnvironment("SECRET_KEY"); -console.log(`πŸ”‘ Loaded keypair. Public key: ${user.publicKey.toBase58()}`); +console.log( + `πŸ”‘ Loaded our keypair securely, using an env file! Our public key is: ${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); +// Use the system program public key +const delegate = new PublicKey("11111111111111111111111111111111"); -// Substitute your token mint address -const tokenMintAddress = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); +// Substitute in your token mint account +const tokenMintAccount = 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, - ); +// Get or create the source and destination token accounts to store this token +const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAccount, + user.publicKey, +); - // Approve the delegate - const approveTransactionSignature = await approve( - connection, - user, - userTokenAccount.address, - delegatePublicKey, - user.publicKey, - DELEGATE_AMOUNT * MINOR_UNITS_PER_MAJOR_UNITS, - ); +// Our token has two decimal places +const MINOR_UNITS_PER_MAJOR_UNITS = Math.pow(10, 2); - const explorerLink = getExplorerLink( +const approveTransactionSignature = await approve( + connection, + user, + sourceTokenAccount.address, + delegate, + user.publicKey, + 50 * MINOR_UNITS_PER_MAJOR_UNITS, +); + +console.log( + `βœ… Approve Delegate Transaction: ${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](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). +Replace `YOUR_TOKEN_MINT_ADDRESS_HERE` with your mint token address obtained +from the previous chapter. Run the script using `npx esrun delegate-tokens.ts`. You should see: ```bash -πŸ”‘ Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM -βœ… Delegate approved. Transaction: https://explorer.solana.com/tx/21tX6L7zk5tkHeoD7V1JYYW25VAWRfQrJPnxDcMXw94yuFbHxX4UZEgS6k6co9dBWe7PqFoMoWEVfbVA92Dk4xsQ?cluster=devnet +βœ… Approve Delegate Transaction: https://explorer.solana.com/tx/3sBr62x2VMaoJ4Z3SQMy6ZPzQKaa5Bs9ni9dgwwZZ5qEViKh1gQznCgH489h6pgfruMmqPbc2GgminTPK4UXRRZd?cluster=devnet ``` Open the Explorer link, you will see the β€Œapproval information. -![Delegate Tokens](/public/assets/courses/unboxed/delegate-token.png) - #### 2. Revoke Delegate -Let's revoke the `delegate` using the `spl-token` library's `revoke()` function. +Lets 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 +Revoke will set delegate for the associated token account to null and reset the delegated amount to 0. Create a new file `revoke-approve-tokens.ts`. -```typescript filename="revoke-approve-tokens.ts" +```typescript 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"; +import { getOrCreateAssociatedTokenAccount, revoke } 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(clusterApiUrl("devnet")); -const connection = new Connection(DEVNET_URL); const user = getKeypairFromEnvironment("SECRET_KEY"); -console.log(`πŸ”‘ Loaded keypair. Public key: ${user.publicKey.toBase58()}`); +console.log( + `πŸ”‘ Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, +); -try { - const tokenMintAddress = new PublicKey(TOKEN_MINT_ADDRESS); +// Substitute in your token mint account +const tokenMintAccount = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); - const userTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - user, - tokenMintAddress, - user.publicKey, - ); +// Get or create the source and destination token accounts to store this token +const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAccount, + user.publicKey, +); - const revokeTransactionSignature = await revoke( - connection, - user, - userTokenAccount.address, - user.publicKey, - ); +const revokeTransactionSignature = await revoke( + connection, + user, + sourceTokenAccount.address, + user.publicKey, +); - const explorerLink = getExplorerLink( +console.log( + `βœ… Revoke Delegate Transaction: ${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](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). +from the previous chapter. Run the script using `npx esrun revoke-approve-tokens.ts`. You should see: ```bash -πŸ”‘ Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM -βœ… Revoke Delegate Transaction: https://explorer.solana.com/tx/YTc2Vd41SiGiHf3iEPkBH3y164fMbV2TSH2hbe7WypT6K6Q2b3f31ryFWhypmBK2tXmvGYjXeYbuwxHeJvnZZX8?cluster=devnet +βœ… Revoke Delegate Transaction: https://explorer.solana.com/tx/5UboxLULHT3pPznBxThfQMc73NNjYNLmvqrB3JVVXPwWxUFWA49WG58sFQP8B5rv4FXxxZm3mur319YNiyYxYgBd?cluster=devnet ``` Open the Explorer link, you will see the revoke information. -![Revoke Approve Tokens](/public/assets/courses/unboxed/revoke-approve-tokens.png) - #### 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. +Use the `spl-token` library's `burn` function to remove half of your tokens from +circulation. Create a new file `burn-tokens.ts`. -```typescript filename="burn-tokens.ts" +```typescript 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"; +import { + getOrCreateAssociatedTokenAccount, + Account, + 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(clusterApiUrl("devnet")); -const connection = new Connection(DEVNET_URL); const user = getKeypairFromEnvironment("SECRET_KEY"); -console.log(`πŸ”‘ Loaded keypair. Public key: ${user.publicKey.toBase58()}`); +console.log( + `πŸ”‘ Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, +); -try { - const tokenMintAccount = new PublicKey(TOKEN_MINT_ADDRESS); +// Substitute in your token mint account +const tokenMintAccount = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); - const userTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - user, - tokenMintAccount, - user.publicKey, - ); +// Get or create the source and destination token accounts to store this token +const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAccount, + user.publicKey, +); - const burnAmount = BURN_AMOUNT * 10 ** TOKEN_DECIMALS; +// Our token has two decimal places +const MINOR_UNITS_PER_MAJOR_UNITS = Math.pow(10, 2); - const transactionSignature = await burn( - connection, - user, - userTokenAccount.address, - tokenMintAccount, - user, - burnAmount, - ); +const transactionSignature = await burn( + connection, + user, + sourceTokenAccount.address, + tokenMintAccount, + user, + 25 * MINOR_UNITS_PER_MAJOR_UNITS, +); - const explorerLink = getExplorerLink( +console.log( + `βœ… Burn Transaction: ${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](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). +from the previous chapter. Run the script using `npx esrun burn-tokens.ts`. You should see: ```bash -πŸ”‘ Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM -βœ… Burn Transaction: https://explorer.solana.com/tx/5Ufipgvsi5aLzzcr8QQ7mLXHyCwBDqsPxGTPinvFpjSiARnEDgFiPbD2ZiaDkkmwKDMoQ94bf5uqF2M7wjFWcKuv?cluster=devnet +βœ… Burn Transaction: https://explorer.solana.com/tx/P9JAK7bSAhycccGunDEThgt12QFiqMr9oexenEmRXXKoXsLKr2x64k9BWNppjTxFeVMUYjBEncRKe3gZsyd29JY?cluster=devnet ``` Open the Explorer link, you will see the burn information. -![Burn Tokens](/public/assets/courses/unboxed/burn-tokens.png) +Well done! You've now -Well done! You've now completed the lab. + - +### Completed the lab? Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=72cab3b8-984b-4b09-a341-86800167cfc7)!