Solana Quick Start Guide

Welcome to the Solana Quick Start Guide! This hands-on guide will introduce you to the core concepts for building on Solana, regardless of your prior experience. By the end of this tutorial, you'll have a basic foundation in Solana development and be ready to explore more advanced topics.

What You'll Learn #

In this tutorial, you'll learn about:

  • Understanding Accounts: Explore how data is stored on the Solana network.
  • Sending Transactions: Learn to interact with the Solana network by sending transactions.
  • Building and Deploying Programs: Create your first Solana program and deploy it to the network.
  • Program Derived Addresses (PDAs): Learn how to use PDAs to create deterministic addresses for accounts.
  • Cross-Program Invocations (CPIs): Learn how to make your programs interact with other programs on Solana.

The best part? You don't need to install anything! We'll be using Solana Playground, a browser-based development environment, for all our examples. This means you can follow along, copy and paste code, and see results immediately, all from your web browser. Basic programming knowledge is helpful but not required.

Let's dive in and start building on Solana!

Solana Playground #

Solana Playground (Solpg) is a browser-based development environment that allows you to quickly develop, deploy, and test Solana programs!

Open a new tab in your web browser and navigate to https://beta.solpg.io/.

Create Playground Wallet #

If you're new to Solana Playground, the first step is to create your Playground Wallet. This wallet will allow you to interact with the Solana network right from your browser.

Step 1. Connect to Playground #

Click the "Not connected" button at the bottom left of the screen.

Not ConnectedNot Connected

Step 2. Create Your Wallet #

You'll see an option to save your wallet's keypair. Optionally, save your wallet's keypair for backup and then click "Continue".

Create Playground WalletCreate Playground Wallet

You should now see your wallet's address, SOL balance, and connected cluster (devnet by default) at the bottom of the window.

ConnectedConnected

Info

Your Playground Wallet will be saved in your browser's local storage. Clearing your browser cache will remove your saved wallet.

Get Devnet SOL #

Before we start building, we first need some devnet SOL.

From a developer's perspective, SOL is required for two main use cases:

  • To create accounts where we can store data or deploy programs
  • To pay for transaction fees when we interact with the network

Below are two methods to fund your wallet with devnet SOL:

Option 1: Using the Playground Terminal #

To fund your Playground wallet with devnet SOL. In the Playground terminal, run:

Terminal
solana airdrop 5

Option 2: Using the Devnet Faucet #

If the airdrop command doesn't work (due to rate limits or errors), you can use the Web Faucet.

  • Enter your wallet address (found at the bottom of the Playground screen) and select an amount
  • Click "Confirm Airdrop" to receive your devnet SOL

Faucet AirdropFaucet Airdrop

Reading from network #

Now, let's explore how to read data from the Solana network. We'll fetch a few different accounts to understand the structure of a Solana account.

On Solana, all data is contained in what we call "accounts". You can think of data on Solana as a public database with a single "Accounts" table, where each entry in this table is an individual account.

Accounts on Solana can store "state" or "executable" programs, all of which can be thought of as entries in the same "Accounts" table. Each account has an "address" (public key) that serves as its unique ID used to locate its corresponding on-chain data.

Solana accounts contain either:

  • State: This is data that's meant to be read from and persisted. It could be information about tokens, user data, or any other type of data defined within a program.
  • Executable Programs: These are accounts that contain the actual code of Solana programs. They contain the instructions that can be executed on the network.

This separation of program code and program state is a key feature of Solana's Account Model. For more details, refer to the Solana Account Model page.

Fetch Playground Wallet #

Let's start by looking at a familiar account - your own Playground Wallet! We'll fetch this account and examine its structure to understand what a basic Solana account looks like.

Step 1: Open the Example #

Click this link to open the example in Solana Playground. You'll see this code:

client.ts
const address = pg.wallet.publicKey;
const accountInfo = await pg.connection.getAccountInfo(address);
 
console.log(JSON.stringify(accountInfo, null, 2));

Step 2: Run the Code #

In the Playground terminal, type the run command and hit enter:

Terminal
run

You should see details about your wallet account, including its balance in lamports, with output similar to the following:

Fetch Token Program #

Next, we'll examine the Token Extensions program, an executable program for interacting with tokens on Solana.

Step 1: Open the Example #

Click this link to open the example in Solana Playground. You'll see this code:

