diff --git a/.gitignore b/.gitignore
index f582b758a..9e485924f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@ public/sitemap*
coverage
.env.local
+.source
diff --git a/content/authors.json b/content/authors.json
new file mode 100644
index 000000000..41df08b51
--- /dev/null
+++ b/content/authors.json
@@ -0,0 +1,10 @@
+[
+ {
+ "id": "unboxed",
+ "title": "Unboxed Software with updates from the Solana Foundation",
+ "github": "Unboxed-Software",
+ "twitter": "unboxedsoftware",
+ "website": "https://www.beunboxed.com/",
+ "image": "unboxed.png"
+ }
+]
diff --git a/content/cookbook/accounts/calculate-rent.mdx b/content/cookbook/accounts/calculate-rent.mdx
new file mode 100644
index 000000000..80ede2379
--- /dev/null
+++ b/content/cookbook/accounts/calculate-rent.mdx
@@ -0,0 +1,47 @@
+---
+title: How to Calculate Account Creation Cost
+description:
+ "Every time you create an account, that creation costs an amount of SOL. Learn
+ how to calculate how much an account costs at creation."
+---
+
+Keeping accounts alive on Solana incurs a storage cost called rent. For the
+calculation, you need to consider the amount of data you intend to store in the
+account. Rent can be reclaimed in full if the account is closed.
+
+
+
+
+
+```typescript title="calculate-rent.ts"
+import { createSolanaRpc } from "@solana/web3.js";
+
+const rpc = createSolanaRpc("https://api.devnet.solana.com");
+// 1.5k bytes
+const space = 1500n;
+
+const lamports = await rpc.getMinimumBalanceForRentExemption(space).send();
+console.log("Minimum balance for rent exception:", lamports);
+```
+
+
+
+
+
+```typescript
+import { Connection, clusterApiUrl } from "@solana/web3.js";
+
+const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+
+// length of data in bytes in the account to calculate rent for
+const dataLength = 1500;
+const rentExemptionAmount =
+ await connection.getMinimumBalanceForRentExemption(dataLength);
+console.log({
+ rentExemptionAmount,
+});
+```
+
+
+
+
diff --git a/content/cookbook/accounts/close-account.mdx b/content/cookbook/accounts/close-account.mdx
new file mode 100644
index 000000000..1171b4fa2
--- /dev/null
+++ b/content/cookbook/accounts/close-account.mdx
@@ -0,0 +1,44 @@
+---
+title: How to Close an Account
+description:
+ "When an account is no longer needed, you can close the account to reclaim the
+ rent. Learn how to close accounts efficiently on Solana."
+---
+
+Closing accounts enables you to reclaim the SOL that was used to open the
+account, but requires deleting of all information in the account. When an
+account is closed, make sure that the data is zeroed out in the same instruction
+to avoid people reopening the account in the same transaction and getting access
+to the data. This is because the account is not actually closed until the
+transaction is completed.
+
+```rust title="close-account.rs" {18-25}
+use solana_program::{
+ account_info::next_account_info, account_info::AccountInfo, entrypoint,
+ entrypoint::ProgramResult, pubkey::Pubkey,
+};
+
+entrypoint!(process_instruction);
+
+fn process_instruction(
+ _program_id: &Pubkey,
+ accounts: &[AccountInfo],
+ _instruction_data: &[u8],
+) -> ProgramResult {
+ let account_info_iter = &mut accounts.iter();
+
+ let source_account_info = next_account_info(account_info_iter)?;
+ let dest_account_info = next_account_info(account_info_iter)?;
+
+ let dest_starting_lamports = dest_account_info.lamports();
+ **dest_account_info.lamports.borrow_mut() = dest_starting_lamports
+ .checked_add(source_account_info.lamports())
+ .unwrap();
+ **source_account_info.lamports.borrow_mut() = 0;
+
+ source_account_info.assign(&system_program::ID);
+ source_account_info.realloc(0, false).map_err(Into::into)
+
+ Ok(())
+}
+```
diff --git a/content/cookbook/accounts/create-account.mdx b/content/cookbook/accounts/create-account.mdx
new file mode 100644
index 000000000..b846e38b8
--- /dev/null
+++ b/content/cookbook/accounts/create-account.mdx
@@ -0,0 +1,149 @@
+---
+title: How to Create an Account
+description:
+ "Accounts are the basic building blocks of anything on Solana. Learn how to
+ create accounts on the Solana blockchain."
+---
+
+Creating an account requires using the System Program `createAccount`
+instruction. The Solana runtime will grant the owner program of an account,
+access to write to its data or transfer lamports. When creating an account, we
+have to preallocate a fixed storage space in bytes (space) and enough lamports
+to cover the rent.
+
+
+
+
+
+```typescript title="create-account.ts"
+import {
+ pipe,
+ createSolanaRpc,
+ appendTransactionMessageInstructions,
+ createSolanaRpcSubscriptions,
+ createTransactionMessage,
+ generateKeyPairSigner,
+ getSignatureFromTransaction,
+ sendAndConfirmTransactionFactory,
+ setTransactionMessageFeePayerSigner,
+ setTransactionMessageLifetimeUsingBlockhash,
+ signTransactionMessageWithSigners,
+} from "@solana/web3.js";
+import { getSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";
+import {
+ getCreateAccountInstruction,
+ SYSTEM_PROGRAM_ADDRESS,
+} from "@solana-program/system";
+
+const rpc = createSolanaRpc("https://api.devnet.solana.com");
+const rpcSubscriptions = createSolanaRpcSubscriptions(
+ "wss://api.devnet.solana.com",
+);
+
+const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
+ rpc,
+ rpcSubscriptions,
+});
+
+const space = 0n; // any extra space in the account
+const rentLamports = await rpc.getMinimumBalanceForRentExemption(space).send();
+console.log("Minimum balance for rent exception:", rentLamports);
+
+// todo: load your own signer with SOL
+const signer = await generateKeyPairSigner();
+
+// generate a new keypair and address to create
+const newAccountKeypair = await generateKeyPairSigner();
+console.log("New account address:", newAccountKeypair.address);
+
+const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
+
+const transactionMessage = pipe(
+ createTransactionMessage({ version: "legacy" }),
+ tx => setTransactionMessageFeePayerSigner(signer, tx),
+ tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+ tx =>
+ appendTransactionMessageInstructions(
+ [
+ // add a priority fee
+ getSetComputeUnitPriceInstruction({
+ microLamports: 200_000,
+ }),
+ // create the new account
+ getCreateAccountInstruction({
+ lamports: rentLamports,
+ newAccount: newAccountKeypair,
+ payer: signer,
+ space: space,
+ // "wallet" accounts are owned by the system program
+ programAddress: SYSTEM_PROGRAM_ADDRESS,
+ }),
+ ],
+ tx,
+ ),
+);
+
+const signedTransaction =
+ await signTransactionMessageWithSigners(transactionMessage);
+const signature = getSignatureFromTransaction(signedTransaction);
+
+await sendAndConfirmTransaction(signedTransaction, {
+ commitment: "confirmed",
+});
+console.log("Signature:", signature);
+```
+
+
+
+
+
+```typescript title="create-account.ts"
+import {
+ SystemProgram,
+ Keypair,
+ Transaction,
+ sendAndConfirmTransaction,
+ Connection,
+ clusterApiUrl,
+ LAMPORTS_PER_SOL,
+} from "@solana/web3.js";
+
+const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+const fromPubkey = Keypair.generate();
+
+// Airdrop SOL for transferring lamports to the created account
+const airdropSignature = await connection.requestAirdrop(
+ fromPubkey.publicKey,
+ LAMPORTS_PER_SOL,
+);
+await connection.confirmTransaction(airdropSignature);
+
+// amount of space to reserve for the account
+const space = 0;
+
+// Seed the created account with lamports for rent exemption
+const rentExemptionAmount =
+ await connection.getMinimumBalanceForRentExemption(space);
+
+const newAccountPubkey = Keypair.generate();
+const createAccountParams = {
+ fromPubkey: fromPubkey.publicKey,
+ newAccountPubkey: newAccountPubkey.publicKey,
+ lamports: rentExemptionAmount,
+ space,
+ programId: SystemProgram.programId,
+};
+
+const createAccountTransaction = new Transaction().add(
+ SystemProgram.createAccount(createAccountParams),
+);
+
+await sendAndConfirmTransaction(connection, createAccountTransaction, [
+ fromPubkey,
+ newAccountPubkey,
+]);
+```
+
+
+
+
diff --git a/content/cookbook/accounts/create-pda-account.mdx b/content/cookbook/accounts/create-pda-account.mdx
new file mode 100644
index 000000000..7b9f0d8e4
--- /dev/null
+++ b/content/cookbook/accounts/create-pda-account.mdx
@@ -0,0 +1,151 @@
+---
+title: How to Create a PDA's Account
+description:
+ "Program Derived Addresses, also known as PDAs, enable developers to extend
+ their program's functionality with program-owned accounts. Learn how to create
+ accounts at PDAs on Solana."
+---
+
+Accounts found at Program Derived Addresses (PDAs) can only be created on-chain.
+The accounts have addresses that have an associated off-curve public key, but no
+secret key.
+
+To generate a PDA, use `findProgramAddressSync` with your required seeds.
+Generating with the same seeds will always generate the same PDA.
+
+## Generating a PDA
+
+```typescript title="generate-pda.ts"
+import { PublicKey } from "@solana/web3.js";
+
+const programId = new PublicKey("G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj");
+
+let [pda, bump] = PublicKey.findProgramAddressSync(
+ [Buffer.from("test")],
+ programId,
+);
+console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);
+// you will find the result is different from `createProgramAddress`.
+// It is expected because the real seed we used to calculate is ["test" + bump]
+```
+
+## Create an Account at a PDA
+
+### Program
+
+```rust title="create-pda.rs" {24-37}
+use solana_program::{
+ account_info::next_account_info, account_info::AccountInfo, entrypoint,
+ entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, system_instruction, sysvar::{rent::Rent, Sysvar}
+};
+
+entrypoint!(process_instruction);
+
+fn process_instruction(
+ program_id: &Pubkey,
+ accounts: &[AccountInfo],
+ instruction_data: &[u8],
+) -> ProgramResult {
+ let account_info_iter = &mut accounts.iter();
+
+ let payer_account_info = next_account_info(account_info_iter)?;
+ let pda_account_info = next_account_info(account_info_iter)?;
+ let rent_sysvar_account_info = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
+
+ // find space and minimum rent required for account
+ let space = instruction_data[0];
+ let bump = instruction_data[1];
+ let rent_lamports = rent_sysvar_account_info.minimum_balance(space.into());
+
+ invoke_signed(
+ &system_instruction::create_account(
+ &payer_account_info.key,
+ &pda_account_info.key,
+ rent_lamports,
+ space.into(),
+ program_id
+ ),
+ &[
+ payer_account_info.clone(),
+ pda_account_info.clone()
+ ],
+ &[&[&payer_account_info.key.as_ref(), &[bump]]]
+ )?;
+
+ Ok(())
+}
+```
+
+## Client
+
+```typescript title="create-pda.ts"
+import {
+ clusterApiUrl,
+ Connection,
+ Keypair,
+ Transaction,
+ SystemProgram,
+ PublicKey,
+ TransactionInstruction,
+ LAMPORTS_PER_SOL,
+ SYSVAR_RENT_PUBKEY,
+} from "@solana/web3.js";
+
+(async () => {
+ // program id
+ const programId = new PublicKey(
+ "7ZP42kRwUQ2zgbqXoaXzAFaiQnDyp6swNktTSv8mNQGN",
+ );
+
+ // connection
+ const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+
+ // setup fee payer
+ const feePayer = Keypair.generate();
+ const feePayerAirdropSignature = await connection.requestAirdrop(
+ feePayer.publicKey,
+ LAMPORTS_PER_SOL,
+ );
+ await connection.confirmTransaction(feePayerAirdropSignature);
+
+ // setup pda
+ let [pda, bump] = await PublicKey.findProgramAddress(
+ [feePayer.publicKey.toBuffer()],
+ programId,
+ );
+ console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);
+
+ const data_size = 0;
+
+ let tx = new Transaction().add(
+ new TransactionInstruction({
+ keys: [
+ {
+ pubkey: feePayer.publicKey,
+ isSigner: true,
+ isWritable: true,
+ },
+ {
+ pubkey: pda,
+ isSigner: false,
+ isWritable: true,
+ },
+ {
+ pubkey: SYSVAR_RENT_PUBKEY,
+ isSigner: false,
+ isWritable: false,
+ },
+ {
+ pubkey: SystemProgram.programId,
+ isSigner: false,
+ isWritable: false,
+ },
+ ],
+ data: Buffer.from(new Uint8Array([data_size, bump])),
+ programId: programId,
+ }),
+ );
+
+ console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`);
+})();
+```
diff --git a/content/cookbook/accounts/get-account-balance.mdx b/content/cookbook/accounts/get-account-balance.mdx
new file mode 100644
index 000000000..7f13fd3bb
--- /dev/null
+++ b/content/cookbook/accounts/get-account-balance.mdx
@@ -0,0 +1,50 @@
+---
+title: How to Get Account Balance
+description:
+ "Every account on Solana has a balance of SOL stored. Learn how to retrieve
+ that account balance on Solana."
+---
+
+
+
+
+
+```typescript title="get-account-balance.ts"
+import { address, createSolanaRpc } from "@solana/web3.js";
+
+const rpc = createSolanaRpc("https://api.devnet.solana.com");
+const LAMPORTS_PER_SOL = 1_000_000_000; // 1 billion lamports per SOL
+
+const wallet = address("nicktrLHhYzLmoVbuZQzHUTicd2sfP571orwo9jfc8c");
+const { value: balance } = await rpc.getBalance(wallet).send();
+console.log(`Balance: ${Number(balance) / LAMPORTS_PER_SOL} SOL`);
+```
+
+> As of `v2.0.0`, developers can use the default configurations within the main
+> library (`@solana/web3.js`) or import any of its subpackages where better
+> composition or more granular control over the imports is desired. See
+> [Tree-Shakability](https://github.com/solana-labs/solana-web3.js?tab=readme-ov-file#tree-shakability)
+> for more information.
+
+
+
+
+
+```typescript title="get-account-balance.ts"
+import {
+ clusterApiUrl,
+ Connection,
+ PublicKey,
+ LAMPORTS_PER_SOL,
+} from "@solana/web3.js";
+
+const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+const wallet = new PublicKey("nicktrLHhYzLmoVbuZQzHUTicd2sfP571orwo9jfc8c");
+
+const balance = await connection.getBalance(wallet);
+console.log(`Balance: ${balance / LAMPORTS_PER_SOL} SOL`);
+```
+
+
+
+
diff --git a/content/cookbook/accounts/meta.json b/content/cookbook/accounts/meta.json
new file mode 100644
index 000000000..afbed64bc
--- /dev/null
+++ b/content/cookbook/accounts/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "Accounts",
+ "pages": [
+ "create-account",
+ "calculate-rent",
+ "create-pda-account",
+ "sign-with-pda",
+ "close-account",
+ "get-account-balance"
+ ],
+ "defaultOpen": true
+}
diff --git a/content/cookbook/accounts/sign-with-pda.mdx b/content/cookbook/accounts/sign-with-pda.mdx
new file mode 100644
index 000000000..665d1e0f4
--- /dev/null
+++ b/content/cookbook/accounts/sign-with-pda.mdx
@@ -0,0 +1,52 @@
+---
+title: How to Sign with a PDA's Account
+description:
+ "A main feature of accounts at Program Derived Addresses is the ability for
+ programs to sign using those accounts. Learn how to sign with PDA accounts on
+ Solana."
+---
+
+Program derived addresses (PDA) can be used to have accounts owned by programs
+that can sign. This is useful if you want a program to own a token account and
+you want the program to transfer tokens from one account to another.
+
+```rust title="sign-with-pda.rs" {22-34}
+use solana_program::{
+ account_info::next_account_info, account_info::AccountInfo, entrypoint,
+ entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, system_instruction,
+};
+
+entrypoint!(process_instruction);
+
+fn process_instruction(
+ _program_id: &Pubkey,
+ accounts: &[AccountInfo],
+ instruction_data: &[u8],
+) -> ProgramResult {
+ let account_info_iter = &mut accounts.iter();
+
+ let pda_account_info = next_account_info(account_info_iter)?;
+ let to_account_info = next_account_info(account_info_iter)?;
+ let system_program_account_info = next_account_info(account_info_iter)?;
+
+ // pass bump seed for saving compute budget
+ let bump_seed = instruction_data[0];
+
+ invoke_signed(
+ &system_instruction::transfer(
+ &pda_account_info.key,
+ &to_account_info.key,
+ 100_000_000, // 0.1 SOL
+ ),
+ &[
+ pda_account_info.clone(),
+ to_account_info.clone(),
+ system_program_account_info.clone(),
+ ],
+ &[&[b"escrow", &[bump_seed]]],
+ )?;
+
+ Ok(())
+}
+
+```
diff --git a/content/cookbook/development/connect-environment.mdx b/content/cookbook/development/connect-environment.mdx
new file mode 100644
index 000000000..0a36b8dbf
--- /dev/null
+++ b/content/cookbook/development/connect-environment.mdx
@@ -0,0 +1,31 @@
+---
+title: Connecting to a Solana Environment
+description: "Learn how to connect to a Solana environment."
+---
+
+When you are working on Solana development, you will need to connect to a
+specific RPC API endpoint. Solana has 3 public development environments:
+
+- mainnet-beta https://api.mainnet-beta.solana.com
+- devnet https://api.devnet.solana.com
+- testnet https://api.testnet.solana.com
+
+```typescript title="connect-to-environment.ts"
+import { clusterApiUrl, Connection } from "@solana/web3.js";
+
+(async () => {
+ const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
+})();
+```
+
+Finally, you can also connect to a private cluster, either one local or running
+remotely with the following:
+
+```ts
+import { Connection } from "@solana/web3.js";
+
+(async () => {
+ // This will connect you to your local validator
+ const connection = new Connection("http://127.0.0.1:8899", "confirmed");
+})();
+```
diff --git a/content/cookbook/development/load-keypair-from-file.mdx b/content/cookbook/development/load-keypair-from-file.mdx
new file mode 100644
index 000000000..839b140d3
--- /dev/null
+++ b/content/cookbook/development/load-keypair-from-file.mdx
@@ -0,0 +1,85 @@
+---
+title: Load a local json file keypair
+description: "Learn how to load a keypair from file."
+---
+
+When running your local project you probably want to use a file json keypair.
+This can be very useful for all the cookbook examples as well. You can grind
+yourself a keypair using `solana-keygen grind --starts-with a23:1` and then load
+and use this one for your projects using the `loadKeypairFromFile` function.
+
+```typescript title="load-keypair-from-file.ts"
+import {
+ airdropFactory,
+ createKeyPairSignerFromBytes,
+ createSolanaRpc,
+ createSolanaRpcSubscriptions,
+ devnet,
+ generateKeyPair,
+ getAddressFromPublicKey,
+ KeyPairSigner,
+ lamports,
+} from "@solana/web3.js";
+import fs from "fs";
+import path from "path";
+import os from "os";
+
+// The new library takes a brand-new approach to Solana key pairs and addresses,
+// which will feel quite different from the classes PublicKey and Keypair from version 1.x.
+// All key operations now use the native Ed25519 implementation in JavaScript’s
+// Web Crypto API.
+async function createKeypair() {
+ const newKeypair: CryptoKeyPair = await generateKeyPair();
+ const publicAddress = await getAddressFromPublicKey(newKeypair.publicKey);
+
+ console.log(`Public key: ${publicAddress}`);
+}
+
+export async function loadDefaultKeypair(): Promise> {
+ return await loadKeypairFromFile("~/.config/solana/id.json");
+}
+
+export async function loadDefaultKeypairWithAirdrop(
+ cluster: string,
+): Promise> {
+ const keypair = await loadDefaultKeypair();
+ const rpc = createSolanaRpc(devnet(`https://api.${cluster}.solana.com`));
+ const rpcSubscriptions = createSolanaRpcSubscriptions(
+ devnet(`wss://api.${cluster}.solana.com`),
+ );
+ try {
+ const result = await rpc.getBalance(keypair.address).send();
+
+ console.log(`Balance: ${result.value} lamports`);
+ if (result.value < lamports(500_000n)) {
+ console.log(`Balance low requesting airdrop`);
+ const airdrop = airdropFactory({ rpc, rpcSubscriptions });
+ await airdrop({
+ commitment: "confirmed",
+ lamports: lamports(1_000_000_000n),
+ recipientAddress: keypair.address,
+ });
+ }
+ } catch (err) {
+ console.error("Error fetching balance:", err);
+ }
+ return keypair;
+}
+
+export async function loadKeypairFromFile(
+ filePath: string,
+): Promise> {
+ // This is here so you can also load the default keypair from the file system.
+ const resolvedPath = path.resolve(
+ filePath.startsWith("~") ? filePath.replace("~", os.homedir()) : filePath,
+ );
+ const loadedKeyBytes = Uint8Array.from(
+ JSON.parse(fs.readFileSync(resolvedPath, "utf8")),
+ );
+ // Here you can also set the second parameter to true in case you need to extract your private key.
+ const keypairSigner = await createKeyPairSignerFromBytes(loadedKeyBytes);
+ return keypairSigner;
+}
+
+createKeypair();
+```
diff --git a/content/cookbook/development/meta.json b/content/cookbook/development/meta.json
new file mode 100644
index 000000000..1d7c97830
--- /dev/null
+++ b/content/cookbook/development/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "Development",
+ "pages": [
+ "start-local-validator",
+ "connect-environment",
+ "test-sol",
+ "subscribing-events",
+ "using-mainnet-accounts-programs",
+ "load-keypair-from-file"
+ ],
+ "defaultOpen": true
+}
diff --git a/content/cookbook/development/start-local-validator.mdx b/content/cookbook/development/start-local-validator.mdx
new file mode 100644
index 000000000..b098b2144
--- /dev/null
+++ b/content/cookbook/development/start-local-validator.mdx
@@ -0,0 +1,25 @@
+---
+title: How to Start a Local Validator
+description: "Learn how to start a local solana validator."
+---
+
+Testing your program code locally can be a lot more reliable than testing on
+devnet, and can help you test before trying it out on devnet.
+
+You can setup your local-test-validator by installing the
+[Solana CLI tool suite](/docs/intro/installation) and running the following
+command:
+
+```shell
+solana-test-validator
+```
+
+Benefits of using local-test-validator include:
+
+- No RPC rate-limits
+- No airdrop limits
+- Direct onchain program deployment (`--bpf-program ...`)
+- Clone accounts from a public cluster, including programs (`--clone ...`)
+- Configurable transaction history retention (`--limit-ledger-size ...`)
+- Configurable epoch length (`--slots-per-epoch ...`)
+- Jump to an arbitrary slot (`--warp-slot ...`)
diff --git a/content/cookbook/development/subscribing-events.mdx b/content/cookbook/development/subscribing-events.mdx
new file mode 100644
index 000000000..a9b1094bc
--- /dev/null
+++ b/content/cookbook/development/subscribing-events.mdx
@@ -0,0 +1,43 @@
+---
+title: Subscribing to Events
+description: Learn how to subscribe to events in the Solana network.
+---
+
+Websockets provide a pub/sub interface where you can listen for certain events.
+Instead of pinging a typical HTTP endpoint at an interval to get frequent
+updates, you can instead receive those updates only when they happen.
+
+Solana's web3
+[`Connection`](https://solana-labs.github.io/solana-web3.js/v1.x/classes/Connection.html)
+under the hood generates a websocket endpoint and registers a websocket client
+when you create a new `Connection` instance (see source code
+[here](https://github.com/solana-labs/solana-web3.js/blob/45923ca00e4cc1ed079d8e55ecbee83e5b4dc174/src/connection.ts#L2100)).
+
+The `Connection` class exposes pub/sub methods - they all start with `on`, like
+event emitters. When you call these listener methods, it registers a new
+subscription to the websocket client of that `Connection` instance. The example
+pub/sub method we use below is
+[`onAccountChange`](https://solana-labs.github.io/solana-web3.js/v1.x/classes/Connection.html#onAccountChange).
+The callback will provide the updated state data through arguments (see
+[`AccountChangeCallback`](https://solana-labs.github.io/solana-web3.js/v1.x/types/AccountChangeCallback.html)
+as an example).
+
+```typescript title="subscribe-to-events.ts"
+import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js";
+
+(async () => {
+ // Establish new connect to devnet - websocket client connected to devnet will also be registered here
+ const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+
+ // Create a test wallet to listen to
+ const wallet = Keypair.generate();
+
+ // Register a callback to listen to the wallet (ws subscription)
+ connection.onAccountChange(
+ wallet.publicKey,
+ (updatedAccountInfo, context) =>
+ console.log("Updated account info: ", updatedAccountInfo),
+ "confirmed",
+ );
+})();
+```
diff --git a/content/cookbook/development/test-sol.mdx b/content/cookbook/development/test-sol.mdx
new file mode 100644
index 000000000..27945c725
--- /dev/null
+++ b/content/cookbook/development/test-sol.mdx
@@ -0,0 +1,29 @@
+---
+title: Getting Test SOL
+description: Learn how to get test SOL for development purposes.
+---
+
+When you're working locally, you need some SOL in order to send transactions. In
+non-mainnet environments you can receive SOL by airdropping it to your address
+
+```typescript title="get-test-sol.ts"
+import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
+
+(async () => {
+ const keypair = Keypair.generate();
+
+ const connection = new Connection("http://127.0.0.1:8899", "confirmed");
+
+ const signature = await connection.requestAirdrop(
+ keypair.publicKey,
+ LAMPORTS_PER_SOL,
+ );
+ const { blockhash, lastValidBlockHeight } =
+ await connection.getLatestBlockhash();
+ await connection.confirmTransaction({
+ blockhash,
+ lastValidBlockHeight,
+ signature,
+ });
+})();
+```
diff --git a/content/cookbook/development/using-mainnet-accounts-programs.mdx b/content/cookbook/development/using-mainnet-accounts-programs.mdx
new file mode 100644
index 000000000..19d030b22
--- /dev/null
+++ b/content/cookbook/development/using-mainnet-accounts-programs.mdx
@@ -0,0 +1,45 @@
+---
+title: Using Mainnet Accounts and Programs
+description:
+ Learn how to use the Mainnet accounts and programs in your local development
+ environment.
+---
+
+Oftentimes, local tests rely on programs and accounts that are not available on
+the local validator by default.
+The Solana CLI allows to both:
+
+- Download Programs and Accounts
+- Load Programs and Accounts to a local validator
+
+### How to load accounts from mainnet
+
+It is possible to download the JUP token mint account to file:
+
+```shell
+# solana account -u