Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
feat: cookbook (#169)
Browse files Browse the repository at this point in the history
* feat: cookbook

* prettier

* feat: added cookbook to crowdin

* Update content/cookbook/index.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/index.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/nfts/index.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/index.md

Co-authored-by: Nick Frostbutter <[email protected]>

* fix: prettier

* fix: prettier

* cookbook material

* added seo descriptions to cookbook pages

* refactor: editorial changes

* Update content/cookbook/wallets/generate-mnemonic.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/wallets/sign-message.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/add-priority-fees.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/add-memo.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/calculate-cost.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/calculate-cost.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/optimize-compute.md

Co-authored-by: Nick Frostbutter <[email protected]>

* Update content/cookbook/transactions/add-memo.md

Co-authored-by: Nick Frostbutter <[email protected]>

* cookbook feedback fixes

* fix: links

---------

Co-authored-by: nickfrosty <[email protected]>
  • Loading branch information
jacobcreech and nickfrosty authored Apr 25, 2024
1 parent 632086a commit 0736916
Show file tree
Hide file tree
Showing 29 changed files with 1,189 additions and 1 deletion.
27 changes: 27 additions & 0 deletions content/cookbook/accounts/calculate-rent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: How to Calculate Account Creation Cost
sidebarSortOrder: 2
description:
"Every time you create an account, that creation costs a small 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 filename="calculate-rent.ts"
import { Connection, clusterApiUrl } from "@solana/web3.js";

(async () => {
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,
});
})();
```
45 changes: 45 additions & 0 deletions content/cookbook/accounts/close-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: How to Close an Account
sidebarSortOrder: 5
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 filename="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;

let mut source_data = source_account_info.data.borrow_mut();
source_data.fill(0);

Ok(())
}
```
62 changes: 62 additions & 0 deletions content/cookbook/accounts/create-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: How to Create an Account
sidebarSortOrder: 1
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 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 filename="create-account.ts"
import {
SystemProgram,
Keypair,
Transaction,
sendAndConfirmTransaction,
Connection,
clusterApiUrl,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";

(async () => {
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,
]);
})();
```
152 changes: 152 additions & 0 deletions content/cookbook/accounts/create-pda-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
title: How to Create a PDA's Account
sidebarSortOrder: 3
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 filename="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 filename="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 filename="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])}`);
})();
```
25 changes: 25 additions & 0 deletions content/cookbook/accounts/get-account-balance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: How to Get Account Balance
sidebarSortOrder: 6
description:
"Every account on Solana has a balance of SOL stored. Learn how to retrieve
that account balance on Solana."
---

```typescript filename="get-account-balance.ts" {13}
import {
clusterApiUrl,
Connection,
PublicKey,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";

(async () => {
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

let wallet = new PublicKey("G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY");
console.log(
`${(await connection.getBalance(wallet)) / LAMPORTS_PER_SOL} SOL`,
);
})();
```
5 changes: 5 additions & 0 deletions content/cookbook/accounts/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
metaOnly: true
title: Accounts
sidebarSortOrder: 3
---
53 changes: 53 additions & 0 deletions content/cookbook/accounts/sign-with-pda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: How to Sign with a PDA's Account
sidebarSortOrder: 4
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 filename="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(())
}

```
13 changes: 13 additions & 0 deletions content/cookbook/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
sidebarSortOrder: 0
title: Solana Cookbook
seoTitle: Code examples for Solana development
description:
"The Solana cookbook is a collection of useful examples and references for
building on Solana"
---

The _Solana Cookbook_ is a developer resource that provides examples and
references for building applications on Solana. Each example and reference will
focus on specific aspects of Solana development while providing additional
details and usage examples.
Loading

0 comments on commit 0736916

Please sign in to comment.