client.ts
import { PublicKey } from "@solana/web3.js";
 
const address = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
const accountInfo = await pg.connection.getAccountInfo(address);
 
console.log(JSON.stringify(accountInfo, null, 2));

Instead of fetching your Playground wallet, here we fetch the address of the Token Extensions Program account.

Step 2: Run the Code #

Run the code using the run command in the terminal.

Terminal
run

Examine the output and how this program account differs from your wallet account.

Fetch Mint Account #

In this step, we'll examine a Mint account, which represents a unique token on the Solana network.

Step 1: Open the Example #

Click this link to open the example in Solana Playground. You'll see this code:

client.ts
import { PublicKey } from "@solana/web3.js";
 
const address = new PublicKey("C33qt1dZGZSsqTrHdtLKXPZNoxs6U1ZBfyDkzmj6mXeR");
const accountInfo = await pg.connection.getAccountInfo(address);
 
console.log(JSON.stringify(accountInfo, null, 2));

In this example, we'll fetch the address of an existing Mint account on devnet.

Step 2: Run the Code #

Run the code using the run command.

Terminal
run

Step 3: Deserialize Mint Account Data #

To read the data field from any account, you need to deserialize the data buffer into the expected data type. This is often done using helper functions from client libraries for a particular program.

Open this next example in Solana Playground. You'll see this code:

client.ts
import { PublicKey } from "@solana/web3.js";
import { getMint, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
 
const address = new PublicKey("C33qt1dZGZSsqTrHdtLKXPZNoxs6U1ZBfyDkzmj6mXeR");
const mintData = await getMint(
  pg.connection,
  address,
  "confirmed",
  TOKEN_2022_PROGRAM_ID,
);
 
console.log(mintData);

This example uses the getMint helper function to automatically deserialize the data field of the Mint account.

Run the code using the run command.

Terminal
run

You should see the following deserialized Mint account data.

Writing to network #

Now that we've explored reading from the Solana network, let's learn how to write data to it. On Solana, we interact with the network by sending transactions made up of instructions. These instructions are defined by programs, which contain the business logic for how accounts should be updated.

Let's walk through two common operations, transferring SOL and creating a token, to demonstrate how to build and send transactions. For more details, refer to the Transactions and Instructions and Fees on Solana pages.

Transfer SOL #

We'll start with a simple SOL transfer from your wallet to another account. This requires invoking the transfer instruction on the System Program.

Click this link to open the example in Solana Playground. You'll see this code:

client.ts
import {
  LAMPORTS_PER_SOL,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
  Keypair,
} from "@solana/web3.js";
 
const sender = pg.wallet.keypair;
const receiver = new Keypair();
 
const transferInstruction = SystemProgram.transfer({
  fromPubkey: sender.publicKey,
  toPubkey: receiver.publicKey,
  lamports: 0.01 * LAMPORTS_PER_SOL,
});
 
const transaction = new Transaction().add(transferInstruction);
 
const transactionSignature = await sendAndConfirmTransaction(
  pg.connection,
  transaction,
  [sender],
);
 
console.log(
  "Transaction Signature:",
  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);

Run the code using the run command.

Terminal
run

Click on the output link to view the transaction details on the SolanaFM explorer.

Transfer SOLTransfer SOL

You've just sent your first transaction on Solana! Notice how we created an instruction, added it to a transaction, and then sent that transaction to the network. This is the basic process for building any transaction.

Create a Token #

Now, let's create a new token by creating and initializing a Mint account. This requires two instructions:

  • Invoke the System Program to create a new account
  • Invoke the Token Extensions Program

Click this link to open the example in Solana Playground. You'll see the following code:

client.ts
import {
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  clusterApiUrl,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
  MINT_SIZE,
  TOKEN_2022_PROGRAM_ID,
  createInitializeMint2Instruction,
  getMinimumBalanceForRentExemptMint,
} from "@solana/spl-token";
 
const wallet = pg.wallet;
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
 
// Generate keypair to use as address of mint account
const mint = new Keypair();
 
// Calculate minimum lamports for space required by mint account
const rentLamports = await getMinimumBalanceForRentExemptMint(connection);
 
// Instruction to create new account with space for new mint account
const createAccountInstruction = SystemProgram.createAccount({
  fromPubkey: wallet.publicKey,
  newAccountPubkey: mint.publicKey,
  space: MINT_SIZE,
  lamports: rentLamports,
  programId: TOKEN_2022_PROGRAM_ID,
});
 
// Instruction to initialize mint account
const initializeMintInstruction = createInitializeMint2Instruction(
  mint.publicKey,
  2, // decimals
  wallet.publicKey, // mint authority
  wallet.publicKey, // freeze authority
  TOKEN_2022_PROGRAM_ID,
);
 
// Build transaction with instructions to create new account and initialize mint account
const transaction = new Transaction().add(
  createAccountInstruction,
  initializeMintInstruction,
);
 
const transactionSignature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [
    wallet.keypair, // payer
    mint, // mint address keypair
  ],
);
 
