From ff3ca4725f22590fc305ee7f0479d7267c3150df Mon Sep 17 00:00:00 2001
From: John <75003086+ZYJLiu@users.noreply.github.com>
Date: Thu, 7 Dec 2023 16:46:05 -0600
Subject: [PATCH] Add Solana Playground

---
 content/guides/token-2022/required-memo.md | 426 +++++++++++++++------
 1 file changed, 309 insertions(+), 117 deletions(-)

diff --git a/content/guides/token-2022/required-memo.md b/content/guides/token-2022/required-memo.md
index 053e9a50f..f1f0e7e61 100644
--- a/content/guides/token-2022/required-memo.md
+++ b/content/guides/token-2022/required-memo.md
@@ -1,5 +1,5 @@
 ---
-date: Sep 01, 2023
+date: Dec 7, 2023
 title: How to use the Required memo extension
 description:
   "Memos in financial transactions serve as a communication tool between sender
@@ -17,61 +17,58 @@ altRoutes:
   - /developers/guides/required-memo
 ---
 
-Memos in financial transactions serve as a communication tool between sender and
-recipient. It aids in the identification of both parties and offers clarity on
-the purpose of the transfer.
+The `MemoTransfer` extension enforces that every incoming transfer to a Token
+Account is accompanied by a [memo](https://spl.solana.com/memo) instruction.
+This memo instruction records a message in the transaction's program logs. This
+feature is particularly useful for adding context to transactions, making it
+easier to understand their purpose when reviewing the transaction logs later.
 
-The Required Memo extension enforces that all incoming transfers have an
-accompanying [memo](https://spl.solana.com/memo) instruction.
+In this guide, we'll walk through an example of using Solana Playground. Here is
+the [final script](https://beta.solpg.io/65724a91fb53fa325bfd0c54).
 
-## The value of adding memos
+## Getting Started
 
-**Identifying Parties**: Memos help pinpoint the sender and receiver. Given the
-anonymity of addresses, a memo can provide context, ensuring the right parties
-are involved.
+Start by opening this Solana Playground
+[link](https://beta.solpg.io/656e19acfb53fa325bfd0c46) with the following
+starter code.
 
-**Clarifying Purpose**: A memo provides clarity on the transaction's intent.
-
-**Tracking Finances**: If you're keeping an eye on your assets, memos help
-ensure you've sent or received the right amounts, which is especially useful
-during tax time (ahemm).
-
-These are a few examples of how transaction memos offer clarity, help in
-tracking, and add a personal touch, making every transaction easily
-identifiable.
-
-This guide walks you through how to use the Required Memo extension to enforce a
-memo on all incoming transfers.
+```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`);
+```
 
-Let's get started!
+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.
 
-## Install dependencies
+To get devnet SOL, run the `solana airdrop` command in the Playground's
+terminal, or visit this [devnet faucet](https://faucet.solana.com/).
 
-```shell
-npm i @solana/web3.js @solana/spl-token
+```
+solana airdrop 5
 ```
 
-Install the `@solana/web3.js` and `@solana/spl-token` packages.
-
-## 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 {
   Connection,
   Keypair,
-  LAMPORTS_PER_SOL,
   SystemProgram,
   Transaction,
   clusterApiUrl,
   sendAndConfirmTransaction,
+  TransactionInstruction,
+  PublicKey,
 } from "@solana/web3.js";
 import {
   ExtensionType,
@@ -82,161 +79,356 @@ import {
   disableRequiredMemoTransfers,
   enableRequiredMemoTransfers,
   getAccountLen,
+  createAccount,
+  mintTo,
+  createTransferInstruction,
 } 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,
-  0.5 * LAMPORTS_PER_SOL,
-);
-await connection.confirmTransaction({
-  signature: airdropSignature,
-  ...(await connection.getLatestBlockhash()),
-});
+// Transaction to send
+let transaction: Transaction;
+// 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
 );
 ```
 
-As a result, we create a new token mint using the `createMint` helper function.
+## Memo Transfer Token Account
+
+Next, let's build a transaction to enable the `MemoTransfer` extension for a new
+Token Account.
 
-## 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.MemoTransfer]);
+// 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 our new account as a token account
-- Enable the required memo extension
+- Initialize the Token Account data
+- Enable the `MemoTransfer` extension
+
+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, // The account that will transfer lamports to the created account
-  newAccountPubkey: account, // Amount of lamports to transfer to the created account
-  space: accountLen, // Amount of space in bytes to allocate to the created account
-  lamports, // Amount of lamports to transfer to the created account
-  programId: TOKEN_2022_PROGRAM_ID, // Public key of the program to assign as the owner of the created account
+  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 Token Account data.
 
 ```javascript
+// Instruction to initialize Token Account data
 const initializeAccountInstruction = createInitializeAccountInstruction(
-  account, // New token account
-  mint, // Mint account
-  ownerKeypair.publicKey, // Owner of the new token account
-  TOKEN_2022_PROGRAM_ID, // Token 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.
+Lastly, build the instruction to enable the `MemoTransfer` extension for the
+Token Account.
 
 ```javascript
+// Instruction to initialize the MemoTransfer Extension
 const enableRequiredMemoTransfersInstruction =
   createEnableRequiredMemoTransfersInstruction(
-    account, // Token account to update
-    owner.publicKey, // The account owner/delegate
-    [], // The signer account(s)
+    tokenAccount, // Token Account address
+    payer.publicKey, // Token Account Owner
+    undefined, // Additional signers
     TOKEN_2022_PROGRAM_ID, // Token Program ID
   );
 ```
 
-We then initialize the Required memo extension for the given token account. It's
-important to note that this can be enabled and disabled at any time.
+## 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 `MemoTransfer` extension
+enabled.
 
 ```javascript
-const transaction = new Transaction().add(
+// Add instructions to new transaction
+transaction = new Transaction().add(
   createAccountInstruction,
   initializeAccountInstruction,
   enableRequiredMemoTransfersInstruction,
 );
-await sendAndConfirmTransaction(
+
+// Send transaction
+transactionSignature = await sendAndConfirmTransaction(
   connection,
   transaction,
-  [payer, owner, accountKeypair],
-  undefined,
+  [payer, tokenAccountKeypair], // Signers
+);
+
+console.log(
+  "\nCreate Token Account:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
 );
 ```
 
-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.
+Run the script by clicking the `Run` button. You can then inspect the
+transaction details on SolanaFM.
+
+## Create and Fund Token Account
+
+Next, let's set up another Token Account to demonstrate the functionality of the
+`MemoTransfer` extension.
 
-## Enabling memo transfers
+First, create a `sourceTokenAccount` owned by the Playground wallet.
 
-An account owner can enable the required memo transfer extension at any time.
+```javascript
+// Create Token Account for Playground wallet
+const sourceTokenAccount = await createAccount(
+  connection,
+  payer, // Payer to create Token Account
+  mint, // Mint Account address
+  payer.publicKey, // Token Account owner
+  undefined, // Optional keypair, default to Associated Token Account
+  undefined, // Confirmation options
+  TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
+);
+```
+
+Next, mint 2 tokens to the `sourceTokenAccount` to fund it.
 
 ```javascript
-await enableRequiredMemoTransfers(
-  connection, // connection to use
-  payer, // payer of the transaction fee
-  account, // account to modify
-  owner, // owner of the account
-  [], // signing account if owner is a multisig
-  undefined, // options for confirming the transaction
-  TOKEN_2022_PROGRAM_ID, // Token Program ID
+// Mint tokens to sourceTokenAccount
+transactionSignature = await mintTo(
+  connection,
+  payer, // Transaction fee payer
+  mint, // Mint Account address
+  sourceTokenAccount, // Mint to
+  mintAuthority, // Mint Authority address
+  200, // Amount
+  undefined, // Additional signers
+  undefined, // Confirmation options
+  TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
+);
+
+console.log(
+  "\nMint Tokens:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
 );
 ```
 
-## Disabling memo transfers
+## Transfer and Memo Instruction
+
+Let's prepare the token transfer and memo instruction.
 
-An account owner can disable the required memo transfer extension at any time.
+First, build the instruction to transfer tokens from the `sourceTokenAccount` to
+the `tokenAccount` with the `MemoTransfer` extension enabled.
 
 ```javascript
-await disableRequiredMemoTransfers(
-  connection, // connection to use
-  payer, // payer of the transaction fee
-  account, // account to modify
-  owner, // owner of the account
-  [], // signing account if owner is a multisig
-  undefined, // options for confirming the transaction
-  TOKEN_2022_PROGRAM_ID, // Token Program ID
+// Instruction to transfer tokens
+const transferInstruction = createTransferInstruction(
+  sourceTokenAccount, // Source Token Account
+  tokenAccount, // Destination Token Account
+  payer.publicKey, // Source Token Account owner
+  100, // Amount
+  undefined, // Additional signers
+  TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
 );
 ```
 
+Next, build the memo instruction. The message will be included in the program
+logs of the transaction the instruction is added to.
+
+```javascript
+// Message for the memo
+const message = "Hello, Solana";
+// Instruction to add memo
+const memoInstruction = new TransactionInstruction({
+  keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }],
+  data: Buffer.from(message, "utf-8"),
+  programId: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
+});
+```
+
+## Attempt Transfer without Memo
+
+To demonstrate the functionality of the `MemoTransfer` extension, let's first
+attempt to send a token transfer without a memo.
+
+```javascript
+try {
+  // Attempt to transfer without memo
+  transaction = new Transaction().add(transferInstruction);
+
+  // Send transaction
+  await sendAndConfirmTransaction(
+    connection,
+    transaction,
+    [payer], // Signers
+  );
+} 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: 0x24]
+  logs:
+   [ 'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]',
+     'Program log: Instruction: Transfer',
+     'Program log: Error: No memo in previous instruction; required for recipient to receive a transfer',
+     'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 6571 of 200000 compute units',
+     'Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x24' ] }
+```
+
+## Transfer with Memo
+
+Next, send a token transfer with the memo instruction included on the
+tranasction.
+
+```javascript
+// Add instructions to new transaction
+transaction = new Transaction().add(memoInstruction, transferInstruction);
+
+// Send transaction
+transactionSignature = await sendAndConfirmTransaction(
+  connection,
+  transaction,
+  [payer], // Signers
+);
+
+console.log(
+  "\nTransfer with Memo:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
+);
+```
+
+Run the script by clicking the `Run` button. You can then inspect the
+transaction details on SolanaFM.
+
+## Enable and Disable Memo Transfer
+
+The `MemoTransfer` extension can also be freely enabled or disabled by the Token
+Account owner.
+
+To enable the `MemoTransfer` extension, use the `enableRequiredMemoTransfers`
+instruction.
+
+```javascript
+// Enable Required Memo Transfers
+transactionSignature = await enableRequiredMemoTransfers(
+  connection, // Connection to use
+  payer, // Payer of the transaction fee
+  tokenAccount, // Token Account to modify
+  payer.publicKey, // Owner of Token Account
+  undefined, // Additional signers
+  undefined, // Confirmation options
+  TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
+);
+
+console.log(
+  "\nEnable Required Memo Transfers:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
+);
+```
+
+To disable the `MemoTransfer` extension, use the `disableRequiredMemoTransfers`
+instruction.
+
+```javascript
+// Disable Required Memo Transfers
+transactionSignature = await disableRequiredMemoTransfers(
+  connection, // Connection to use
+  payer, // Payer of the transaction fee
+  tokenAccount, // Token Account to modify
+  payer.publicKey, // Owner of Token Account
+  undefined, // Additional signers
+  undefined, // Confirmation options
+  TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
+);
+
+console.log(
+  "\nDisable Required Memo Transfers:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
+);
+```
+
+Once the `MemoTransfer` extension is disabled, transactions to transfer tokens
+without a memo instruction will complete successfully.
+
+```javascript
+// Add instructions to new transaction
+transaction = new Transaction().add(transferInstruction);
+
+// Send transaction
+transactionSignature = await sendAndConfirmTransaction(
+  connection,
+  transaction,
+  [payer], // Signers
+);
+
+console.log(
+  "\nTransfer without Memo:",
+  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
+);
+```
+
+Run the script by clicking the `Run` button. You can then inspect the
+transaction details on SolanaFM.
+
 ## Conclusion
 
 The Required Memo extension in the Token 2022 program ensures every incoming