From 64cf47dd1579984e4f2603ad24bd465581d71654 Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Thu, 17 Aug 2023 12:00:46 +0000 Subject: [PATCH 1/8] docs(guides): add guide for immutable owner extension --- content/guides/token-2022/immutable-owner.md | 183 +++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 content/guides/token-2022/immutable-owner.md diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md new file mode 100644 index 000000000..d813ea35d --- /dev/null +++ b/content/guides/token-2022/immutable-owner.md @@ -0,0 +1,183 @@ +# Immutable Owner: Token 2022 Account Extension + +With the Token program, the `SetAuthority` instruction can be used for various use cases. Among them, an Account's owner may transfer ownership of an account to another. + +The immutable owner extension ensures that ownership of a token account cannot be reassigned. + +## Understanding the implications + +So, why is this important? The addresses for Associated Token Accounts are derived based on the owner and the mint. This makes it easy to find the related token account for a specific owner. If the account owner has reassigned ownership of this account, then applications may derive the address for that account and use it, not knowing that it no longer belongs to the owner. + +This guide walks you through how to use the Immutable Owner extension to prevent the transfer of ownership of a token account. + +Let's get started! + +## Install dependencies + +```shell +npm i @solana/web3.js @solana/spl-token +``` + +Install the `@solana/web3.js` and `@solana/spl-token` packages. + +## Setting up + +We'll begin by setting up our script to create a new token account. + +First, we will need to: + +- Establish a connection to the devnet cluster +- Generate a payer account and fund it +- Create a new token mint using the Token 2022 program + +```javascript +import { + clusterApiUrl, + sendAndConfirmTransaction, + Connection, + Keypair, + SystemProgram, + Transaction, + LAMPORTS_PER_SOL, +} from '@solana/web3.js'; +import { + createAccount, + createMint, + createInitializeImmutableOwnerInstruction, + createInitializeAccountInstruction, + getAccountLen, + ExtensionType, + TOKEN_2022_PROGRAM_ID, +} from '@solana/spl-token'; + +// We establish a connection to the cluster +const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); + +// Next, we create and fund the payer account +const payer = Keypair.generate(); +const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL); +await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) }); + +// Next, we create a new keypair that will be the mint authority +const mintAuthority = Keypair.generate(); +const decimals = 9; + +// Next, we create a new token mint +const mint = await createMint( + connection, // connection + payer, // fee payer + mintAuthority.publicKey, // mint authority + mintAuthority.publicKey, // freeze authority + decimals, // decimals + undefined, // keypair + undefined, // confirm options + TOKEN_2022_PROGRAM_ID // Token Program ID +); +``` + +> The [Mint](https://spl.solana.com/token#creating-a-new-token-type) is used to create or "mint" new tokens that are stored in token accounts. + +Lets explore two options of using the extension: + +1. Creating an account with immutable ownership +2. Creating an associated token account with immutable ownership + +## Option 1: Creating an account with immutable ownership + +### Account setup + +```javascript +const ownerKeypair = Keypair.generate(); +const accountKeypair = Keypair.generate(); + +// Explanation of the two keypairs: +// ownerKeypair: The owner of the token account +// accountKeypair: The address of our token account + +const account = accountKeypair.publicKey; + +const accountLen = getAccountLen([ExtensionType.ImmutableOwner]); +const lamports = await connection.getMinimumBalanceForRentExemption(accountLen); +``` + +Next, we get the size of our new account and calculate the amount for rent exemption. We use the helper `getAccountLen` helper function, which takes an array of extensions we want for this account. + +> The total size of the token account is 165 bytes (the size of a token account) + the size of the added extensions + +### The Instructions + +```javascript +const createAccountInstruction = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: account, + space: accountLen, + lamports, + programId: TOKEN_2022_PROGRAM_ID, +}); +``` + +We create a new account and assign ownership to the token 2022 program. + +```javascript +const initializeImmutableOwnerInstruction = createInitializeImmutableOwnerInstruction( + account, + TOKEN_2022_PROGRAM_ID +); +``` + +We then initialize the Immutable Owner extension for the given token account. It's important to note that this can only be done for accounts that have not been initialized yet. + +```javascript +const initializeAccountInstruction = createInitializeAccountInstruction( + account, + mint, + ownerKeypair.publicKey, + TOKEN_2022_PROGRAM_ID +); +``` + +Next, we initialize our newly created account to hold tokens. + +### Send and confirm + +```javascript +const transaction = new Transaction().add( + createAccountInstruction, + initializeImmutableOwnerInstruction, + initializeAccountInstruction +); +await sendAndConfirmTransaction(connection, transaction, [payer, accountKeypair], undefined); +``` + +Finally, we add the instructions to our transaction and send it to the network. As a result, we've created a token account for our new mint with the immutable owner extension applied. + +If we attempt to change the owner of this account, we get an error: + +```shell +"Program log: Instruction: SetAuthority", +"Program log: The owner authority cannot be changed" +``` + +## Option 2: Creating an associated token account with immutable ownership + +By default, all associated token accounts have the immutable owner extension applied. + +### Create Account + +```javascript +const associatedTokenAccount = await createAccount( + connection, // connection + payer, // fee payer + mint, // token mint + ownerKeypair.publicKey, // owner + undefined, // keypair | defaults to ATA + undefined, // confirm options + TOKEN_2022_PROGRAM_ID // program id +); +``` + +The newly created `associatedTokenAccount` has the immutable owner extension applied as the Associated Token Account program always uses the extension when creating accounts. + +## Conclusion + +With the Immutable Owner extension, Token 2022 removes a potential foot gun. By ensuring that a token account's derived address genuinely reflects its owner. From 6d9ee5e80129f976ce13c4759308fdcffc29087c Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Thu, 17 Aug 2023 12:04:44 +0000 Subject: [PATCH 2/8] style(guides): add metadata for guide --- content/guides/token-2022/immutable-owner.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md index d813ea35d..0faab70c7 100644 --- a/content/guides/token-2022/immutable-owner.md +++ b/content/guides/token-2022/immutable-owner.md @@ -1,4 +1,20 @@ -# Immutable Owner: Token 2022 Account Extension +--- +date: Aug 17, 2023 +difficulty: beginner +title: "Immutable Owner: Token 2022 Account Extension" +description: + "This guide walks you through how to use the Immutable Owner extension to prevent the transfer of ownership of a token account." +tags: + - token-2022 +keywords: + - beginner + - spl + - spl token + - token 2022 + - token 2022 extensions +altRoutes: + - /developers/guides/immutable-owner +--- With the Token program, the `SetAuthority` instruction can be used for various use cases. Among them, an Account's owner may transfer ownership of an account to another. From 4d72ce9273dad8c299fe4e38d2298b7a4248f88a Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Sun, 20 Aug 2023 16:34:45 +0000 Subject: [PATCH 3/8] chore(docs): reduce amount of SOL requested for airdrop --- content/guides/token-2022/immutable-owner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md index 0faab70c7..4e9c1bece 100644 --- a/content/guides/token-2022/immutable-owner.md +++ b/content/guides/token-2022/immutable-owner.md @@ -71,7 +71,7 @@ const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); // Next, we create and fund the payer account const payer = Keypair.generate(); -const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL); +const airdropSignature = await connection.requestAirdrop(payer.publicKey, 10000000); await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) }); // Next, we create a new keypair that will be the mint authority From 7a5bbf5d5e59005a90503e5bee3b83d56731d347 Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Sun, 20 Aug 2023 16:51:06 +0000 Subject: [PATCH 4/8] docs(token 2022): changed to be consistent to how we refer to the account --- content/guides/token-2022/immutable-owner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md index 4e9c1bece..c8aab61d8 100644 --- a/content/guides/token-2022/immutable-owner.md +++ b/content/guides/token-2022/immutable-owner.md @@ -141,7 +141,7 @@ const initializeImmutableOwnerInstruction = createInitializeImmutableOwnerInstru ); ``` -We then initialize the Immutable Owner extension for the given token account. It's important to note that this can only be done for accounts that have not been initialized yet. +We then initialize the Immutable Owner extension for the given account. It's important to note that this can only be done for accounts that have not been initialized yet. ```javascript const initializeAccountInstruction = createInitializeAccountInstruction( From ee6262c90ff5e3bdd376c70156d4fa47f1fed38e Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Wed, 6 Sep 2023 13:36:29 +0000 Subject: [PATCH 5/8] style: format with prettier --- content/guides/token-2022/immutable-owner.md | 215 +++++++++++-------- 1 file changed, 130 insertions(+), 85 deletions(-) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md index c8aab61d8..fc5b459d0 100644 --- a/content/guides/token-2022/immutable-owner.md +++ b/content/guides/token-2022/immutable-owner.md @@ -1,30 +1,39 @@ --- -date: Aug 17, 2023 +date: Sep 01, 2023 difficulty: beginner -title: "Immutable Owner: Token 2022 Account Extension" +title: "How to use the immutable owner extension" description: - "This guide walks you through how to use the Immutable Owner extension to prevent the transfer of ownership of a token account." -tags: - - token-2022 + "With the Token program, the `SetAuthority` instruction can be used for various use + cases. Among them, an Account's owner may transfer ownership of an account to another. + " keywords: - - beginner - - spl - - spl token - token 2022 - - token 2022 extensions + - token extensions + - token program +tags: + - token 2022 + - token extensions altRoutes: - /developers/guides/immutable-owner --- -With the Token program, the `SetAuthority` instruction can be used for various use cases. Among them, an Account's owner may transfer ownership of an account to another. +With the Token program, the `SetAuthority` instruction can be used for various +use cases. Among them, an Account's owner may transfer ownership of an account +to another. -The immutable owner extension ensures that ownership of a token account cannot be reassigned. +The immutable owner extension ensures that ownership of a token account cannot +be reassigned. ## Understanding the implications -So, why is this important? The addresses for Associated Token Accounts are derived based on the owner and the mint. This makes it easy to find the related token account for a specific owner. If the account owner has reassigned ownership of this account, then applications may derive the address for that account and use it, not knowing that it no longer belongs to the owner. +So, why is this important? The addresses for Associated Token Accounts are +derived based on the owner and the mint. This makes it easy to find the related +token account for a specific owner. If the account owner has reassigned +ownership of this account, then applications may derive the address for that +account and use it, not knowing that it no longer belongs to the owner. -This guide walks you through how to use the Immutable Owner extension to prevent the transfer of ownership of a token account. +This guide walks you through how to use the Immutable Owner extension to prevent +the transfer of ownership of a token account. Let's get started! @@ -38,7 +47,7 @@ Install the `@solana/web3.js` and `@solana/spl-token` packages. ## Setting up -We'll begin by setting up our script to create a new token account. +Let's start by setting up our script to create a new token mint. First, we will need to: @@ -48,107 +57,132 @@ First, we will need to: ```javascript import { - clusterApiUrl, - sendAndConfirmTransaction, - Connection, - Keypair, - SystemProgram, - Transaction, - LAMPORTS_PER_SOL, -} from '@solana/web3.js'; + clusterApiUrl, + sendAndConfirmTransaction, + Connection, + Keypair, + SystemProgram, + Transaction, + LAMPORTS_PER_SOL, +} from "@solana/web3.js"; import { - createAccount, - createMint, - createInitializeImmutableOwnerInstruction, - createInitializeAccountInstruction, - getAccountLen, - ExtensionType, - TOKEN_2022_PROGRAM_ID, -} from '@solana/spl-token'; + createAccount, + createMint, + createInitializeImmutableOwnerInstruction, + createInitializeAccountInstruction, + getAccountLen, + ExtensionType, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; // We establish a connection to the cluster -const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); +const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); // Next, we create and fund the payer account const payer = Keypair.generate(); -const airdropSignature = await connection.requestAirdrop(payer.publicKey, 10000000); -await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) }); +const airdropSignature = await connection.requestAirdrop( + payer.publicKey, + 2 * LAMPORTS_PER_SOL, +); +await connection.confirmTransaction({ + signature: airdropSignature, + ...(await connection.getLatestBlockhash()), +}); +``` + +## Mint setup + +Next, let's configure the properties of our token mint and generate the +necessary authorities. -// Next, we create a new keypair that will be the mint authority +```javascript +// authority that can mint new tokens const mintAuthority = Keypair.generate(); const decimals = 9; // Next, we create a new token mint const mint = await createMint( - connection, // connection - payer, // fee payer - mintAuthority.publicKey, // mint authority - mintAuthority.publicKey, // freeze authority - decimals, // decimals - undefined, // keypair - undefined, // confirm options - TOKEN_2022_PROGRAM_ID // Token Program ID + connection, // Connection to use + payer, // Payer of the transaction and initialization fees + mintAuthority.publicKey, //Account or multisig that will control minting + mintAuthority.publicKey, // Optional Account or multisig that can freeze token accounts + decimals, // Location of the decimal place + undefined, // Optional keypair, defaulting to a new random one + undefined, // Options for confirming the transaction + TOKEN_2022_PROGRAM_ID, // Token Program ID ); ``` -> The [Mint](https://spl.solana.com/token#creating-a-new-token-type) is used to create or "mint" new tokens that are stored in token accounts. +As a result, we create a new token mint using the `createMint` helper function. Lets explore two options of using the extension: -1. Creating an account with immutable ownership -2. Creating an associated token account with immutable ownership +- [Understanding the implications](#understanding-the-implications) +- [Install dependencies](#install-dependencies) +- [Setting up](#setting-up) +- [Mint setup](#mint-setup) +- [Creating an account with immutable ownership](#creating-an-account-with-immutable-ownership) + - [Account setup](#account-setup) + - [The Instructions](#the-instructions) + - [Send and confirm](#send-and-confirm) +- [Creating an associated token account with immutable ownership](#creating-an-associated-token-account-with-immutable-ownership) + - [Create Account](#create-account) +- [Conclusion](#conclusion) -## Option 1: Creating an account with immutable ownership +## Creating an account with immutable ownership ### Account setup ```javascript +// owner of the token account const ownerKeypair = Keypair.generate(); const accountKeypair = Keypair.generate(); - -// Explanation of the two keypairs: -// ownerKeypair: The owner of the token account -// accountKeypair: The address of our token account - +// address of our token account const account = accountKeypair.publicKey; const accountLen = getAccountLen([ExtensionType.ImmutableOwner]); const lamports = await connection.getMinimumBalanceForRentExemption(accountLen); ``` -Next, we get the size of our new account and calculate the amount for rent exemption. We use the helper `getAccountLen` helper function, which takes an array of extensions we want for this account. - -> The total size of the token account is 165 bytes (the size of a token account) + the size of the added extensions +Next, we get the size of our new account and calculate the amount for rent +exemption. We use the helper `getAccountLen` helper function, which takes an +array of extensions we want for this account. ### The Instructions +Now, let's build the set of instructions to: + +- Create a new account +- Initialize the immutable owner extension +- Initialize our new account as a token account + ```javascript const createAccountInstruction = SystemProgram.createAccount({ - fromPubkey: payer.publicKey, - newAccountPubkey: account, - space: accountLen, - lamports, - programId: TOKEN_2022_PROGRAM_ID, + fromPubkey: payer.publicKey, + newAccountPubkey: account, + space: accountLen, + lamports, + programId: TOKEN_2022_PROGRAM_ID, }); ``` We create a new account and assign ownership to the token 2022 program. ```javascript -const initializeImmutableOwnerInstruction = createInitializeImmutableOwnerInstruction( - account, - TOKEN_2022_PROGRAM_ID -); +const initializeImmutableOwnerInstruction = + createInitializeImmutableOwnerInstruction(account, TOKEN_2022_PROGRAM_ID); ``` -We then initialize the Immutable Owner extension for the given account. It's important to note that this can only be done for accounts that have not been initialized yet. +We then initialize the Immutable Owner extension for the given token account. +It's important to note that this can only be done for accounts that have not +been initialized yet. ```javascript const initializeAccountInstruction = createInitializeAccountInstruction( - account, - mint, - ownerKeypair.publicKey, - TOKEN_2022_PROGRAM_ID + account, + mint, + ownerKeypair.publicKey, + TOKEN_2022_PROGRAM_ID, ); ``` @@ -158,42 +192,53 @@ Next, we initialize our newly created account to hold tokens. ```javascript const transaction = new Transaction().add( - createAccountInstruction, - initializeImmutableOwnerInstruction, - initializeAccountInstruction + createAccountInstruction, + initializeImmutableOwnerInstruction, + initializeAccountInstruction, +); +await sendAndConfirmTransaction( + connection, + transaction, + [payer, accountKeypair], + undefined, ); -await sendAndConfirmTransaction(connection, transaction, [payer, accountKeypair], undefined); ``` -Finally, we add the instructions to our transaction and send it to the network. As a result, we've created a token account for our new mint with the immutable owner extension applied. +Finally, we add the instructions to our transaction and send it to the network. +As a result, we've created a token account for our new mint with the immutable +owner extension applied. If we attempt to change the owner of this account, we get an error: ```shell -"Program log: Instruction: SetAuthority", +"Program log: Instruction: SetAuthority", "Program log: The owner authority cannot be changed" ``` -## Option 2: Creating an associated token account with immutable ownership +## Creating an associated token account with immutable ownership -By default, all associated token accounts have the immutable owner extension applied. +By default, all associated token accounts have the immutable owner extension +applied. ### Create Account ```javascript const associatedTokenAccount = await createAccount( - connection, // connection - payer, // fee payer - mint, // token mint - ownerKeypair.publicKey, // owner - undefined, // keypair | defaults to ATA - undefined, // confirm options - TOKEN_2022_PROGRAM_ID // program id + connection, // connection + payer, // fee payer + mint, // token mint + ownerKeypair.publicKey, // owner + undefined, // keypair | defaults to ATA + undefined, // confirm options + TOKEN_2022_PROGRAM_ID, // program id ); ``` -The newly created `associatedTokenAccount` has the immutable owner extension applied as the Associated Token Account program always uses the extension when creating accounts. +The newly created `associatedTokenAccount` has the immutable owner extension +applied as the Associated Token Account program always uses the extension when +creating accounts. ## Conclusion -With the Immutable Owner extension, Token 2022 removes a potential foot gun. By ensuring that a token account's derived address genuinely reflects its owner. +With the Immutable Owner extension, Token 2022 removes a potential foot gun. By +ensuring that a token account's derived address genuinely reflects its owner. From 49015b76e80c0010f9300e285053a7a64a823378 Mon Sep 17 00:00:00 2001 From: John <75003086+ZYJLiu@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:53:22 -0600 Subject: [PATCH 6/8] Add Solana Playground --- content/guides/token-2022/immutable-owner.md | 328 +++++++++++-------- 1 file changed, 200 insertions(+), 128 deletions(-) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-2022/immutable-owner.md index fc5b459d0..a5a336a1a 100644 --- a/content/guides/token-2022/immutable-owner.md +++ b/content/guides/token-2022/immutable-owner.md @@ -1,10 +1,10 @@ --- -date: Sep 01, 2023 +date: Dec 6, 2023 difficulty: beginner title: "How to use the immutable owner extension" -description: - "With the Token program, the `SetAuthority` instruction can be used for various use - cases. Among them, an Account's owner may transfer ownership of an account to another. +description: "With the Token program, the `SetAuthority` instruction can be used + for various use cases. Among them, an Account's owner may transfer ownership + of an account to another. " keywords: - token 2022 @@ -17,43 +17,60 @@ altRoutes: - /developers/guides/immutable-owner --- -With the Token program, the `SetAuthority` instruction can be used for various -use cases. Among them, an Account's owner may transfer ownership of an account -to another. +With the Token Program, the `SetAuthority` instruction can be used to change a +Token Account's owner to another account. The `ImmutableOwner` extension ensures +that ownership of a Token Account cannot be reassigned. -The immutable owner extension ensures that ownership of a token account cannot -be reassigned. +In this guide, we'll walk through an example of using Solana Playground. Here is +the [final script](https://beta.solpg.io/65710736fb53fa325bfd0c4f). -## Understanding the implications +## Understanding the Implications So, why is this important? The addresses for Associated Token Accounts are derived based on the owner and the mint. This makes it easy to find the related -token account for a specific owner. If the account owner has reassigned -ownership of this account, then applications may derive the address for that -account and use it, not knowing that it no longer belongs to the owner. +Token Account for a specific owner. -This guide walks you through how to use the Immutable Owner extension to prevent -the transfer of ownership of a token account. +If the owner of an existing Associated Token Account is changed, users may +unintentionally transfer funds to an account under the assumption that it +belongs to the original owner. -Let's get started! +With Token Extensions, Associated Token Accounts have the `ImmutableOwner` +extension enabled by default, preventing the ownership from being changed. -## Install dependencies +The `ImmutableOwner` extension can also be enabled for any new Token Account. -```shell -npm i @solana/web3.js @solana/spl-token +## Getting Started + +Start by opening this Solana Playground +[link](https://beta.solpg.io/656e19acfb53fa325bfd0c46) with the following +starter code. + +```javascript +// Client +console.log("My address:", pg.wallet.publicKey.toString()); +const balance = await pg.connection.getBalance(pg.wallet.publicKey); +console.log(`My balance: ${balance / web3.LAMPORTS_PER_SOL} SOL`); ``` -Install the `@solana/web3.js` and `@solana/spl-token` packages. +If it is your first time using Solana Playground, you'll first need to create a +Playground Wallet and fund the wallet with devnet SOL. + +To get devnet SOL, run the `solana airdrop` command in the Playground's +terminal, or visit this [devnet faucet](https://faucet.solana.com/). + +``` +solana airdrop 5 +``` -## Setting up +Once you've created and funded the Playground wallet, click the "Run" button to +run the starter code. -Let's start by setting up our script to create a new token mint. +## Add Dependencies -First, we will need to: +Let's start by setting up our script. We'll be using the `@solana/web3.js` and +`@solana/spl-token` libraries. -- Establish a connection to the devnet cluster -- Generate a payer account and fund it -- Create a new token mint using the Token 2022 program +Replace the starter code with the following: ```javascript import { @@ -63,182 +80,237 @@ import { Keypair, SystemProgram, Transaction, - LAMPORTS_PER_SOL, } from "@solana/web3.js"; import { - createAccount, + createAssociatedTokenAccount, createMint, createInitializeImmutableOwnerInstruction, createInitializeAccountInstruction, getAccountLen, ExtensionType, TOKEN_2022_PROGRAM_ID, + setAuthority, + AuthorityType, } from "@solana/spl-token"; -// We establish a connection to the cluster +// Playground wallet +const payer = pg.wallet.keypair; + +// Connection to devnet cluster const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); -// Next, we create and fund the payer account -const payer = Keypair.generate(); -const airdropSignature = await connection.requestAirdrop( - payer.publicKey, - 2 * LAMPORTS_PER_SOL, -); -await connection.confirmTransaction({ - signature: airdropSignature, - ...(await connection.getLatestBlockhash()), -}); +// Transaction signature returned from sent transaction +let transactionSignature: string; ``` -## Mint setup +## Mint Setup -Next, let's configure the properties of our token mint and generate the -necessary authorities. +We'll first need to create a new Mint Account before we can create Token +Accounts. ```javascript -// authority that can mint new tokens -const mintAuthority = Keypair.generate(); -const decimals = 9; +// Authority that can mint new tokens +const mintAuthority = pg.wallet.publicKey; +// Decimals for Mint Account +const decimals = 2; -// Next, we create a new token mint +// Create Mint Account const mint = await createMint( - connection, // Connection to use + connection, payer, // Payer of the transaction and initialization fees - mintAuthority.publicKey, //Account or multisig that will control minting - mintAuthority.publicKey, // Optional Account or multisig that can freeze token accounts - decimals, // Location of the decimal place - undefined, // Optional keypair, defaulting to a new random one + mintAuthority, // Mint Authority + null, // Optional Freeze Authority + decimals, // Decimals of Mint + undefined, // Optional keypair undefined, // Options for confirming the transaction - TOKEN_2022_PROGRAM_ID, // Token Program ID + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID +); +``` + +## Associated Token Account + +The `ImmutableOwner` extension is enabled by default for Associated Token +Accounts created for Mint Accounts that are owned by the Token Extension +Program. + +Let's demonstrate this concept by creating an Associated Token Account for the +Playground wallet. + +```javascript +// Create Associated Token Account for Playground wallet +const associatedTokenAccount = await createAssociatedTokenAccount( + connection, + payer, // Payer to create Token Account + mint, // Mint Account address + payer.publicKey, // Token Account owner + undefined, // Confirmation options + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID ); ``` -As a result, we create a new token mint using the `createMint` helper function. +Attempting to change the owner of the Associated Token Account will result in an +error. -Lets explore two options of using the extension: +```javascript +try { + // Attempt to change owner of Associated Token Account + await setAuthority( + connection, // Connection to use + payer, // Payer of the transaction fee + associatedTokenAccount, // Associated Token Account + payer.publicKey, // Owner of the Associated Token Account + AuthorityType.AccountOwner, // Type of Authority + new Keypair().publicKey, // New Account Owner + undefined, // Additional signers + undefined, // Confirmation options + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID + ); +} catch (error) { + console.log("\nExpect Error:", error); +} +``` + +Run the script by clicking the `Run` button. You can then inspect the error in +the Playground terminal. You should see a message similar to the following: + +``` +Expect Error: { [Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x22] + logs: + [ 'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]', + 'Program log: Instruction: SetAuthority', + 'Program log: The owner authority cannot be changed', + 'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 3057 of 200000 compute units', + 'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x22' ] } +``` -- [Understanding the implications](#understanding-the-implications) -- [Install dependencies](#install-dependencies) -- [Setting up](#setting-up) -- [Mint setup](#mint-setup) -- [Creating an account with immutable ownership](#creating-an-account-with-immutable-ownership) - - [Account setup](#account-setup) - - [The Instructions](#the-instructions) - - [Send and confirm](#send-and-confirm) -- [Creating an associated token account with immutable ownership](#creating-an-associated-token-account-with-immutable-ownership) - - [Create Account](#create-account) -- [Conclusion](#conclusion) +### Immutable Owner Token Account -## Creating an account with immutable ownership +Next, let's build a transaction to enable the `ImmutableOwner` extension for a +new Token Account. Note that this can only be done for new Token Accounts. -### Account setup +First, let's generate a new keypair to use as the address of the Token Account. ```javascript -// owner of the token account -const ownerKeypair = Keypair.generate(); -const accountKeypair = Keypair.generate(); -// address of our token account -const account = accountKeypair.publicKey; +// Random keypair to use as owner of Token Account +const tokenAccountKeypair = Keypair.generate(); +// Address for Token Account +const tokenAccount = tokenAccountKeypair.publicKey; +``` + +Next, let's determine the size of the new Token Account and calculate the +minimum lamports needed for rent exemption. +```javascript +// Size of Token Account with extension const accountLen = getAccountLen([ExtensionType.ImmutableOwner]); +// Minimum lamports required for Token Account const lamports = await connection.getMinimumBalanceForRentExemption(accountLen); ``` -Next, we get the size of our new account and calculate the amount for rent -exemption. We use the helper `getAccountLen` helper function, which takes an -array of extensions we want for this account. +With Token Extensions, the size of the Token Account will vary based on the +extensions enabled. -### The Instructions +## Build Instructions -Now, let's build the set of instructions to: +Next, let's build the set of instructions to: - Create a new account -- Initialize the immutable owner extension -- Initialize our new account as a token account +- Initialize the `ImmutableOwner` extension +- Initialize the remaining Token Account data + +First, build the instruction to invoke the System Program to create an account +and assign ownership to the Token Extensions Program. ```javascript +// Instruction to invoke System Program to create new account const createAccountInstruction = SystemProgram.createAccount({ - fromPubkey: payer.publicKey, - newAccountPubkey: account, - space: accountLen, - lamports, - programId: TOKEN_2022_PROGRAM_ID, + fromPubkey: payer.publicKey, // Account that will transfer lamports to created account + newAccountPubkey: tokenAccount, // Address of the account to create + space: accountLen, // 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 }); ``` -We create a new account and assign ownership to the token 2022 program. +Next, build the instruction to initialize the `ImmutableOwner` extension for the +Token Account. ```javascript +// Instruction to initialize the ImmutableOwner Extension const initializeImmutableOwnerInstruction = - createInitializeImmutableOwnerInstruction(account, TOKEN_2022_PROGRAM_ID); + createInitializeImmutableOwnerInstruction( + tokenAccount, // Token Account address + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID + ); ``` -We then initialize the Immutable Owner extension for the given token account. -It's important to note that this can only be done for accounts that have not -been initialized yet. +Lastly, build the instruction to initialize the rest of the Token Account data. ```javascript +// Instruction to initialize Token Account data const initializeAccountInstruction = createInitializeAccountInstruction( - account, - mint, - ownerKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + tokenAccount, // Token Account Address + mint, // Mint Account + payer.publicKey, // Token Account Owner + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID ); ``` -Next, we initialize our newly created account to hold tokens. +## Send Transaction -### Send and confirm +Next, let's add the instructions to a new transaction and send it to the +network. This will create a Token Account with the `ImmutableOwner` extension +enabled. ```javascript +// Add instructions to new transaction const transaction = new Transaction().add( createAccountInstruction, initializeImmutableOwnerInstruction, initializeAccountInstruction, ); -await sendAndConfirmTransaction( + +// Send transaction +transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [payer, accountKeypair], - undefined, + [payer, tokenAccountKeypair], // Signers ); -``` - -Finally, we add the instructions to our transaction and send it to the network. -As a result, we've created a token account for our new mint with the immutable -owner extension applied. - -If we attempt to change the owner of this account, we get an error: -```shell -"Program log: Instruction: SetAuthority", -"Program log: The owner authority cannot be changed" +console.log( + "\nCreate Token Account:", + `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, +); ``` -## Creating an associated token account with immutable ownership - -By default, all associated token accounts have the immutable owner extension -applied. +Run the script by clicking the `Run` button. You can then inspect the +transaction details on SolanaFM. -### Create Account +If you attempt to change the owner of the Token Account, then you should see the +same error as before. ```javascript -const associatedTokenAccount = await createAccount( - connection, // connection - payer, // fee payer - mint, // token mint - ownerKeypair.publicKey, // owner - undefined, // keypair | defaults to ATA - undefined, // confirm options - TOKEN_2022_PROGRAM_ID, // program id -); +try { + // Attempt to change owner of Token Account + await setAuthority( + connection, // Connection to use + payer, // Payer of the transaction fee + tokenAccount, // Token Account + payer.publicKey, // Owner of the Token Account + AuthorityType.AccountOwner, // Type of Authority + new Keypair().publicKey, // New Account Owner + undefined, // Additional signers + undefined, // Confirmation options + TOKEN_2022_PROGRAM_ID, // Token Extension Program ID + ); +} catch (error) { + console.log("\nExpect Error:", error); +} ``` -The newly created `associatedTokenAccount` has the immutable owner extension -applied as the Associated Token Account program always uses the extension when -creating accounts. - ## Conclusion -With the Immutable Owner extension, Token 2022 removes a potential foot gun. By -ensuring that a token account's derived address genuinely reflects its owner. +The `ImmutableOwner` extension prevents an vulnerability that was previously +possible by reassigning the owner of Associated Token Accounts. This security +feature can also be applied to any new Token Account, guarding against +unintended ownership changes. From c96efba6e0604866b4e7d84982a16d28cfe95ec7 Mon Sep 17 00:00:00 2001 From: nickfrosty Date: Thu, 14 Dec 2023 16:28:53 -0500 Subject: [PATCH 7/8] refactor: changed dir --- .../guides/{token-2022 => token-extensions}/immutable-owner.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename content/guides/{token-2022 => token-extensions}/immutable-owner.md (100%) diff --git a/content/guides/token-2022/immutable-owner.md b/content/guides/token-extensions/immutable-owner.md similarity index 100% rename from content/guides/token-2022/immutable-owner.md rename to content/guides/token-extensions/immutable-owner.md From 08bef3a47930deb427f71a53f1b41c68732e68d5 Mon Sep 17 00:00:00 2001 From: nickfrosty Date: Thu, 14 Dec 2023 16:39:27 -0500 Subject: [PATCH 8/8] refactor: editorial changes --- .../token-extensions/immutable-owner.md | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/content/guides/token-extensions/immutable-owner.md b/content/guides/token-extensions/immutable-owner.md index a5a336a1a..b34275815 100644 --- a/content/guides/token-extensions/immutable-owner.md +++ b/content/guides/token-extensions/immutable-owner.md @@ -1,11 +1,12 @@ --- date: Dec 6, 2023 difficulty: beginner -title: "How to use the immutable owner extension" -description: "With the Token program, the `SetAuthority` instruction can be used - for various use cases. Among them, an Account's owner may transfer ownership - of an account to another. - " +seoTitle: "Token Extensions: Immutable Owner" +title: "How to use the Immutable Owner extension" +description: + "With the Token program, the `SetAuthority` instruction can be used to + transfer ownership of an account to another. The `ImmutableOwner` extension + can be used to prevent this." keywords: - token 2022 - token extensions @@ -21,8 +22,8 @@ With the Token Program, the `SetAuthority` instruction can be used to change a Token Account's owner to another account. The `ImmutableOwner` extension ensures that ownership of a Token Account cannot be reassigned. -In this guide, we'll walk through an example of using Solana Playground. Here is -the [final script](https://beta.solpg.io/65710736fb53fa325bfd0c4f). +In this guide, we WILL walk through an example of using Solana Playground. Here +is the [final script](https://beta.solpg.io/65710736fb53fa325bfd0c4f). ## Understanding the Implications @@ -37,7 +38,8 @@ belongs to the original owner. With Token Extensions, Associated Token Accounts have the `ImmutableOwner` extension enabled by default, preventing the ownership from being changed. -The `ImmutableOwner` extension can also be enabled for any new Token Account. +The `ImmutableOwner` extension can also be enabled for any new Token Account +created by the Token Extension program. ## Getting Started @@ -55,6 +57,14 @@ console.log(`My balance: ${balance / web3.LAMPORTS_PER_SOL} SOL`); If it is your first time using Solana Playground, you'll first need to create a Playground Wallet and fund the wallet with devnet SOL. + + +If you do not have a Playground wallet, you may see a type error within the +editor on all declarations of `pg.wallet.publicKey`. This type error will clear +after you create a Playground wallet. + + + To get devnet SOL, run the `solana airdrop` command in the Playground's terminal, or visit this [devnet faucet](https://faucet.solana.com/). @@ -310,7 +320,7 @@ try { ## Conclusion -The `ImmutableOwner` extension prevents an vulnerability that was previously +The `ImmutableOwner` extension prevents a vulnerability that was previously possible by reassigning the owner of Associated Token Accounts. This security feature can also be applied to any new Token Account, guarding against unintended ownership changes.