console.log(
  "\nTransaction Signature:",
  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);
 
console.log(
  "\nMint Account:",
  `https://solana.fm/address/${mint.publicKey}?cluster=devnet-solana`,
);

Run the code using the run command.

Terminal
run

You'll see two links printed to the Playground terminal:

  • One for the transaction details
  • One for the newly created mint account

Click the links to inspect the transaction details and the newly created mint account on SolanaFM.

Create TokenCreate Token

Mint AccountMint Account

Notice how we built a transaction with multiple instructions this time. We first created a new account and then initialized its data as a mint. This is how you build more complex transactions that involve instructions from multiple programs.

Deploying Your First Solana Program #

In this section, we'll build, deploy, and test a simple Solana program using the Anchor framework. By the end, you'll have deployed your first program to the Solana blockchain!

The purpose of this section is to familiarize you with the Solana Playground. We'll walk through a more detailed example in the PDA and CPI sections. For more details, refer to the Programs on Solana page.

Create Anchor Project #

First, open https://beta.solpg.io in a new browser tab.

  • Click the "Create a new project" button on the left-side panel.

  • Enter a project name, select Anchor as the framework, then click the "Create" button.

New ProjectNew Project

You'll see a new project created with the program code in the src/lib.rs file.

lib.rs
use anchor_lang::prelude::*;
 
// This is your program's public key and it will update
// automatically when you build the project.
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data); // Message will show up in the tx logs
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    // We must specify the space in order to initialize an account.
    // First 8 bytes are default account discriminator,
    // next 8 bytes come from NewAccount.data being type u64.
    // (u64 = 64 bits unsigned integer = 8 bytes)
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[account]
pub struct NewAccount {
    data: u64
}

Build and Deploy Program #

To build the program, simply run build in the terminal.

Terminal
build

Notice that the address in declare_id!() has been updated. This is your program's on-chain address.

Once the program is built, run deploy in the terminal to deploy the program to the network (devnet by default). To deploy a program, SOL must be allocated to the on-chain account that stores the program.

Before deployment, ensure you have enough SOL. You can get devnet SOL by either running solana airdrop 5 in the Playground terminal or using the Web Faucet.

Terminal
deploy

Alternatively, you can also use the Build and Deploy buttons on the left-side panel.

Build and DeployBuild and Deploy

Once the program is deployed, you can now invoke its instructions.

Test Program #

Included with the starter code is a test file found in tests/anchor.test.ts. This file demonstrates how to invoke the initialize instruction on the starter program from the client.

anchor.test.ts
// No imports needed: web3, anchor, pg and more are globally available
 
describe("Test", () => {
  it("initialize", async () => {
    // Generate keypair for the new account
    const newAccountKp = new web3.Keypair();
 
    // Send transaction
    const data = new BN(42);
    const txHash = await pg.program.methods
      .initialize(data)
      .accounts({
        newAccount: newAccountKp.publicKey,
        signer: pg.wallet.publicKey,
        systemProgram: web3.SystemProgram.programId,
      })
      .signers([newAccountKp])
      .rpc();
    console.log(`Use 'solana confirm -v ${txHash}' to see the logs`);
 
    // Confirm transaction
    await pg.connection.confirmTransaction(txHash);
 
    // Fetch the created account
    const newAccount = await pg.program.account.newAccount.fetch(
      newAccountKp.publicKey,
    );
 
    console.log("On-chain data is:", newAccount.data.toString());
 
    // Check whether the data on-chain is equal to local 'data'
    assert(data.eq(newAccount.data));
  });
});

To run the test file once the program is deployed, run test in the terminal.

Terminal
test

You should see an output indicating that the test passed successfully.

You can also use the Test button on the left-side panel.

Run TestRun Test

You can then view the transaction logs by running the solana confirm -v command and specifying the transaction hash (signature) from the test output:

Terminal
solana confirm -v [TxHash]

For example:

Terminal
solana confirm -v 3TewJtiUz1EgtT88pLJHvKFzqrzDNuHVi8CfD2mWmHEBAaMfC5NAaHdmr19qQYfTiBace6XUmADvR4Qrhe8gH5uc

Alternatively, you can view the transaction details on SolanaFM or Solana Explorer by searching for the transaction signature (hash).

Info

Reminder to update the cluster (network) connection on the Explorer you are using to match Solana Playground. Solana Playground's default cluster is devnet.

Close Program #

Lastly, the SOL allocated to the on-chain program can be fully recovered by closing the program.

You can close a program by running the following command and specifying the program address found in declare_id!():

Terminal
solana program close [ProgramID]

For example:

Terminal
solana program close 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r

Congratulations! You've just built and deployed your first Solana program using the Anchor framework!

Program Derived Address #

In this section, we'll walk through how to build a basic CRUD (Create, Read, Update, Delete) program. The program will store a user's message using a Program Derived Address (PDA) as the account's address.

The purpose of this section is to guide you through the steps for building and testing a Solana program using the Anchor framework and demonstrating how to use PDAs within a program. For more details, refer to the Programs Derived Address page.

For reference, here is the final code after completing both the PDA and CPI sections.

Starter Code #

Begin by opening this Solana Playground link with the starter code. Then click the "Import" button, which will add the program to your list of projects on Solana Playground.

ImportImport

In the lib.rs file, you'll find a program scaffolded with the create, update, and delete instructions we'll implement in the following steps.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("8KPzbM2Cwn4Yjak7QYAEH9wyoQh86NcBicaLuzPaejdw");
 
#[program]
pub mod pda {
    use super::*;
 
    pub fn create(_ctx: Context<Create>) -> Result<()> {
        Ok(())
    }
 
    pub fn update(_ctx: Context<Update>) -> Result<()> {
        Ok(())
    }
 
    pub fn delete(_ctx: Context<Delete>) -> Result<()> {
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Create {}
 
#[derive(Accounts)]
pub struct Update {}
 
#[derive(Accounts)]
pub struct Delete {}
 
#[account]
pub struct MessageAccount {}

Before we begin, run build in the Playground terminal to check the starter program builds successfully.

Terminal
build

Define Message Account Type #

First, let's define the structure for the message account that our program will create. This is the data that we'll store in the account created by the program.

In lib.rs, update the MessageAccount struct with the following:

lib.rs
#[account]
pub struct MessageAccount {
    pub user: Pubkey,
    pub message: String,
    pub bump: u8,
}

Build the program again by running build in the terminal.

Terminal
build

We've defined what our message account will look like. Next, we'll implement the program instructions.

Implement Create Instruction #

Now, let's implement the create instruction to create and initialize the MessageAccount.

Start by defining the accounts required for the instruction by updating the Create struct with the following:

lib.rs
#[derive(Accounts)]
#[instruction(message: String)]
pub struct Create<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
 
    #[account(
        init,
        seeds = [b"message", user.key().as_ref()],
        bump,
        payer = user,
        space = 8 + 32 + 4 + message.len() + 1
    )]
    pub message_account: Account<'info, MessageAccount>,
    pub system_program: Program<'info, System>,
}

Next, implement the business logic for the create instruction by updating the create function with the following:

lib.rs
pub fn create(ctx: Context<Create>, message: String) -> Result<()> {
    msg!("Create Message: {}", message);
    let account_data = &mut ctx.accounts.message_account;
    account_data.user = ctx.accounts.user.key();
    account_data.message = message;
    account_data.bump = ctx.bumps.message_account;
    Ok(())
}

Rebuild the program.

Terminal
build

Implement Update Instruction #

Next, implement the update instruction to update the MessageAccount with a new message.

Just as before, the first step is to specify the accounts required by the update instruction.

Update the Update struct with the following:

lib.rs
#[derive(Accounts)]
#[instruction(message: String)]
pub struct Update<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
 
    #[account(
        mut,
        seeds = [b"message", user.key().as_ref()],
        bump = message_account.bump,
        realloc = 8 + 32 + 4 + message.len() + 1,
        realloc::payer = user,
        realloc::zero = true,
    )]
    pub message_account: Account<'info, MessageAccount>,
    pub system_program: Program<'info, System>,
}

Next, implement the logic for the update instruction.

lib.rs
pub fn update(ctx: Context<Update>, message: String) -> Result<()> {
    msg!("Update Message: {}", message);
    let account_data = &mut ctx.accounts.message_account;
    account_data.message = message;
    Ok(())
}

Rebuld the program

Terminal
build

Implement Delete Instruction #

Next, implement the delete instruction to close the MessageAccount.

Update the Delete struct with the following:

lib.rs
#[derive(Accounts)]
pub struct Delete<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
 
    #[account(
        mut,
        seeds = [b"message", user.key().as_ref()],
        bump = message_account.bump,
        close= user,
    )]
    pub message_account: Account<'info, MessageAccount>,
}

Next, implement the logic for the update instruction.

lib.rs
pub fn delete(_ctx: Context<Delete>) -> Result<()> {
    msg!("Delete Message");
    Ok(())
}

Rebuild the program.

Terminal
build

Deploy Program #

The basic CRUD program is now complete. Deploy the program by running deploy in the Playground terminal.

Terminal
deploy

Set Up Test File #

Included with the starter code is also a test file in anchor.test.ts.

anchor.test.ts
import { PublicKey } from "@solana/web3.js";
 
describe("pda", () => {
  it("Create Message Account", async () => {});
 
  it("Update Message Account", async () => {});
 
  it("Delete Message Account", async () => {});
});

Add the code below inside describe, but before the it sections.

anchor.test.ts
const program = pg.program;
const wallet = pg.wallet;
 
const [messagePda, messageBump] = PublicKey.findProgramAddressSync(
  [Buffer.from("message"), wallet.publicKey.toBuffer()],
  program.programId,
);

Run the test file by running test in the Playground terminal to check the file runs as expected. We will implement the tests in the following steps.

Terminal
test

Invoke Create Instruction #

Update the first test with the following:

anchor.test.ts
it("Create Message Account", async () => {
  const message = "Hello, World!";
  const transactionSignature = await program.methods
    .create(message)
    .accounts({
      messageAccount: messagePda,
    })
    .rpc({ commitment: "confirmed" });
 
  const messageAccount = await program.account.messageAccount.fetch(
    messagePda,
    "confirmed",
  );
 
  console.log(JSON.stringify(messageAccount, null, 2));
  console.log(
    "Transaction Signature:",
    `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
  );
});

Invoke Update Instruction #

Update the second test with the following:

anchor.test.ts
it("Update Message Account", async () => {
  const message = "Hello, Solana!";
  const transactionSignature = await program.methods
    .update(message)
    .accounts({
      messageAccount: messagePda,
    })
    .rpc({ commitment: "confirmed" });
 
  const messageAccount = await program.account.messageAccount.fetch(
    messagePda,
    "confirmed",
  );
 
  console.log(JSON.stringify(messageAccount, null, 2));
  console.log(
    "Transaction Signature:",
    `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
  );
});

Invoke Delete Instruction #

Update the third test with the following:

anchor.test.ts
it("Delete Message Account", async () => {
  const transactionSignature = await program.methods
    .delete()
    .accounts({
      messageAccount: messagePda,
    })
    .rpc({ commitment: "confirmed" });
 
  const messageAccount = await program.account.messageAccount.fetchNullable(
    messagePda,
    "confirmed",
  );
 
  console.log("Expect Null:", JSON.stringify(messageAccount, null, 2));
  console.log(
    "Transaction Signature:",
    `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
  );
});

Run Test #

Once the tests are set up, run the test file by running test in the Playground terminal.

Terminal
test

Cross Program Invocation #

In this section, we'll update our existing CRUD program to include Cross Program Invocations (CPIs). We'll modify the program to transfer SOL between accounts in the update and delete instructions, demonstrating how to interact with other programs (in this case, the System Program) from within our program.

The purpose of this section is to walk through the process of implementing CPIs in a Solana program using the Anchor framework, building upon the PDA concepts we explored in the previous section. For more details, refer to the Cross Program Invocation page.

Modify Update Instruction #

First, we'll implement a simple "pay-to-update" mechanism by modifying the Update struct and update function.

Begin by updating the lib.rs file to bring into scope items from the system_program module.

lib.rs
use anchor_lang::system_program::{transfer, Transfer};

Next, update the Update struct to include an additional account called vault_account. This account, controlled by our program, will receive SOL from a user when they update their message account.

lib.rs
#[account(
    mut,
    seeds = [b"vault", user.key().as_ref()],
    bump,
)]
pub vault_account: SystemAccount<'info>,

Next, implement the CPI logic in the update instruction to transfer 0.001 SOL from the user's account to the vault account.

lib.rs
let transfer_accounts = Transfer {
    from: ctx.accounts.user.to_account_info(),
    to: ctx.accounts.vault_account.to_account_info(),
};
let cpi_context = CpiContext::new(
    ctx.accounts.system_program.to_account_info(),
    transfer_accounts,
);
transfer(cpi_context, 1_000_000)?;

Rebuild the program.

Terminal
build

Modify Delete Instruction #

We'll now implement a "refund on delete" mechanism by modifying the Delete struct and delete function.

First, update the Delete struct to include the vault_account. This allows us to transfer any SOL in the vault back to the user when they close their message account.

lib.rs
#[account(
    mut,
    seeds = [b"vault", user.key().as_ref()],
    bump,
)]
pub vault_account: SystemAccount<'info>,

Also add the system_program as the CPI for the transfer requires invoking the System Program.

lib.rs
pub system_program: Program<'info, System>,

Next, implement the CPI logic in the delete instruction to transfer SOL from the vault account back to the user's account.

lib.rs
let user_key = ctx.accounts.user.key();
let signer_seeds: &[&[&[u8]]] =
    &[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];
 
let transfer_accounts = Transfer {
    from: ctx.accounts.vault_account.to_account_info(),
    to: ctx.accounts.user.to_account_info(),
};
let cpi_context = CpiContext::new(
    ctx.accounts.system_program.to_account_info(),
    transfer_accounts,
).with_signer(signer_seeds);
transfer(cpi_context, ctx.accounts.vault_account.lamports())?;

Note that we updated _ctx: Context<Delete> to ctx: Context<Delete> as we'll be using the context in the body of the function.

Rebuild the program.

Terminal
build

Redeploy Program #

After making these changes, we need to redeploy our updated program. This ensures that our modified program is available for testing. On Solana, updating a program simply requires deploying the compiled program at the same program ID.

Terminal
deploy

Update Test File #

Next, we'll update our anchor.test.ts file to include the new vault account in our instructions. This requires deriving the vault PDA and including it in our update and delete instruction calls.

Derive Vault PDA #

First, add the vault PDA derivation:

anchor.test.ts
const [vaultPda, vaultBump] = PublicKey.findProgramAddressSync(
  [Buffer.from("vault"), wallet.publicKey.toBuffer()],
  program.programId,
);

Modify Update Test #

Then, update the update instruction to include the vaultAccount.

anchor.test.ts
const transactionSignature = await program.methods
  .update(message)
  .accounts({
    messageAccount: messagePda,
    vaultAccount: vaultPda,
  })
  .rpc({ commitment: "confirmed" });

Modify Delete Test #

Then, update the delete instruction to include the vaultAccount.

anchor.test.ts
const transactionSignature = await program.methods
  .delete()
  .accounts({
    messageAccount: messagePda,
    vaultAccount: vaultPda,
  })
  .rpc({ commitment: "confirmed" });

Rerun Test #

After making these changes, run the tests to ensure everything is working as expected:

Terminal
test

You can then inspect the SolanFM links to view the transaction details, where you’ll find the CPIs for the transfer instructions within the update and delete instructions.

Update CPIUpdate CPI

Delete CPIDelete CPI

If you encounter any errors, you can reference the final code.

Next Steps #

You've completed the Solana Quickstart guide! You've learned about accounts, transactions, PDAs, CPIs, and deployed your own programs.

Visit the Core Concepts pages for more comprehensive explanations of the topics covered in this guide.

Additional learning resources can be found on the Developer Resources page.

Explore More Examples #

If you prefer learning by example, check out the Program Examples Repository for a variety of example programs.

Solana Playground offers a convenient feature allowing you to import or view projects using their GitHub links. For example, open this Solana Playground link to view the Anchor project from this Github repo.

Click the Import button and enter a project name to add it to your list of projects in Solana Playground. Once a project is imported, all changes are automatically saved and persisted within the Playground environment.