diff --git a/docs/.env.example b/docs/.env.example deleted file mode 100644 index 71d53c5c13..0000000000 --- a/docs/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -NEXT_PUBLIC_DOCSEARCH_APP_ID= -NEXT_PUBLIC_DOCSEARCH_API_KEY= -NEXT_PUBLIC_DOCSEARCH_INDEX_NAME= diff --git a/docs/.eslintrc.json b/docs/.eslintrc.json deleted file mode 100644 index bffb357a71..0000000000 --- a/docs/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/docs/.gitignore b/docs/.gitignore index 4db8cdcd07..55a12ae71d 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,35 +1,28 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies +# deps /node_modules -/.pnp -.pnp.js -# testing -/coverage +# generated content +.contentlayer +.content-collections +.source -# next.js +# test & build +/coverage /.next/ /out/ - -# production /build +*.tsbuildinfo # misc .DS_Store *.pem - -# debug +/.pnp +.pnp.js npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* -# local env files +# others .env*.local - -# vercel .vercel - -.vscode/ -.idea \ No newline at end of file +next-env.d.ts \ No newline at end of file diff --git a/docs/.prettierrc b/docs/.prettierrc new file mode 100644 index 0000000000..d2f009f9d5 --- /dev/null +++ b/docs/.prettierrc @@ -0,0 +1,10 @@ +{ + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "printWidth": 80, + "trailingComma": "all", + "arrowParens": "avoid", + "endOfLine": "auto", + "proseWrap": "always" +} diff --git a/docs/README.md b/docs/README.md index 351378f663..0566d41e7d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,16 @@ # Anchor Docs -## Getting started +Created with [Fumadocs](https://fumadocs.vercel.app). Page contents located in +[`docs/content/docs`](/docs/content/docs). -```bash -npm install -cp .env.example .env.local -``` - -Next, run the development server: +Run development server: ```bash npm run dev +# or +pnpm dev +# or +yarn dev ``` -Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website. - -## Customizing - -You can start editing this template by modifying the files in the `/src` folder. The site will auto-update as you edit these files. +Open http://localhost:3000 with your browser to see the result. diff --git a/docs/app/(home)/layout.tsx b/docs/app/(home)/layout.tsx new file mode 100644 index 0000000000..608a58b1d4 --- /dev/null +++ b/docs/app/(home)/layout.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from 'react'; +import { HomeLayout } from 'fumadocs-ui/layouts/home'; +import { baseOptions } from '@/app/layout.config'; + +export default function Layout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return {children}; +} diff --git a/docs/app/(home)/page.tsx b/docs/app/(home)/page.tsx new file mode 100644 index 0000000000..f58825d8d9 --- /dev/null +++ b/docs/app/(home)/page.tsx @@ -0,0 +1,19 @@ +import Link from "next/link"; + +export default function HomePage() { + return ( +
+

Hello World

+

+ You can open{" "} + + /docs + {" "} + and see the documentation. +

+
+ ); +} diff --git a/docs/app/api/search/route.ts b/docs/app/api/search/route.ts new file mode 100644 index 0000000000..6818340f7c --- /dev/null +++ b/docs/app/api/search/route.ts @@ -0,0 +1,4 @@ +import { docsSource } from '@/app/source'; +import { createFromSource } from 'fumadocs-core/search/server'; + +export const { GET } = createFromSource(docsSource); diff --git a/docs/app/docs/[[...slug]]/page.tsx b/docs/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000000..81b4b202a1 --- /dev/null +++ b/docs/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,93 @@ +import { docsSource as source } from "@/app/source"; +import { + DocsPage, + DocsBody, + DocsDescription, + DocsTitle, + DocsCategory, +} from "fumadocs-ui/page"; +import { notFound } from "next/navigation"; +import defaultMdxComponents from "fumadocs-ui/mdx"; +import { ImageZoom } from "fumadocs-ui/components/image-zoom"; +import { Accordion, Accordions } from "fumadocs-ui/components/accordion"; +import { Step, Steps } from "fumadocs-ui/components/steps"; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; +import { Callout } from "fumadocs-ui/components/callout"; +import { TypeTable } from "fumadocs-ui/components/type-table"; +import { Files, Folder, File } from "fumadocs-ui/components/files"; +import GithubIcon from "@/public/icons/github.svg"; + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + const MDX = page.data.body; + + return ( + item.depth <= 3)} + full={page.data.full} + tableOfContent={{ footer: }} + > + {page.data.title} + {page.data.description} + + , + Accordion, + Accordions, + Step, + Steps, + Tab, + Tabs, + Callout, + TypeTable, + Files, + Folder, + File, + }} + /> + {page.data.index ? : null} + + + ); +} + +function EditOnGithub({ path }: { path: string }) { + // placeholder + const href = `https://github.com/coral-xyz/anchor/blob/master/docs/content/docs/${path.startsWith("/") ? path.slice(1) : path}`; + return ( + + + Edit on GitHub + + ); +} + +export async function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + }; +} diff --git a/docs/app/docs/layout.tsx b/docs/app/docs/layout.tsx new file mode 100644 index 0000000000..33f3444a70 --- /dev/null +++ b/docs/app/docs/layout.tsx @@ -0,0 +1,7 @@ +import { DocsLayout } from "fumadocs-ui/layouts/notebook"; +import type { ReactNode } from "react"; +import { docsOptions } from "../layout.config"; + +export default function Layout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/docs/app/global.css b/docs/app/global.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/docs/app/global.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/docs/public/favicon.ico b/docs/app/icon.ico similarity index 100% rename from docs/public/favicon.ico rename to docs/app/icon.ico diff --git a/docs/app/layout.config.tsx b/docs/app/layout.config.tsx new file mode 100644 index 0000000000..197330c7c3 --- /dev/null +++ b/docs/app/layout.config.tsx @@ -0,0 +1,54 @@ +import { type DocsLayoutProps } from "fumadocs-ui/layouts/notebook"; +import { type HomeLayoutProps } from "fumadocs-ui/layouts/home"; +import { docsSource } from "./source"; +import StackExchangeIcon from "@/public/icons/stackexchange.svg"; +import GithubIcon from "@/public/icons/github.svg"; +import DiscordIcon from "@/public/icons/discord.svg"; +import Image from "next/image"; + +/** + * Shared layout configurations + * + * you can configure layouts individually from: + * Home Layout: app/(home)/layout.tsx + * Docs Layout: app/docs/layout.tsx + */ +export const baseOptions: HomeLayoutProps = { + nav: { + title: ( +
+ Logo + Anchor Docs +
+ ), + url: "/docs", + }, + links: [ + { + icon: , + text: "Github", + url: "https://github.com/coral-xyz/anchor", + active: "none", + }, + { + icon: , + text: "Discord", + url: "https://discord.com/invite/NHHGSXAnXk", + active: "none", + }, + { + icon: , + text: "Stack Exchange", + url: "https://solana.stackexchange.com/", + active: "none", + }, + ], +}; + +export const docsOptions: DocsLayoutProps = { + ...baseOptions, + sidebar: { + defaultOpenLevel: 1, + }, + tree: docsSource.pageTree, +}; diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx new file mode 100644 index 0000000000..934fe2f21e --- /dev/null +++ b/docs/app/layout.tsx @@ -0,0 +1,19 @@ +import "./global.css"; +import { RootProvider } from "fumadocs-ui/provider"; +import { Inter } from "next/font/google"; +import type { ReactNode } from "react"; +import { GoogleAnalytics } from "@next/third-parties/google"; +const inter = Inter({ + subsets: ["latin"], +}); + +export default function Layout({ children }: { children: ReactNode }) { + return ( + + + {children} + + + + ); +} diff --git a/docs/app/source.ts b/docs/app/source.ts new file mode 100644 index 0000000000..882708be6b --- /dev/null +++ b/docs/app/source.ts @@ -0,0 +1,8 @@ +import { docs, meta } from "@/.source"; +import { createMDXSource } from "fumadocs-mdx"; +import { loader } from "fumadocs-core/source"; + +export const docsSource = loader({ + baseUrl: "/docs", + source: createMDXSource(docs, meta), +}); diff --git a/docs/content/docs/basics/cpi.mdx b/docs/content/docs/basics/cpi.mdx new file mode 100644 index 0000000000..fbb8e374af --- /dev/null +++ b/docs/content/docs/basics/cpi.mdx @@ -0,0 +1,586 @@ +--- +title: Cross Program Invocation +description: + Learn how to implement Cross Program Invocations (CPIs) in Anchor programs to + enable composability between different Solana programs. +--- + +Cross Program Invocations (CPI) refer to the process of one program invoking +instructions of another program, which enables the composibility of Solana +programs. + +This section will cover the basics of implementing CPIs in an Anchor program, +using a simple SOL transfer instruction as a practical example. Once you +understand the basics of how to implement a CPI, you can apply the same concepts +for any instruction. + +## Cross Program Invocations + +Let's examine a program that implements a CPI to the System Program's transfer +instruction. Here is the example program on +[Solana Playground](https://beta.solpg.io/66df2751cffcf4b13384d35a). + +The `lib.rs` file includes a single `sol_transfer` instruction. When the +`sol_transfer` instruction on the Anchor program is invoked, the program +internally invokes the transfer instruction of the System Program. + +```rust title="lib.rs" +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; + +declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi"); + +#[program] +pub mod cpi { + use super::*; + + // [!code word:sol_transfer] + // [!code highlight] + pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ); + // [!code word:transfer] + // [!code highlight] + transfer(cpi_context, amount)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account(mut)] + sender: Signer<'info>, + #[account(mut)] + recipient: SystemAccount<'info>, + system_program: Program<'info, System>, +} +``` + +The `cpi.test.ts` file shows how to invoke the Anchor program's `sol_transfer` +instruction and logs a link to the transaction details on SolanaFM. + +```ts title="cpi.test.ts" +it("SOL Transfer Anchor", async () => { + const transactionSignature = await program.methods + .solTransfer(new BN(transferAmount)) + .accounts({ + sender: sender.publicKey, + recipient: recipient.publicKey, + }) + .rpc(); + + console.log( + `\nTransaction Signature:` + + `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +You can build, deploy, and run the test for this example on Playground to view +the transaction details on the [SolanaFM explorer](https://solana.fm/). + +The transaction details will show that the Anchor program was first invoked +(instruction 1), which then invokes the System Program (instruction 1.1), +resulting in a successful SOL transfer. + +![Transaction Details](/docs/core/cpi/transaction-details.png) + +### Example Explanation + +Cross Program Invocations (CPIs) allow one program to invoke instructions on +another program. The process of implementing a CPI is the same as that of +creating a instruction where you must specify: + +1. The program ID of the program being called +2. The accounts required by the instruction +3. Any instruction data required as arguments + +This pattern ensures the CPI has all the information needed to invoke the target +program's instruction. + +The System Program's transfer instruction requires two accounts: + +- `from`: The account sending SOL. +- `to`: The account receiving SOL. + +In the example program, the `SolTransfer` struct specifies the accounts required +by the transfer instruction. The System Program is also included because the CPI +invokes the System Program. + +```rust +// [!code word:sender] +// [!code word:recipient] +// [!code word:system_program] +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account(mut)] + sender: Signer<'info>, // from account + #[account(mut)] + recipient: SystemAccount<'info>, // to account + system_program: Program<'info, System>, // program ID +} +``` + +The following tabs present three approaches to implementing Cross Program +Invocations (CPIs), each at a different level of abstraction. All examples are +functionally equivalent. The main purpose is to illustrate the implementation +details of a CPI. + + + + +The `sol_transfer` instruction included in the example code shows a typical +approach for constructing CPIs using the Anchor framework. + +This approach involves creating a +[`CpiContext`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/context.rs#L171), +which includes the `program_id` and accounts required for the instruction being +called. The `CpiContext` is then passed to an Anchor helper function +([`transfer`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/system_program.rs#L298)) +to invoke a specific instruction. + +```rust +use anchor_lang::system_program::{transfer, Transfer}; +``` + +```rust +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + // [!code word:cpi_context] + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ); + + // [!code highlight] + transfer(cpi_context, amount)?; + Ok(()) +} +``` + +The `cpi_context` variable specifies the program ID (System Program) and +accounts (sender and recipient) required by the transfer instruction. + +```rust /program_id/ /from_pubkey/ /to_pubkey/ +let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, +); +``` + +The `cpi_context` and `amount` are then passed into the `transfer` function to +execute the CPI invoking the transfer instruction of the System Program. + +```rust +transfer(cpi_context, amount)?; +``` + + + + +This example shows a different approach to implementing a CPI using the `invoke` +function and +[`system_instruction::transfer`](https://github.com/anza-xyz/agave/blob/v1.18.26/sdk/program/src/system_instruction.rs#L881-L891), +which is generally seen in native Rust programs. + +Under the hood, the previous example is an abstraction of this implementation. +The example below is functionally equivalent to the previous example. + +```rust +use anchor_lang::solana_program::{program::invoke, system_instruction}; +``` + +```rust +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + // [!code word:instruction:1] + let instruction = + &system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount); + + // [!code highlight] + // [!code word:instruction:1] + invoke(instruction, &[from_pubkey, to_pubkey, program_id])?; + Ok(()) +} +``` + + + + +You can also manually build the instruction to pass into the `invoke()` +function. This is useful when there is no crate available to help build the +instruction you want to invoke. This approach requires you to specify the +`AccountMeta`s for the instruction and correctly create the instruction data +buffer. + +The `sol_transfer` instruction below is a manual implementation of a CPI to the +System Program's transfer instruction. + +```rust +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + // Prepare instruction AccountMetas + let account_metas = vec![ + AccountMeta::new(from_pubkey.key(), true), + AccountMeta::new(to_pubkey.key(), false), + ]; + + // SOL transfer instruction discriminator + let instruction_discriminator: u32 = 2; + + // Prepare instruction data + let mut instruction_data = Vec::with_capacity(4 + 8); + instruction_data.extend_from_slice(&instruction_discriminator.to_le_bytes()); + instruction_data.extend_from_slice(&amount.to_le_bytes()); + + // Create instruction + // [!code word:instruction:1] + let instruction = Instruction { + program_id: program_id.key(), + accounts: account_metas, + data: instruction_data, + }; + + // Invoke instruction + // [!code word:instruction:1] + // [!code highlight] + invoke(&instruction, &[from_pubkey, to_pubkey, program_id])?; + Ok(()) +} +``` + +When building an instruction in Rust, use the following syntax to specify the +`AccountMeta` for each account: + +```rust +AccountMeta::new(account1_pubkey, true), // writable, signer +AccountMeta::new(account2_pubkey, false), // writable, not signer +AccountMeta::new_readonly(account3_pubkey, false), // not writable, not signer +AccountMeta::new_readonly(account4_pubkey, true), // writable, signer +``` + + + + +Here is a reference program on +[Solana Playground](https://beta.solpg.io/github.com/ZYJLiu/doc-examples/tree/main/cpi) +which includes all 3 examples. + +## Cross Program Invocations with PDA Signers + +Next, let's examine a program that implements a CPI to the System Program's +transfer instruction where the sender is a Program Derived Address (PDA) that +must be "signed" for by the program. Here is the example program on +[Solana Playground](https://beta.solpg.io/66df2bd2cffcf4b13384d35b). + +The `lib.rs` file includes the following program with a single `sol_transfer` +instruction. + +```rust title="lib.rs" +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; + +declare_id!("3455LkCS85a4aYmSeNbRrJsduNQfYRY82A7eCD3yQfyR"); + +#[program] +pub mod cpi { + use super::*; + + // [!code word:sol_transfer] + // [!code highlight] + pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ) + .with_signer(signer_seeds); + + transfer(cpi_context, amount)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account( + mut, + seeds = [b"pda", recipient.key().as_ref()], + bump, + )] + pda_account: SystemAccount<'info>, + #[account(mut)] + recipient: SystemAccount<'info>, + system_program: Program<'info, System>, +} +``` + +The `cpi.test.ts` file shows how to invoke the Anchor program's `sol_transfer` +instruction and logs a link to the transaction details on SolanaFM. + +It shows how to derive the PDA using the seeds specified in the program: + +```ts +// [!code word:pda] +// [!code word:wallet.publicKey] +const [PDA] = PublicKey.findProgramAddressSync( + // [!code highlight] + [Buffer.from("pda"), wallet.publicKey.toBuffer()], + program.programId, +); +``` + +The first step in this example is to fund the PDA account with a basic SOL +transfer from the Playground wallet. + +```ts title="cpi.test.ts" +it("Fund PDA with SOL", async () => { + const transferInstruction = SystemProgram.transfer({ + fromPubkey: wallet.publicKey, + toPubkey: PDA, + lamports: transferAmount, + }); + + const transaction = new Transaction().add(transferInstruction); + + const transactionSignature = await sendAndConfirmTransaction( + connection, + transaction, + [wallet.payer], // signer + ); + + console.log( + `\nTransaction Signature:` + + `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +Once the PDA is funded with SOL, invoke the `sol_transfer` instruction. This +instruction transfers SOL from the PDA account back to the `wallet` account via +a CPI to the System Program, which is "signed" for by the program. + +```ts +it("SOL Transfer with PDA signer", async () => { + const transactionSignature = await program.methods + .solTransfer(new BN(transferAmount)) + .accounts({ + pdaAccount: PDA, + recipient: wallet.publicKey, + }) + .rpc(); + + console.log( + `\nTransaction Signature: https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +You can build, deploy, and run the test to view the transaction details on the +[SolanaFM explorer](https://solana.fm/). + +The transaction details will show that the custom program was first invoked +(instruction 1), which then invokes the System Program (instruction 1.1), +resulting in a successful SOL transfer. + +![Transaction Details](/docs/core/cpi/transaction-details-pda.png) + +### Example Explanation + +In the example code, the `SolTransfer` struct specifies the accounts required by +the transfer instruction. + +The sender is a PDA that the program must sign for. The `seeds` to derive the +address for the `pda_account` include the hardcoded string "pda" and the address +of the `recipient` account. This means the address for the `pda_account` is +unique for each `recipient`. + +```rust +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account( + mut, + // [!code highlight] + seeds = [b"pda", recipient.key().as_ref()], + bump, + )] + // [!code word:pda_account] + pda_account: SystemAccount<'info>, + #[account(mut)] + // [!code word:recipient] + recipient: SystemAccount<'info>, + // [!code word:system_program] + system_program: Program<'info, System>, +} +``` + +The Javascript equivalent to derive the PDA is included in the test file. + +```ts +// [!code word:pda] +// [!code word:wallet.publicKey] +const [PDA] = PublicKey.findProgramAddressSync( + // [!code highlight] + [Buffer.from("pda"), wallet.publicKey.toBuffer()], + program.programId, +); +``` + +The following tabs present two approaches to implementing Cross Program +Invocations (CPIs), each at a different level of abstraction. Both examples are +functionally equivalent. The main purpose is to illustrate the implementation +details of the CPI. + + + + +The `sol_transfer` instruction included in the example code shows a typical +approach for constructing CPIs using the Anchor framework. + +This approach involves creating a +[`CpiContext`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/context.rs#L171), +which includes the `program_id` and accounts required for the instruction being +called, followed by a helper function (`transfer`) to invoke a specific +instruction. + +```rust +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + // [!code word:cpi_context] + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ) + .with_signer(signer_seeds); + + // [!code highlight] + transfer(cpi_context, amount)?; + Ok(()) +} +``` + +When signing with PDAs, the seeds and bump seed are included in the +`cpi_context` as `signer_seeds` using `with_signer()`. The bump seed for a PDA +can be accessed using `ctx.bumps` followed by the name of the PDA account. + +```rust /signer_seeds/ /bump_seed/ {3} +let seed = to_pubkey.key(); +let bump_seed = ctx.bumps.pda_account; +// [!code word:signer_seeds] +// [!code highlight] +let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + +let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, +) +// [!code highlight] +.with_signer(signer_seeds); +``` + +The `cpi_context` and `amount` are then passed into the `transfer` function to +execute the CPI. + +```rust +transfer(cpi_context, amount)?; +``` + +When the CPI is processed, the Solana runtime will validate that the provided +seeds and caller program ID derive a valid PDA. The PDA is then added as a +signer on the invocation. This mechanism allows for programs to sign for PDAs +that are derived from their program ID. + + + + +Under the hood, the previous example is a wrapper around the `invoke_signed()` +function which uses +[`system_instruction::transfer`](https://github.com/anza-xyz/agave/blob/v1.18.26/sdk/program/src/system_instruction.rs#L881-L891) +to build the instruction. + +This example shows how to use the `invoke_signed()` function to make a CPI +signed for by a PDA. + +```rust +use anchor_lang::solana_program::{program::invoke_signed, system_instruction}; +``` + +```rust +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + + // [!code word:signer_seeds] + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + // [!code word:instruction:1] + let instruction = + &system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount); + + // [!code word:instruction:1] + // [!code highlight] + invoke_signed(instruction, &[from_pubkey, to_pubkey, program_id], signer_seeds)?; + Ok(()) +} +``` + +This implementation is functionally equivalent to the previous example. The +`signer_seeds` are passed into the `invoke_signed` function. + + + + +Here is a reference program on +[Solana Playground](https://beta.solpg.io/github.com/ZYJLiu/doc-examples/tree/main/cpi-pda) +which includes both examples. diff --git a/docs/content/docs/basics/idl.mdx b/docs/content/docs/basics/idl.mdx new file mode 100644 index 0000000000..f90d346f10 --- /dev/null +++ b/docs/content/docs/basics/idl.mdx @@ -0,0 +1,492 @@ +--- +title: Program IDL File +description: + Learn about the Interface Description Language (IDL) file in Anchor, its + purpose, benefits, and how it simplifies program-client interactions +--- + +An Interface Description Language (IDL) file for an Anchor program provides a +standardized JSON file describing the program's instructions and accounts. This +file simplifies the process of integrating your on-chain program with client +applications. + +Key Benefits of the IDL: + +- Standardization: Provides a consistent format for describing the program's + instructions and accounts +- Client Generation: Used to generate client code to interact with the program + + +The `anchor build` command generates an IDL file located at +`/target/idl/.json`. + + +The code snippets in the sections below highlight how the program, IDL, and +client relate to each other. + +## Program Instructions + +The `instructions` array in the IDL corresponds directly to the instructions +defined in your program. It specifies the required accounts and parameters for +each instruction. + + + + +The program below includes an `initialize` instruction, specifying the accounts +and parameters it requires. + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd"); + +#[program] +mod hello_anchor { + use super::*; + // [!code word:initialize] + // [!code word:Initialize] + // [!code highlight:5] + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + + // [!code highlight:8] +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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, +} +``` + + + + +The generated IDL file includes the instruction in a standardized JSON format, +including its name, discriminator, accounts, and arguments. + +```json title="JSON" +{ + "address": "BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + // [!code word:initialize] + // [!code highlight] + "name": "initialize", + // [!code word:discriminator:1] + // [!code highlight] + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + // [!code word:accounts:1] + "accounts": [ + // [!code highlight:14] + { + "name": "new_account", + "writable": true, + "signer": true + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + // [!code word:args:1] + "args": [ + // [!code highlight:4] + { + "name": "data", + "type": "u64" + } + ] + } + ], + "accounts": [ + { + "name": "NewAccount", + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ], + "types": [ + { + "name": "NewAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": "u64" + } + ] + } + } + ] +} +``` + + + + +The IDL file is then used to generate a client for interacting with the program, +simplifying the process of invoking the program instruction. + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program, BN } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; +import { Keypair } from "@solana/web3.js"; +import assert from "assert"; + +describe("hello_anchor", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + const wallet = provider.wallet as anchor.Wallet; + const program = anchor.workspace.HelloAnchor as Program; + + it("initialize", async () => { + // Generate keypair for the new account + const newAccountKp = new Keypair(); + + // Send transaction + const data = new BN(42); + // [!code word:initialize] + // [!code highlight:8] + const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + }) + .signers([newAccountKp]) + .rpc(); + + // Fetch the created account + const newAccount = await program.account.newAccount.fetch( + newAccountKp.publicKey, + ); + + console.log("Transaction signature: ", transactionSignature); + console.log("On-chain data is:", newAccount.data.toString()); + assert(data.eq(newAccount.data)); + }); +}); +``` + + + + +## Program Accounts + +The `accounts` array in the IDL corresponds to the structs in a program +annotated with the `#[account]` attribute. These structs define the data stored +on accounts created by the program. + + + + +The program below defines a `NewAccount` struct with a single `data` field of +type `u64`. + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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>, +} + +// [!code word:NewAccount] +// [!code highlight:4] +#[account] +pub struct NewAccount { + data: u64, +} +``` + + + + +The generated IDL file includes the account in a standardized JSON format, +including its name, discriminator, and fields. + +```json title="JSON" +{ + "address": "BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "new_account", + "writable": true, + "signer": true + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "data", + "type": "u64" + } + ] + } + ], + "accounts": [ + { + // [!code word:discriminator] + // [!code word:NewAccount] + // [!code highlight:2] + "name": "NewAccount", + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ], + "types": [ + // [!code highlight:12] + { + "name": "NewAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": "u64" + } + ] + } + } + ] +} +``` + + + + +The IDL file is then used to generate a client for interacting with the program, +simplifying the process of fetching and deserializing account data. + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program, BN } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; +import { Keypair } from "@solana/web3.js"; +import assert from "assert"; + +describe("hello_anchor", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + const wallet = provider.wallet as anchor.Wallet; + const program = anchor.workspace.HelloAnchor as Program; + + it("initialize", async () => { + // Generate keypair for the new account + const newAccountKp = new Keypair(); + + // Send transaction + const data = new BN(42); + const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + }) + .signers([newAccountKp]) + .rpc(); + + // Fetch the created account + // [!code word:.newAccount.:1] + // [!code highlight:3] + const newAccount = await program.account.newAccount.fetch( + newAccountKp.publicKey, + ); + + console.log("Transaction signature: ", transactionSignature); + console.log("On-chain data is:", newAccount.data.toString()); + assert(data.eq(newAccount.data)); + }); +}); +``` + + + + +## Discriminators + +Anchor assigns a unique 8 byte discriminator to each instruction and account +type in a program. These discriminators serve as identifiers to distinguish +between different instructions or account types. + +The discriminator is generated using the first 8 bytes of the Sha256 hash of a +prefix combined with the instruction or account name. As of Anchor v0.30, these +discriminators are included in the IDL file. + + + Note that when working with Anchor, you typically won't need to interact + directly with these discriminators. This section is primarily to provide + context on how the discriminator is generated and used. + + + + + +The instruction discriminator is used by the program to determine which specific +instruction to execute when called. + +When an Anchor program instruction is invoked, the discriminator is included as +the first 8 bytes of the instruction data. This is done automatically by the +Anchor client. + +```json title="IDL" + "instructions": [ + { + "name": "initialize", + // [!code word:discriminator] + // [!code highlight] + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + ... + } + ] +``` + +The discriminator for an instruction is the first 8 bytes of the Sha256 hash of +the prefix `global` plus the instruction name. + +For example: + +``` +sha256("global:initialize") +``` + +Hexadecimal output: + +``` +af af 6d 1f 0d 98 9b ed d4 6a 95 07 32 81 ad c2 1b b5 e0 e1 d7 73 b2 fb bd 7a b5 04 cd d4 aa 30 +``` + +The first 8 bytes are used as the discriminator for the instruction. + +``` +af = 175 +af = 175 +6d = 109 +1f = 31 +0d = 13 +98 = 152 +9b = 155 +ed = 237 +``` + +You can find the implementation of the discriminator generation in the Anchor +codebase +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/program/common.rs#L5-L19), +which is used +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/program/instruction.rs#L27). + + + + +The account discriminator is used to identify the specific account type when +deserializing on-chain data and is set when the account is created. + +```json title="IDL" + "accounts": [ + { + "name": "NewAccount", + // [!code word:discriminator] + // [!code highlight] + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ] +``` + +The discriminator for an account is the first 8 bytes of the Sha256 hash of the +prefix `account` plus the account name. + +For example: + +``` +sha256("account:NewAccount") +``` + +Hexadecimal output: + +``` +b0 5f 04 76 5b b1 7d e8 a1 93 57 2a d3 5e b1 ae e5 f0 69 e2 09 7e 5c d2 64 56 55 2a cb 4a e9 57 +``` + +The first 8 bytes are used as the discriminator for the account. + +``` +b0 = 176 +5f = 95 +04 = 4 +76 = 118 +5b = 91 +b1 = 177 +7d = 125 +e8 = 232 +``` + +You can find the implementation of the discriminator generation in the Anchor +codebase +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L101-L117). + + + Note that different programs using identical account names will generate the + same discriminator. When deserializing account data, Anchor programs will also + check an account is owned by the expected program for a specified account + type. + + + + diff --git a/docs/content/docs/basics/index.mdx b/docs/content/docs/basics/index.mdx new file mode 100644 index 0000000000..bd0f53c3d0 --- /dev/null +++ b/docs/content/docs/basics/index.mdx @@ -0,0 +1,9 @@ +--- +title: Anchor Framework Basics +description: + Learn how to use the Anchor framework to build secure Solana programs. +index: true +--- + +Before diving into Anchor, it's recommended to have a basic understanding of +[Solana's core concepts](https://solana.com/docs/core). diff --git a/docs/content/docs/basics/meta.json b/docs/content/docs/basics/meta.json new file mode 100644 index 0000000000..018ef1ba6c --- /dev/null +++ b/docs/content/docs/basics/meta.json @@ -0,0 +1,4 @@ +{ + "title": "The Basics", + "pages": ["program-structure", "idl", "client", "pda", "cpi"] +} diff --git a/docs/content/docs/basics/pda.mdx b/docs/content/docs/basics/pda.mdx new file mode 100644 index 0000000000..89271df26a --- /dev/null +++ b/docs/content/docs/basics/pda.mdx @@ -0,0 +1,346 @@ +--- +title: Program Derived Address +description: + Learn how to use Program Derived Addresses (PDAs) in Anchor programs to create + deterministic account addresses. +--- + +Program Derived Addresses (PDA) refer to a feature of Solana development that +allows you to create a unique address derived deterministically from pre-defined +inputs (seeds) and a program ID. + +This section will cover basic examples of how to use PDAs in an Anchor program. + +## Anchor PDA Constraints + +When using PDAs in an Anchor program, you generally use Anchor's account +constraints to define the seeds to derive the PDA. These constraints serve as +security checks to ensure that the correct address is derived. + +The constraints used to define the PDA seeds include: + +- `seeds`: An array of optional seeds used to derive the PDA. Seeds can be + static values or dynamic references to account data. +- `bump`: The bump seed used to derive the PDA. Used to ensure the address falls + off the Ed25519 curve and is a valid PDA. +- `seeds::program` - (Optional) The program ID used to derive the PDA address. + This constraint is only used to derive a PDA where the program ID is not the + current program. + + + The `seeds` and `bump` constraints are required to be used together. + + +### Usage Examples + +Below are examples demonstrating how to use PDA constraints in an Anchor +program. + + + + +The `seeds` constraint specifies the optional values used to derive the PDA. + +#### No Optional Seeds + +- Use an empty array `[]` to define a PDA without optional seeds. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + // [!code highlight] + seeds = [], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Single Static Seed + +- Specify optional seeds in the `seeds` constraint. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + // [!code word:b"hello_world"] + // [!code highlight] + seeds = [b"hello_world"], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Multiple Seeds and Account References + +- Multiple seeds can be specified in the `seeds` constraint. The `seeds` + constraint can also reference other account addresses or account data. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + pub signer: Signer<'info>, + #[account( + // [!code highlight] + seeds = [b"hello_world", signer.key().as_ref()], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +The example above uses both a static seed (`b"hello_world"`) and a dynamic seed +(the signer's public key). + + + + +The `bump` constraint specifies the bump seed used to derive the PDA. + +#### Automatic Bump Calculation + +When using the `bump` constraint without a value, the bump is automatically +calculated each time the instruction is invoked. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + // [!code highlight] + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Specify Bump Value + +You can explicitly provide the bump value, which is useful for optimizing +compute unit usage. This assumes that the PDA account has been created and the +bump seed is stored as a field on an existing account. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + // [!code highlight] + bump = pda_account.bump_seed, + )] + pub pda_account: Account<'info, CustomAccount>, +} + +#[account] +pub struct CustomAccount { + pub bump_seed: u8, +} +``` + +By storing the bump value in the account's data, the program doesn't need to +recalculate it, saving compute units. The saved bump value can be stored on the +account itself or another account. + + + + +The `seeds::program` constraint specifies the program ID used to derive the PDA. +This constraint is only used when deriving a PDA from a different program. + +Use this constraint when your instruction needs to interact with PDA accounts +created by another program. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + bump, + // [!code word:other_program] + // [!code highlight] + seeds::program = other_program.key(), + )] + pub pda_account: SystemAccount<'info>, + pub other_program: Program<'info, OtherProgram>, +} +``` + + + + +The `init` constraint is commonly used with `seeds` and `bump` to create a new +account with an address that is a PDA. Under the hood, the `init` constraint +invokes the System Program to create the account. + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account(mut)] + pub signer: Signer<'info>, + #[account( + // [!code word:init] + // [!code highlight:3] + init, + payer = signer, + space = 8 + 1, + seeds = [b"hello_world", signer.key().as_ref()], + bump, + )] + pub pda_account: Account<'info, CustomAccount>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct CustomAccount { + pub bump_seed: u8, +} +``` + + + The `init` constraint must be used with `payer` and `space`. The `payer` + specifies the account that will pay for the account creation. The `space` + specifies the bytes to allocate for the account (including the 8-byte + discriminator). + + + + + +## PDA seeds in the IDL + +Program Derived Address (PDA) seeds defined in the `seeds` constraint are +included in the program's IDL file. This allows the Anchor client to +automatically resolve account addresses using these seeds when constructing +instructions. + +This example below shows the relationship between the program, IDL, and client. + + + + +The program below defines a `pda_account` using a static seed (`b"hello_world"`) +and the signer's public key as a dynamic seed. + +```rust +use anchor_lang::prelude::*; + +declare_id!("BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5"); + +#[program] +mod hello_anchor { + use super::*; + pub fn test_instruction(ctx: Context) -> Result<()> { + msg!("PDA: {}", ctx.accounts.pda_account.key()); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:signer] + pub signer: Signer<'info>, + #[account( + // [!code highlight] + seeds = [b"hello_world", signer.key().as_ref()], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + + + + +The program's IDL file includes the PDA seeds defined in the `seeds` constraint. + +- The static seed `b"hello_world"` is converted to byte values. +- The dynamic seed is included as reference to the signer account. + +```json +{ + "address": "BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "test_instruction", + "discriminator": [33, 223, 61, 208, 32, 193, 201, 79], + "accounts": [ + { + "name": "signer", + "signer": true + }, + { + "name": "pda_account", + "pda": { + // [!code word:seeds] + "seeds": [ + { + // [!code highlight:2] + "kind": "const", + "value": [104, 101, 108, 108, 111, 95, 119, 111, 114, 108, 100] + }, + { + // [!code highlight:2] + "kind": "account", + "path": "signer" + } + ] + } + } + ], + "args": [] + } + ] +} +``` + + + + +The Anchor client can automatically resolve the PDA address using the IDL file. + +In the example below, Anchor automatically resolves the PDA address using the +provider wallet as the signer, and its public key as the dynamic seed for PDA +derivation. This removes the need to explicitly derive the PDA when building the +instruction. + +```ts {13} +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; + +describe("hello_anchor", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.HelloAnchor as Program; + + it("Is initialized!", async () => { + // Account address is automatically resolved using the IDL + const tx = await program.methods.testInstruction().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + +When the instruction is invoked, the PDA is printed to program logs as defined +in the program instruction. + +```{3} +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 invoke [1] +Program log: Instruction: TestInstruction +Program log: PDA: 3Hikt5mpKaSS4UNA5Du1TZJ8tp4o8VC8YWW6X9vtfVnJ +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 consumed 18505 of 200000 compute units +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 success +``` + + + diff --git a/docs/content/docs/basics/program-structure.mdx b/docs/content/docs/basics/program-structure.mdx new file mode 100644 index 0000000000..e0a03fb698 --- /dev/null +++ b/docs/content/docs/basics/program-structure.mdx @@ -0,0 +1,438 @@ +--- +title: Program Structure +description: + Learn about the structure of Anchor programs, including key macros and their + roles in simplifying Solana program development +--- + +The Anchor framework uses +[Rust macros](https://rust-book.cs.brown.edu/ch20-06-macros.html) to reduce +boilerplate code and simplify the implementation of common security checks +required for writing Solana programs. + +The main macros found in an Anchor program include: + +- [`declare_id`](#declare_id-macro): Specifies the program's on-chain address +- [`#[program]`](#program-attribute): Specifies the module containing the + program’s instruction logic +- [`#[derive(Accounts)]`](#deriveaccounts-macro): Applied to structs to indicate + a list of accounts required by an instruction +- [`#[account]`](#account-attribute): Applied to structs to create custom + account types for the program + +## Example Program + +Let's examine a simple program that demonstrates the usage of the macros +mentioned above to understand the basic structure of an Anchor program. + +The program below includes a single instruction called `initialize` that creates +a new account (`NewAccount`) and initializes it with a `u64` value. + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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, +} +``` + +## declare_id! macro + +The +[`declare_id`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L439) +macro specifies the on-chain address of the program, known as the program ID. +You can find the implementation of the code generated by the `declare_id!` macro +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/id.rs#L40-L73). + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +// [!code highlight] +declare_id!("11111111111111111111111111111111"); +``` + +By default, the program ID is the public key of the keypair generated at +`/target/deploy/your_program_name.json`. + +To update the value of the program ID in the `declare_id` macro with the public +key of the keypair in the `/target/deploy/your_program_name.json` file, run the +following command: + +```shell title="Terminal" +anchor keys sync +``` + +The `anchor keys sync` command is useful to run when cloning a repository where +the value of the program ID in a cloned repo's `declare_id` macro won't match +the one generated when you run `anchor build` locally. + +## #[program] attribute + +The +[`#[program]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/lib.rs#L12) +attribute annotates the module containing all the instruction handlers for your +program. Each public function within this module corresponds to an instruction +that can be invoked. + +You can find the implementation of the code generated by the `#[program]` +attribute +[here](https://github.com/coral-xyz/anchor/tree/v0.30.1/lang/syn/src/codegen/program). + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +// [!code highlight] +#[program] +mod hello_anchor { + use super::*; + // [!code highlight:5] + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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, +} +``` + +### Instruction Context + +Instruction handlers are functions that define the logic executed when an +instruction is invoked. The first parameter of each handler is a `Context` +type, where `T` is a struct implementing the +[`Accounts`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/lib.rs#L105) +trait and specifies the accounts the instruction requires. + +The +[`Context`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/context.rs#L24) +type provides the instruction with access to the following non-argument inputs: + +```rust +pub struct Context<'a, 'b, 'c, 'info, T> { + /// Currently executing program id. + pub program_id: &'a Pubkey, + /// Deserialized accounts. + pub accounts: &'b mut T, + /// Remaining accounts given but not deserialized or validated. + /// Be very careful when using this directly. + pub remaining_accounts: &'c [AccountInfo<'info>], + /// Bump seeds found during constraint validation. This is provided as a + /// convenience so that handlers don't have to recalculate bump seeds or + /// pass them in as arguments. + pub bumps: BTreeMap, +} +``` + +The `Context` fields can be accessed in an instruction using dot notation: + +- `ctx.accounts`: The accounts required for the instruction +- `ctx.program_id`: The program's public key (address) +- `ctx.remaining_accounts`: Additional accounts not specified in the `Accounts` + struct. +- `ctx.bumps`: Bump seeds for any Program Derived Address (PDA) accounts + specified in the `Accounts` struct + +Additional parameters are optional and can be included to specify arguments that +must be provided when the instruction is invoked. + +```rust title="lib.rs" +// [!code word:Context] +// [!code word:data:1] +pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) +} +``` + +In this example, the `Initialize` struct implements the `Accounts` trait where +each field in the struct represents an account required by the `initialize` +instruction. + +```rust title="lib.rs" +// [!code word:Initialize] +// [!code word:Accounts] +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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>, +} +``` + +## #[derive(Accounts)] macro + +The +[`#[derive(Accounts)]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/derive/accounts/src/lib.rs#L630) +macro is applied to a struct to specify the accounts that must be provided when +an instruction is invoked. This macro implements the +[`Accounts`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/lib.rs#L105) +trait, which simplifies account validation and serialization and deserialization +of account data. + +You can find the implementation of the code generated by the +`#[derive(Accounts)]` macro +[here](https://github.com/coral-xyz/anchor/tree/v0.30.1/lang/syn/src/codegen/accounts). + +```rust +// [!code word:Accounts] +#[derive(Accounts)] +// [!code highlight] +pub struct Initialize<'info> { + #[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>, +} +``` + +Each field in the struct represents an account required by an instruction. The +naming of each field is arbitrary, but it is recommended to use a descriptive +name that indicates the purpose of the account. + +```rust +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + // [!code word:new_account] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + // [!code word:signer] + pub signer: Signer<'info>, + // [!code word:system_program] + pub system_program: Program<'info, System>, +} +``` + +### Account Validation + +To prevent security vulnerabilities, it's important to verify that accounts +provided to an instruction are the expected accounts. Accounts are validated in +Anchor programs in two ways that are generally used together: + +- [Account Constraints](/docs/references/account-constraints): Constraints + define additional conditions that an account must satisfy to be considered + valid for the instruction. Constraints are applied using the `#[account(..)]` + attribute, which is placed above a field in a struct that implements the + `Accounts` trait. + + You can find a full list of the constraints + [here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/parser/accounts/constraints.rs) + and implementation + [here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/accounts/constraints.rs). + + ```rust + #[derive(Accounts)] + pub struct Initialize<'info> { + // [!code highlight] + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + // [!code highlight] + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, + } + ``` + +- [Account Types](/docs/references/account-types): Anchor provides various + account types to help ensure that the account provided by the client matches + what the program expects. + + You can find the implementation of the account types + [here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts). + + ```rust + #[derive(Accounts)] + pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + // [!code word: Account] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + // [!code word:Signer] + pub signer: Signer<'info>, + // [!code word:Program] + pub system_program: Program<'info, System>, + } + ``` + +When an instruction in an Anchor program is invoked, the program first validates +the accounts provided before executing the instruction's logic. After +validation, these accounts can be accessed within the instruction using the +`ctx.accounts` syntax. + +```rust title="lib.rs" +// [!code word:new_account] +// [!code word:Initialize] +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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, +} +``` + +## #[account] attribute + +The +[`#[account]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L66) +attribute is applied to structs that define the structure of the data stored in +custom accounts created by your program. + +```rust +// [!code highlight] +#[account] +pub struct NewAccount { + data: u64, +} +``` + +This macro implements various traits +[detailed here](https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html). +The key functionalities of the `#[account]` macro include: + +- [Assign Program Owner](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L119-L132): + When creating an account, the program owner of the account is automatically + set to the program specified in `declare_id`. +- [Set Discriminator](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L101-L117): + A unique 8 byte discriminator, specific to the account type, is added as the + first 8 bytes of account data during its initialization. This helps in + differentiating account types and is used for account validation. +- [Data Serialization and Deserialization](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L202-L246): + Account data is automatically serialized and deserialized as the account type. + +```rust title="lib.rs" +// [!code word:NewAccount] +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + // [!code word:data :1] + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[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>, +} + +// [!code word:data] +// [!code highlight:4] +#[account] +pub struct NewAccount { + data: u64, +} +``` + +### Account Discriminator + +An account discriminator in an Anchor program refers to an 8 byte identifier +unique to each account type. You can find the implementation of the account +discriminator +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L101-L117). + +The discriminator is the first 8 bytes of the SHA256 hash of the string +`account:`. This discriminator is stored as the first 8 bytes of +account data when an account is created. + +When creating an account in an Anchor program, 8 bytes must be allocated for the +discriminator. + +```rust +// [!code word:8 ] +#[account(init, payer = signer, space = 8 + 8)] +pub new_account: Account<'info, NewAccount>, +``` + +The discriminator is used during the following two scenarios: + +- Initialization: When an account is created, the discriminator is set as the + first 8 bytes of the account's data. +- Deserialization: When account data is deserialized, the first 8 bytes of + account data is checked against the discriminator of the expected account + type. + +If there's a mismatch, it indicates that the client has provided an unexpected +account. This mechanism serves as an account validation check in Anchor +programs. diff --git a/docs/content/docs/clients/index.mdx b/docs/content/docs/clients/index.mdx new file mode 100644 index 0000000000..e38c2cb0ab --- /dev/null +++ b/docs/content/docs/clients/index.mdx @@ -0,0 +1,7 @@ +--- +title: Clients +description: + Learn how to interact with Anchor programs using client libraries in + TypeScript and Rust. +index: true +--- diff --git a/docs/content/docs/clients/meta.json b/docs/content/docs/clients/meta.json new file mode 100644 index 0000000000..be9a9e31b0 --- /dev/null +++ b/docs/content/docs/clients/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Client Libraries", + "pages": ["typescript", "rust"] +} diff --git a/docs/content/docs/clients/rust.mdx b/docs/content/docs/clients/rust.mdx new file mode 100644 index 0000000000..d9b957126d --- /dev/null +++ b/docs/content/docs/clients/rust.mdx @@ -0,0 +1,268 @@ +--- +title: Rust +description: + Learn how to use Anchor's Rust client library to interact with Solana programs +--- + +The [`anchor-client`](https://docs.rs/anchor-client/latest/anchor_client/) crate +is the Rust client library for interacting with Anchor programs. You can find +the source code [here](https://github.com/coral-xyz/anchor/tree/v0.30.1/client). + +## Example + +The example below demonstrates how to use the `anchor-client` crate to interact +with a simple Anchor program. The program client can be automatically generated +from the program's IDL using the `declare_program!` macro. This macro generates +dependency free modules that enable you to interact with the program's +instructions and accounts. + +The program has two instructions: + +- `initialize` – Creates and initializes a counter account to store a value +- `increment` – Increments the value stored on the counter account + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC"); + +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + let counter = &ctx.accounts.counter; + msg!("Counter account created! Current count: {}", counter.count); + Ok(()) + } + + pub fn increment(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + msg!("Previous counter: {}", counter.count); + + counter.count += 1; + msg!("Counter incremented! Current count: {}", counter.count); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + space = 8 + 8 + )] + pub counter: Account<'info, Counter>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Increment<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +#[account] +pub struct Counter { + pub count: u64, +} +``` + +Below is an example folder structure for a Rust client that interacts with the +Anchor program: + + + + + + + + + + + + + + +The program IDL must be in a `/idls` folder. The `declare_program!` macro +searches for the IDL in the `/idls` folder to generate the client modules. + +```json title="idls/example.json" +{ + "address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC", + "metadata": { + "name": "example", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "Counter", + "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] + } + ], + "types": [ + { + "name": "Counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +} +``` + +Below is the `src/main.rs` file for interacting with the program: + +1. The `declare_program!` macro - Generates client modules for the program using + the IDL file + +2. The `anchor_client` crate - Provides utilities for interacting with the + program, including: + - Building program instructions + - Sending transactions + - Fetching program accounts + +```rust title="src/main.rs" +use anchor_client::{ + solana_client::rpc_client::RpcClient, + solana_sdk::{ + commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair, + signer::Signer, system_program, + }, + Client, Cluster, +}; +use anchor_lang::prelude::*; +use std::rc::Rc; + +declare_program!(example); +use example::{accounts::Counter, client::accounts, client::args}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let connection = RpcClient::new_with_commitment( + "http://127.0.0.1:8899", // Local validator URL + CommitmentConfig::confirmed(), + ); + + // Generate Keypairs and request airdrop + let payer = Keypair::new(); + let counter = Keypair::new(); + println!("Generated Keypairs:"); + println!(" Payer: {}", payer.pubkey()); + println!(" Counter: {}", counter.pubkey()); + + println!("\nRequesting 1 SOL airdrop to payer"); + let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?; + + // Wait for airdrop confirmation + while !connection.confirm_transaction(&airdrop_signature)? { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + println!(" Airdrop confirmed!"); + + // Create program client + let provider = Client::new_with_options( + Cluster::Localnet, + Rc::new(payer), + CommitmentConfig::confirmed(), + ); + let program = provider.program(example::ID)?; + + // Build and send instructions + println!("\nSend transaction with initialize and increment instructions"); + let initialize_ix = program + .request() + .accounts(accounts::Initialize { + counter: counter.pubkey(), + payer: program.payer(), + system_program: system_program::ID, + }) + .args(args::Initialize) + .instructions()? + .remove(0); + + let increment_ix = program + .request() + .accounts(accounts::Increment { + counter: counter.pubkey(), + }) + .args(args::Increment) + .instructions()? + .remove(0); + + let signature = program + .request() + .instruction(initialize_ix) + .instruction(increment_ix) + .signer(&counter) + .send() + .await?; + println!(" Transaction confirmed: {}", signature); + + println!("\nFetch counter account data"); + let counter_account: Counter = program.account::(counter.pubkey()).await?; + println!(" Counter value: {}", counter_account.count); + Ok(()) +} +``` + +Below are the dependencies for the `Cargo.toml` file: + +```toml title="Cargo.toml" +[package] +name = "rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +anchor-client = { version = "0.30.1", features = ["async"] } +anchor-lang = "0.30.1" +anyhow = "1.0.93" +tokio = { version = "1.0", features = ["full"] } +``` diff --git a/docs/content/docs/clients/typescript.mdx b/docs/content/docs/clients/typescript.mdx new file mode 100644 index 0000000000..cb5557d84e --- /dev/null +++ b/docs/content/docs/clients/typescript.mdx @@ -0,0 +1,690 @@ +--- +title: TypeScript +description: + Learn how to use Anchor's TypeScript client library to interact with Solana + programs +--- + +Anchor provides a Typescript client library +([@coral-xyz/anchor](https://github.com/coral-xyz/anchor/tree/v0.30.1/ts/packages/anchor)) +that simplifies the process of interacting with Solana programs from the client +in JavaScript or TypeScript. + + + The `@coral-xyz/anchor` library is only compatible with the legacy version + (v1) of `@solana/web3.js` and `@solana/spl-token`. It is not compatible with + the new version (v2) of `@solana/web3.js`. + + +## Client Program + +To interact with an Anchor program using `@coral-xyz/anchor`, you'll need to +create a +[`Program`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/index.ts#L58) +instance using the program's [IDL file](/docs/basics/idl). + +Creating an instance of the `Program` requires the program's IDL and an +[`AnchorProvider`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/provider.ts#L55). +An `AnchorProvider` is an abstraction that combines two things: + +- `Connection` - the connection to a Solana cluster (i.e. localhost, devnet, + mainnet) +- `Wallet` - (optional) a default wallet used to pay for and sign transactions + + + + +When integrating with a frontend using the +[Solana wallet adapter](https://github.com/anza-xyz/wallet-adapter), you'll need +to set up the `AnchorProvider` and `Program`. + +```ts title="example" +import { Program, AnchorProvider, setProvider } from "@coral-xyz/anchor"; +import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react"; +import type { HelloAnchor } from "./idlType"; +import idl from "./idl.json"; + +const { connection } = useConnection(); +const wallet = useAnchorWallet(); + +// [!code word:AnchorProvider] +// [!code highlight:2] +const provider = new AnchorProvider(connection, wallet, {}); +setProvider(provider); + +// [!code word:Program] +// [!code highlight:3] +export const program = new Program(idl as HelloAnchor, { + connection, +}); +``` + +In the code snippet above: + +- `idl.json` is the IDL file generated by Anchor, found at + `/target/idl/.json` in an Anchor project. +- `idlType.ts` is the IDL type (for use with TypeScript), found at + `/target/types/.ts` in an Anchor project. + +Alternatively, you can create an `Program` instance using only the IDL and the +`Connection` to a Solana cluster. This means there is no default `Wallet`, but +allows you to use the `Program` to fetch accounts or build instructions without +a connected wallet. + +```ts +import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; +import { Program } from "@coral-xyz/anchor"; +import type { HelloAnchor } from "./idlType"; +import idl from "./idl.json"; + +const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); + +// [!code word:Program] +// [!code highlight:3] +export const program = new Program(idl as HelloAnchor, { + connection, +}); +``` + + + + +When creating a new Anchor project, a default test file is generated that +includes a `Program` instance. This setup differs from applications outside the +Anchor workspace (like React or Node.js) where you'll need to manually create +the `Program` instance. + +```ts +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; + +describe("hello_anchor", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + // [!code highlight] + const program = anchor.workspace.HelloAnchor as Program; + + it("Is initialized!", async () => { + // Add your test here. + const tx = await program.methods.initialize().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + + + + +## Invoke Instructions + +Once the `Program` is set up using a program's IDL file, you can use the Anchor +[`MethodsBuilder`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L155) +to: + +- Build individual instructions +- Build transactions +- Build and send transactions + +The basic format looks like the following: + + + + +`program.methods` - This is the builder API for creating instruction calls from +the program's IDL + +```ts +// [!code word:methods] +// [!code highlight] +await program.methods + .instructionName(instructionData) + .accounts({}) + .signers([]) + .rpc(); +``` + + + + +Following `.methods`, specify the name of an instruction from the program IDL, +passing in any required arguments as comma-separated values. + +```ts +// [!code word:instructionName] +await program.methods + // [!code highlight] + .instructionName(instructionData1, instructionData2) + .accounts({}) + .signers([]) + .rpc(); +``` + + + + +`.accounts` - Pass in the address of the accounts required by the instruction as +specified in the IDL + +```ts +// [!code word:accounts] +await program.methods + .instructionName(instructionData) + // [!code highlight] + .accounts({}) + .signers([]) + .rpc(); +``` + +Note that certain account addresses don't need to be explicitly provided, as +they can automatically resolve using information included in the IDL. These +typically include: + +- Common accounts (ex. the System Program) +- Accounts where the address is a PDA (Program Derived Address) + + + + +`.signers` - Optionally pass in an array of keypairs required as additional +signers by the instruction. This is commonly used when creating new accounts +where the account address is the public key of a newly generated keypair. + +```ts +// [!code word:signers] +await program.methods + .instructionName(instructionData) + .accounts({}) + // [!code highlight] + .signers([]) + .rpc(); +``` + + + Note that `.signers` should only be used when also using `.rpc()`. When using + `.transaction()` or `.instruction()`, signers should be added to the + transaction before sending. + + + + + +Anchor provides multiple methods for building program instructions: + + + + +The +[`rpc()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L283) +method +[sends a signed transaction](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/rpc.ts#L29) +with the specified instruction and returns a `TransactionSignature`. + +When using `.rpc`, the `Wallet` from the `Provider` is automatically included as +a signer. + +```ts +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([newAccountKp]) + // [!code word:rpc] + // [!code highlight] + .rpc(); +``` + + + + +The +[`transaction()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L382) +method +[builds a `Transaction`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/transaction.ts#L18-L26) +with the specified instruction without sending the transaction. + +```ts +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const transaction = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + // [!code word:transaction:1] + // [!code highlight] + .transaction(); + +const transactionSignature = await connection.sendTransaction(transaction, [ + wallet.payer, + newAccountKp, +]); +``` + + + + +The +[`instruction()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L348) +method +[builds a `TransactionInstruction`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/instruction.ts#L57-L61) +using the specified instruction. This is useful if you want to manually add the +instruction to a transaction and combine it with other instructions. + +```ts +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const instruction = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + // [!code word:instruction:1] + // [!code highlight] + .instruction(); + +const transaction = new Transaction().add(instruction); + +const transactionSignature = await connection.sendTransaction(transaction, [ + wallet.payer, + newAccountKp, +]); +``` + + + + +## Fetch Accounts + +The `Program` client simplifies the process of fetching and deserializing +accounts created by your Anchor program. + +Use `program.account` followed by the name of the account type defined in the +IDL. Anchor provides multiple methods for fetching accounts. + + + + +Use +[`all()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L251) +to fetch all existing accounts for a specific account type. + +```ts +// [!code word:all] +const accounts = await program.account.newAccount.all(); +``` + + + + +Use `memcmp` (memory compare) to filter for account data that matches a specific +value at a specific offset. Using `memcmp` requires you to understand the byte +layout of the data field for the account type you are fetching. + +When calculating the offset, remember that the first 8 bytes in accounts created +by an Anchor program are reserved for the account discriminator. + +```ts +// [!code word:memcmp] +const accounts = await program.account.newAccount.all([ + { + // [!code highlight:4] + memcmp: { + offset: 8, + bytes: "", + }, + }, +]); +``` + + + + +Use +[`fetch()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L165) +to fetch the account data for a single account + +```ts +// [!code word:fetch] +const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS); +``` + + + + +Use +[`fetchMultiple()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L200) +to fetch the account data for multiple accounts by passing in an array of +account addresses + +```ts +// [!code word:fetchMultiple] +const accounts = await program.account.newAccount.fetchMultiple([ + ACCOUNT_ADDRESS_ONE, + ACCOUNT_ADDRESS_TWO, +]); +``` + + + + +## Example + +The example below demonstrates how to use `@coral-xyz/anchor` to interact with a +simple Anchor program. The program has two instructions: + +- `initialize` – Creates and initializes a counter account to store a value +- `increment` – Increments the value stored on the counter account + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC"); + +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + let counter = &ctx.accounts.counter; + msg!("Counter account created! Current count: {}", counter.count); + Ok(()) + } + + pub fn increment(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + msg!("Previous counter: {}", counter.count); + + counter.count += 1; + msg!("Counter incremented! Current count: {}", counter.count); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + space = 8 + 8 + )] + pub counter: Account<'info, Counter>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Increment<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +#[account] +pub struct Counter { + pub count: u64, +} +``` + +Below is an example folder structure for a TypeScript client that interacts with +the Anchor program: + + + + + + + + + + + + +The `/idl` directory in the example includes two files: + +- `example.json`: The IDL file for the program +- `example.ts`: A TypeScript type definition file generated for the IDL + +The tabs below include the `example.json` and `example.ts` files as a reference +of what these files look like. + + + +```json tab="example.json" +{ + "address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC", + "metadata": { + "name": "example", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "Counter", + "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] + } + ], + "types": [ + { + "name": "Counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +} +``` + +```ts tab="example.ts" +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/example.json`. + */ +export type Example = { + address: "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC"; + metadata: { + name: "example"; + version: "0.1.0"; + spec: "0.1.0"; + description: "Created with Anchor"; + }; + instructions: [ + { + name: "increment"; + discriminator: [11, 18, 104, 9, 104, 174, 59, 33]; + accounts: [ + { + name: "counter"; + writable: true; + }, + ]; + args: []; + }, + { + name: "initialize"; + discriminator: [175, 175, 109, 31, 13, 152, 155, 237]; + accounts: [ + { + name: "payer"; + writable: true; + signer: true; + }, + { + name: "counter"; + writable: true; + signer: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, + ]; + args: []; + }, + ]; + accounts: [ + { + name: "counter"; + discriminator: [255, 176, 4, 245, 188, 253, 124, 25]; + }, + ]; + types: [ + { + name: "counter"; + type: { + kind: "struct"; + fields: [ + { + name: "count"; + type: "u64"; + }, + ]; + }; + }, + ]; +}; +``` + + + + + +When you run `anchor build` in an Anchor project, the Anchor CLI automatically +generates: + +- The IDL file (`.json`) in the `target/idl` folder (ex. + `target/idl/example.json`) + +- The TypeScript type definitions (`.ts`) in the `target/types` folder (ex. + `target/types/example.ts`) + + + +The `example.ts` file below includes the script to interact with the program. + +```ts title="example.ts" +import { + Connection, + Keypair, + LAMPORTS_PER_SOL, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { Program } from "@coral-xyz/anchor"; +import type { Example } from "./idl/example.ts"; +import idl from "./idl/example.json"; + +// Set up a connection to the cluster +const connection = new Connection("http://127.0.0.1:8899", "confirmed"); + +// Create a Program instance using the IDL and connection +const program = new Program(idl as Example, { + connection, +}); + +// Generate new Keypairs for the payer and the counter account +const payer = Keypair.generate(); +const counter = Keypair.generate(); + +// Airdrop SOL to fund the payer's account for transaction fees +const airdropTransactionSignature = await connection.requestAirdrop( + payer.publicKey, + LAMPORTS_PER_SOL, +); +await connection.confirmTransaction(airdropTransactionSignature); + +// Build the initialize instruction +const initializeInstruction = await program.methods + .initialize() + .accounts({ + payer: payer.publicKey, + counter: counter.publicKey, + }) + .instruction(); + +// Build the increment instruction +const incrementInstruction = await program.methods + .increment() + .accounts({ + counter: counter.publicKey, + }) + .instruction(); + +// Add both instructions to a single transaction +const transaction = new Transaction().add( + initializeInstruction, + incrementInstruction, +); + +// Send the transaction +const transactionSignature = await sendAndConfirmTransaction( + connection, + transaction, + [payer, counter], +); +console.log("Transaction Signature", transactionSignature); + +// Fetch the counter account +const counterAccount = await program.account.counter.fetch(counter.publicKey); +console.log("Count:", counterAccount.count); +``` diff --git a/docs/content/docs/features/declare-program.mdx b/docs/content/docs/features/declare-program.mdx new file mode 100644 index 0000000000..13809aa334 --- /dev/null +++ b/docs/content/docs/features/declare-program.mdx @@ -0,0 +1,778 @@ +--- +title: Dependency Free Composability +description: + Learn how to use Anchor's declare_program macro to interact with programs + without additional dependencies. +--- + +The +[`declare_program!()`](https://github.com/coral-xyz/anchor/tree/v0.30.1/lang/attribute/program/src/declare_program) +macro simplifies the process of interacting with Anchor programs by generating +Rust modules (from a program's IDL) that can be used in both on-chain and +off-chain code. You can find an example program +[here](https://github.com/coral-xyz/anchor/tree/master/tests/declare-program). + +The following modules are generated by the `declare_program!()` macro: + +| Module | Description | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| [`cpi`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/cpi.rs) | Helper functions for making cross-program invocations (CPIs) to the program from other on-chain programs | +| [`client`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/client.rs) | Accounts and arguments required to build program instructions to add to client-side transactions | +| [`account`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/accounts.rs) | Account data types (program state) defined in the program | +| [`program`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/program.rs) | Program ID constant used to identify the program | +| [`constants`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/constants.rs) | Program constants defined in the program | +| [`events`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/events.rs) | Program events defined in the program | +| [`types`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/declare_program/mods/types.rs) | Program types defined in the program | + +## Examples + +The following examples demonstrate how to use the `declare_program!()` macro in +two scenarios: + +1. Making Cross Program Invocations (CPIs) from one program to another program +2. Building client-side transactions to invoke a program's instructions + +Both examples show how the modules generated by the `declare_program!()` macro +simplify program interactions, whether you're writing on-chain or off-chain +code. + +### On-chain CPI + +To use the `declare_program!()` macro, you need the IDL file for the target +program. The IDL file must be placed in a directory named `/idls` in your +project. The `/idls` directory can be located at any level in your project +structure. For example, your project could have this layout: + + + + + + + + + + + + + + + +Below is the source code (`lib.rs`) for the target (callee) program that +generates the `example.json` IDL file shown above. + +Using the program's IDL file, another program can use the `declare_program!()` +macro to generate a CPI module, enabling it to make CPIs to this program's +instructions. + + + +```rust tab="Callee Program" +use anchor_lang::prelude::*; + +declare_id!("8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8"); + +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + let counter = &ctx.accounts.counter; + msg!("Counter account created! Current count: {}", counter.count); + Ok(()) + } + + pub fn increment(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + msg!("Previous counter: {}", counter.count); + + counter.count += 1; + msg!("Counter incremented! Current count: {}", counter.count); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + space = 8 + 8 + )] + pub counter: Account<'info, Counter>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Increment<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +#[account] +pub struct Counter { + pub count: u64, +} +``` + +```ts tab="IDL" +{ + "address": "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8", + "metadata": { + "name": "example", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [ + 11, + 18, + 104, + 9, + 104, + 174, + 59, + 33 + ], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "Counter", + "discriminator": [ + 255, + 176, + 4, + 245, + 188, + 253, + 124, + 25 + ] + } + ], + "types": [ + { + "name": "Counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +} +``` + + + +Below is the source code (`lib.rs`) for the caller program (example-cpi) that +uses the `declare_program!()` macro to generate a CPI module to invoke the +instructions defined in the callee program above. + + + +```rust tab="Caller Program" +use anchor_lang::prelude::*; + +declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH"); + +// [!code word:declare_program] +// [!code highlight:9] +declare_program!(example); +use example::{ + accounts::Counter, + cpi::{ + self, + accounts::{Increment, Initialize}, + }, + program::Example, +}; + +#[program] +pub mod example_cpi { + + use super::*; + + pub fn initialize_cpi(ctx: Context) -> Result<()> { + // Create CPI context for initialize + let cpi_ctx = CpiContext::new( + ctx.accounts.example_program.to_account_info(), + Initialize { + payer: ctx.accounts.payer.to_account_info(), + counter: ctx.accounts.counter.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + }, + ); + + // Invoke the initialize instruction + cpi::initialize(cpi_ctx)?; + Ok(()) + } + + pub fn increment_cpi(ctx: Context) -> Result<()> { + // Create CPI context for increment + let cpi_ctx = CpiContext::new( + ctx.accounts.example_program.to_account_info(), + Increment { + counter: ctx.accounts.counter.to_account_info(), + }, + ); + + // Invoke the increment instruction + cpi::increment(cpi_ctx)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InitializeCpi<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account(mut)] + pub counter: Signer<'info>, + pub system_program: Program<'info, System>, + pub example_program: Program<'info, Example>, +} + +#[derive(Accounts)] +pub struct IncrementCpi<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, + pub example_program: Program<'info, Example>, +} +``` + +```ts tab="Test" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { Example } from "../target/types/example"; +import { ExampleCpi } from "../target/types/example_cpi"; +import { Keypair } from "@solana/web3.js"; + +describe("example", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.Example as Program; + const cpiProgram = anchor.workspace.ExampleCpi as Program; + + const counterAccount = Keypair.generate(); + + it("Is initialized!", async () => { + const transactionSignature = await cpiProgram.methods + .initializeCpi() + .accounts({ + counter: counterAccount.publicKey, + }) + .signers([counterAccount]) + .rpc({ skipPreflight: true }); + + const accountData = await program.account.counter.fetch( + counterAccount.publicKey, + ); + + console.log(`Transaction Signature: ${transactionSignature}`); + console.log(`Count: ${accountData.count}`); + }); + + it("Increment", async () => { + const transactionSignature = await cpiProgram.methods + .incrementCpi() + .accounts({ + counter: counterAccount.publicKey, + }) + .rpc(); + + const accountData = await program.account.counter.fetch( + counterAccount.publicKey, + ); + + console.log(`Transaction Signature: ${transactionSignature}`); + console.log(`Count: ${accountData.count}`); + }); +}); +``` + + + +#### Explanation + + + + + +The `declare_program!()` macro takes a single argument - the name of the +program's IDL file (e.g. `example.json`): + +```rust +declare_program!(example); // Looks for /idls/example.json +``` + + + + + +Bring into scope the generated modules: + +```rust +use example::{ + accounts::Counter, // Account types + cpi::{ // Cross program invocation helpers + self, + accounts::{Increment, Initialize}, + }, + program::Example, // Program type +}; +``` + + + + + +Use the imported types in the account validation structs: + +```rust +#[derive(Accounts)] +pub struct IncrementCpi<'info> { + // Counter type from accounts module + #[account(mut)] + // [!code word:Counter] + // [!code highlight] + pub counter: Account<'info, Counter>, + + // Example type from program module + // [!code word:Example] + // [!code highlight] + pub example_program: Program<'info, Example>, +} +``` + + + + + +Use the CPI module to invoke the program's instructions: + +```rust +pub fn initialize_cpi(ctx: Context) -> Result<()> { + // Create CPI context for initialize + let cpi_ctx = CpiContext::new( + ctx.accounts.example_program.to_account_info(), + Initialize { + payer: ctx.accounts.payer.to_account_info(), + counter: ctx.accounts.counter.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + }, + ); + + // Invoke the initialize instruction + // [!code highlight] + cpi::initialize(cpi_ctx)?; + Ok(()) +} +``` + +```rust +pub fn increment_cpi(ctx: Context) -> Result<()> { + // Create CPI context for increment + let cpi_ctx = CpiContext::new( + ctx.accounts.example_program.to_account_info(), + Increment { + counter: ctx.accounts.counter.to_account_info(), + }, + ); + + // Invoke the increment instruction + // [!code highlight] + cpi::increment(cpi_ctx)?; + Ok(()) +} +``` + + + + + +### Off-chain Client + +To use the `declare_program!()` macro, you need the IDL file for the target +program. The IDL file must be placed in a directory named `/idls` in your +project. The `/idls` directory can be located at any level in your project +structure. For example, your project could have this layout: + + + + + + + + + + + +Below is the source code (`lib.rs`) for the target program that generates the +`example.json` IDL file shown above. The program's IDL can then be used in a +client script along with the `declare_program!()` macro to generate a Client +module to build the program's instructions. + + + +```rust tab="Callee Program" +use anchor_lang::prelude::*; + +declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC"); + +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + let counter = &ctx.accounts.counter; + msg!("Counter account created! Current count: {}", counter.count); + Ok(()) + } + + pub fn increment(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + msg!("Previous counter: {}", counter.count); + + counter.count += 1; + msg!("Counter incremented! Current count: {}", counter.count); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + space = 8 + 8 + )] + pub counter: Account<'info, Counter>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Increment<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +#[account] +pub struct Counter { + pub count: u64, +} +``` + +```ts tab="IDL" +{ + "address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC", + "metadata": { + "name": "example", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "Counter", + "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] + } + ], + "types": [ + { + "name": "Counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +} +``` + + + +Below is the client script (main.rs) that uses the `declare_program!()` macro to +generate a Client module to build the program's instructions. + + + +```rust tab="Client Script" +use anchor_client::{ + solana_client::rpc_client::RpcClient, + solana_sdk::{ + commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair, + signer::Signer, system_program, + }, + Client, Cluster, +}; +use anchor_lang::prelude::*; +use std::rc::Rc; + +declare_program!(example); +use example::{accounts::Counter, client::accounts, client::args}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let connection = RpcClient::new_with_commitment( + "http://127.0.0.1:8899", // Local validator URL + CommitmentConfig::confirmed(), + ); + + // Generate Keypairs and request airdrop + let payer = Keypair::new(); + let counter = Keypair::new(); + println!("Generated Keypairs:"); + println!(" Payer: {}", payer.pubkey()); + println!(" Counter: {}", counter.pubkey()); + + println!("\nRequesting 1 SOL airdrop to payer"); + let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?; + + // Wait for airdrop confirmation + while !connection.confirm_transaction(&airdrop_signature)? { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + println!(" Airdrop confirmed!"); + + // Create program client + let provider = Client::new_with_options( + Cluster::Localnet, + Rc::new(payer), + CommitmentConfig::confirmed(), + ); + let program = provider.program(example::ID)?; + + // Build and send instructions + println!("\nSend transaction with initialize and increment instructions"); + let initialize_ix = program + .request() + .accounts(accounts::Initialize { + counter: counter.pubkey(), + payer: program.payer(), + system_program: system_program::ID, + }) + .args(args::Initialize) + .instructions()? + .remove(0); + + let increment_ix = program + .request() + .accounts(accounts::Increment { + counter: counter.pubkey(), + }) + .args(args::Increment) + .instructions()? + .remove(0); + + let signature = program + .request() + .instruction(initialize_ix) + .instruction(increment_ix) + .signer(&counter) + .send() + .await?; + println!(" Transaction confirmed: {}", signature); + + println!("\nFetch counter account data"); + let counter_account: Counter = program.account::(counter.pubkey()).await?; + println!(" Counter value: {}", counter_account.count); + Ok(()) +} +``` + +```toml tab="Dependencies" +[package] +name = "rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +anchor-client = { version = "0.30.1", features = ["async"] } +anchor-lang = "0.30.1" +anyhow = "1.0.93" +tokio = { version = "1.0", features = ["full"] } +``` + + + + + + + +The `declare_program!()` macro takes a single argument - the name of the +program's IDL file (e.g. `example.json`): + +```rust +declare_program!(example); // Looks for /idls/example.json +``` + + + + + +Bring into scope the generated modules: + +```rust +use example::{ + accounts::Counter, // Program Account types + client::accounts, // Accounts for program instructions + client::args, // Arguments for program instructions +}; +``` + + + + + +Use the Client module to build the program's instructions: + +```rust +// Build initialize instruction +let initialize_ix = program + .request() + // Accounts required for initialize instruction + .accounts(accounts::Initialize { + counter: counter.pubkey(), + payer: program.payer(), + system_program: system_program::ID, + }) + // Arguments for initialize instruction (discriminator) + .args(args::Initialize) + .instructions()? + .remove(0); +``` + +```rust +// Build increment instruction +let increment_ix = program + .request() + // Accounts required for increment instruction + .accounts(accounts::Increment { + counter: counter.pubkey(), + }) + // Arguments for increment instruction (discriminator) + .args(args::Increment) + .instructions()? + .remove(0); +``` + + + + +Add the program's instructions to a transaction and send the transaction: + +```rust +let signature = program + .request() + .instruction(initialize_ix) + .instruction(increment_ix) + .signer(&counter) + .send() + .await?; +``` + + + + + +Use the Account module to fetch and deserialize the program's account types: + +```rust +// Counter type from accounts module +let counter_account: Counter = program.account::(counter.pubkey()).await?; +``` + + + + diff --git a/docs/content/docs/features/errors.mdx b/docs/content/docs/features/errors.mdx new file mode 100644 index 0000000000..e0a8e87c01 --- /dev/null +++ b/docs/content/docs/features/errors.mdx @@ -0,0 +1,321 @@ +--- +title: Custom Errors +description: Learn how to implement custom error handling in Anchor programs. +--- + +All instruction handlers in Anchor programs return a custom `Result` type +that allows you to handle successful execution with `Ok(T)` and error cases with +`Err(Error)`. + +```rust +// [!code word:Result] +pub fn custom_instruction(ctx: Context) -> Result<()> { + // --snip-- + Ok(()) +} +``` + +The +[`Result`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/lib.rs#L74) +type in Anchor programs is a type alias that wraps the standard Rust +`Result`. In this case, `T` represents the successful return type, while +`E` is Anchor's custom `Error` type. + +```rust +pub type Result = std::result::Result; +``` + +## Anchor Error + +When an error occurs in an Anchor program, it returns a custom +[`Error`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/error.rs#L277-L281) +type defined as: + +```rust +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + AnchorError(Box), + ProgramError(Box), +} +``` + +The `Error` type in Anchor programs can be one of two variants: + +1. [`ProgramErrorWithOrigin`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/error.rs#L389-L394): + Custom type that wraps a standard Solana + [`ProgramError`](https://github.com/anza-xyz/agave/blob/v1.18.26/sdk/program/src/program_error.rs#L12-L66) + type. These errors come from the `solana_program` crate. + +```rust +#[derive(Debug)] +pub struct ProgramErrorWithOrigin { + pub program_error: ProgramError, + pub error_origin: Option, + pub compared_values: Option, +} +``` + +2. [`AnchorError`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/error.rs#L490-L497): + Errors defined by the Anchor framework. + +```rust +#[derive(Debug)] +pub struct AnchorError { + pub error_name: String, + pub error_code_number: u32, + pub error_msg: String, + pub error_origin: Option, + pub compared_values: Option, +} +``` + +An `AnchorError` can be thought of as having two categories: + +1. Internal Anchor Errors - These are built-in errors included with the Anchor + framework. They are defined in the + [`ErrorCode`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/error.rs#L10-L275) + enum. + +2. Custom Program Errors - These are program specific errors that developers + define to handle custom error cases. + +The `error_code_number` from an `AnchorError` has the following numbering +scheme: + +| Error Code | Description | +| ---------- | ------------------------------------- | +| >= 100 | Instruction error codes | +| >= 1000 | IDL error codes | +| >= 2000 | Constraint error codes | +| >= 3000 | Account error codes | +| >= 4100 | Misc error codes | +| = 5000 | Deprecated error code | +| >= 6000 | Starting point for custom user errors | + +## Usage + +Anchor provides a convenient way to define custom errors through the +`error_code` attribute. The implementation details can be found +[here](https://github.com/coral-xyz/anchor/blob/master/lang/syn/src/codegen/error.rs). + +When you define an enum with the `error_code` attribute, Anchor automatically: + +- Assigns an error code starting from 6000 +- Generates the necessary boilerplate for error handling +- Enables the use of custom error messages via the `msg` attribute + +```rust +#[error_code] +pub enum MyError { + #[msg("My custom error message")] + MyCustomError, + #[msg("My second custom error message")] + MySecondCustomError, +} +``` + +### err! + +To throw an error, use the +[`err!`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/lib.rs#L720-L728) +macro. The `err!` macro provides a convenient way to return custom errors from +your program. Under the hood, `err!` uses the `error!` macro to construct +`AnchorError`. The implementation can be found +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/error/src/lib.rs#L84-L116). + +```rust +#[program] +mod hello_anchor { + use super::*; + pub fn set_data(ctx: Context + + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("9oECKMeeyf1fWNPKzyrB2x1AbLjHDFjs139kEyFwBpoV"); + +#[program] +pub mod custom_error { + use super::*; + + pub fn validate_amount(_ctx: Context + + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { CustomError } from "../target/types/custom_error"; +import assert from "assert"; + +describe("custom-error", () = { + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace.CustomError as Program + + +When a program error occurs, Anchor's TypeScript Client SDK returns a detailed +[error response](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/error.ts#L51-L71) +containing information about the error. Here's an example error response showing +the structure and available fields: + +```shell title="Error Response" +{ + errorLogs: [ + 'Program log: AnchorError thrown in programs/custom-error/src/lib.rs:11. Error Code: AmountTooLarge. Error Number: 6001. Error Message: Amount must be less than or equal to 100.' + ], + logs: [ + 'Program 9oECKMeeyf1fWNPKzyrB2x1AbLjHDFjs139kEyFwBpoV invoke [1]', + 'Program log: Instruction: ValidateAmount', + 'Program log: AnchorError thrown in programs/custom-error/src/lib.rs:11. Error Code: AmountTooLarge. Error Number: 6001. Error Message: Amount must be less than or equal to 100.', + 'Program 9oECKMeeyf1fWNPKzyrB2x1AbLjHDFjs139kEyFwBpoV consumed 2153 of 200000 compute units', + 'Program 9oECKMeeyf1fWNPKzyrB2x1AbLjHDFjs139kEyFwBpoV failed: custom program error: 0x1771' + ], + error: { + errorCode: { code: 'AmountTooLarge', number: 6001 }, + errorMessage: 'Amount must be less than or equal to 100', + comparedValues: undefined, + origin: { file: 'programs/custom-error/src/lib.rs', line: 11 } + }, + _programErrorStack: ProgramErrorStack { + stack: [ + [PublicKey [PublicKey(9oECKMeeyf1fWNPKzyrB2x1AbLjHDFjs139kEyFwBpoV)]] + ] + } +} +``` + +For a more comprehensive example, you can also reference the +[errors test program](https://github.com/coral-xyz/anchor/blob/master/tests/errors/programs/errors/src/lib.rs) +in the Anchor repository. diff --git a/docs/content/docs/features/events.mdx b/docs/content/docs/features/events.mdx new file mode 100644 index 0000000000..64cf3ce3ce --- /dev/null +++ b/docs/content/docs/features/events.mdx @@ -0,0 +1,367 @@ +--- +title: Emit Events +description: + Learn how to emit events in Anchor programs using emit! and emit_cpi! macros. +--- + +## Examples + +Anchor provides two macros for emitting events in your programs: + +1. `emit!()` - Emits events directly to program logs. This is the simpler, + though program logs may be truncated by data providers in some cases +2. `emit_cpi!()` - Emits events through a Cross Program Invocation (CPI) by + including the event data in the instruction data. + + + +The `emit_cpi()` approach was introduced an alternative to program logs, which +can sometimes be truncated by data providers. While CPI instruction data is less +likely to be truncated, this approach does incur additional compute costs from +the Cross Program Invocation. + + + + + +For more robust solutions for events, consider geyser gRPC services by +[Triton](https://docs.triton.one/project-yellowstone/dragons-mouth-grpc-subscriptions) +or [Helius](https://docs.helius.dev/data-streaming/geyser-yellowstone). + + + +### `emit` + +The +[`emit!()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/event/src/lib.rs#L94-L102) +macro provides a way to emit events through program logs. When called, it: + +1. Uses the + [`sol_log_data()`](https://github.com/anza-xyz/agave/blob/v1.18.26/sdk/program/src/log.rs#L115-L124) + syscall to write the data to program logs +2. Encodes the event data as a + [base64 string](https://github.com/anza-xyz/agave/blob/v1.18.26/program-runtime/src/stable_log.rs#L46-L61) + prefixed with `Program Data:` + +To receive emitted events in your client application, use the +[`addEventListener()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/event.ts#L74-L123) +method. This method automatically +[parses and decodes](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/event.ts#L230-L251) +event data from the program logs. + +Example usage: + + + + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy"); + +#[program] +pub mod event { + use super::*; + + pub fn emit_event(_ctx: Context, input: String) -> Result<()> { + // [!code word:emit!] + // [!code highlight] + emit!(CustomEvent { message: input }); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct EmitEvent {} + +// [!code highlight:4] +#[event] +pub struct CustomEvent { + pub message: String, +} +``` + + + + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { Event } from "../target/types/event"; + +describe("event", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.Event as Program; + + it("Emits custom event", async () => { + // Set up listener before sending transaction + // [!code word:addEventListener] + // [!code highlight:4] + const listenerId = program.addEventListener("customEvent", event => { + // Do something with the event data + console.log("Event Data:", event); + }); + + // Message to be emitted in the event + const message = "Hello, Solana!"; + // Send transaction + await program.methods.emitEvent(message).rpc(); + + // Remove listener + await program.removeEventListener(listenerId); + }); +}); +``` + + + + +The following is the output of the program logs. The event data is base64 +encoded as `Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=`. + +```shell title="Program Logs" +Log Messages: + Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy invoke [1] + Program log: Instruction: EmitEvent + Program data: Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE= + Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy consumed 1012 of 200000 compute units + Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy success +``` + + + Ensure the RPC provider you use does not truncate the program logs from the + transaction data. + + +### `emit_cpi` + +The +[`emit_cpi!()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/event/src/lib.rs#L148-L184) +macro emits events through Cross Program Invocations (CPIs) to the program +itself. The event data is encoded and included in the CPI's instruction data +(instead of program logs). + +To emit events through CPIs, you need to enable the `event-cpi` feature in your +program's `Cargo.toml`: + +```toml title="Cargo.toml" +[dependencies] +anchor-lang = { version = "0.30.1", features = ["event-cpi"] } +``` + +Example usage: + + + + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1"); + +#[program] +pub mod event_cpi { + use super::*; + + pub fn emit_event(ctx: Context, input: String) -> Result<()> { + // [!code word:emit_cpi!] + // [!code highlight] + emit_cpi!(CustomEvent { message: input }); + Ok(()) + } +} + +// [!code highlight] +#[event_cpi] +#[derive(Accounts)] +pub struct EmitEvent {} + +// [!code highlight:4] +#[event] +pub struct CustomEvent { + pub message: String, +} +``` + + + + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { EventCpi } from "../target/types/event_cpi"; + +describe("event-cpi", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace.EventCpi as Program; + + it("Emits custom event", async () => { + const message = "Hello, Solana!"; + const transactionSignature = await program.methods.emitEvent(message).rpc(); + + // Wait for the transaction to be confirmed + await program.provider.connection.confirmTransaction( + transactionSignature, + "confirmed", + ); + + // Fetch the transaction data + // [!code highlight:4] + const transactionData = await program.provider.connection.getTransaction( + transactionSignature, + { commitment: "confirmed" }, + ); + + // Decode the event data from the CPI instruction data + // [!code highlight:4] + const eventIx = transactionData.meta.innerInstructions[0].instructions[0]; + const rawData = anchor.utils.bytes.bs58.decode(eventIx.data); + const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8)); + const event = program.coder.events.decode(base64Data); + console.log(event); + }); +}); +``` + + + + +The +[`event_cpi`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/event/src/lib.rs#L217-L226) +attribute must be added to the `#[derive(Accounts)]` struct for the instruction +instruction that emits events using the `emit_cpi!()` macro. This attribute +[automatically includes additional accounts](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/parser/accounts/event_cpi.rs#L28-L70) +that are required for the self CPI. + +```rust title="lib.rs" +// [!code highlight] +#[event_cpi] +#[derive(Accounts)] +pub struct RequiredAccounts { + // --snip-- +} +``` + +To get the emitted event data in your client application, you need to fetch the +transaction using the transaction signature and parse the event data from the +CPI instruction data. + +```ts title="test.ts" +// 1. Fetch the full transaction data using the transaction signature +const transactionData = await program.provider.connection.getTransaction( + transactionSignature, + { commitment: "confirmed" }, +); + +// 2. Extract the CPI (inner instruction) that contains the event data +const eventIx = transactionData.meta.innerInstructions[0].instructions[0]; + +// 3. Decode the event data +const rawData = anchor.utils.bytes.bs58.decode(eventIx.data); +const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8)); +const event = program.coder.events.decode(base64Data); +console.log(event); +``` + +Below is an example transaction showing how event data appears in the +transaction details. When using `emit_cpi!()`, the event data is encoded and +included in the `data` field of an inner instruction (CPI). + +In the example transaction below, the encoded event data is +`"data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp"` in the +`innerInstructions` array. + +```shell title="Transaction Data" +{ + "blockTime": 1735854530, + "meta": { + "computeUnitsConsumed": 13018, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 0, + "instructions": [ + { + "accounts": [ + 1 + ], + "data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp", + "programIdIndex": 2, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [1]", + "Program log: Instruction: EmitEvent", + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [2]", + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 5000 of 192103 compute units", + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success", + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 13018 of 200000 compute units", + "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success" + ], + "postBalances": [ + 499999999999995000, + 0, + 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, + 0, + 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 2, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1", + "2brZf9PQqEvv17xtbj5WNhZJULgVZuLZT6FgH1Cqpro2", + "2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1" + ], + "recentBlockhash": "2QtnU35RXTo7uuQEVARYJgWYRYtbqUxWQkK8WywUnVdY", + "instructions": [ + { + "accounts": [ + 1, + 2 + ], + "data": "3XZZ984toC4WXCLkxBsLimpEGgH75TKXRJnk", + "programIdIndex": 2, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3gFbKahSSbitRSos4MH3cqeVv2FiTNaLCuWaLPo6R98FEbHnTshoYxopGcx74nFLqt1pbZK9i8dnr4NFXayrMndZ" + ] + } +} +``` + + + Currently, event data emitted through CPIs cannot be directly subscribed to. + To access this data, you must fetch the complete transaction data and manually + decode the event information from the instruction data of the CPI. + diff --git a/docs/content/docs/features/index.mdx b/docs/content/docs/features/index.mdx new file mode 100644 index 0000000000..25a8abac54 --- /dev/null +++ b/docs/content/docs/features/index.mdx @@ -0,0 +1,5 @@ +--- +title: Features +description: Learn how to use additional features of the Anchor framework +index: true +--- diff --git a/docs/content/docs/features/meta.json b/docs/content/docs/features/meta.json new file mode 100644 index 0000000000..a4b2f5be20 --- /dev/null +++ b/docs/content/docs/features/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Additional Features", + "pages": ["declare-program", "errors", "events", "zero-copy"] +} diff --git a/docs/content/docs/features/zero-copy.mdx b/docs/content/docs/features/zero-copy.mdx new file mode 100644 index 0000000000..a01c7cb1ba --- /dev/null +++ b/docs/content/docs/features/zero-copy.mdx @@ -0,0 +1,404 @@ +--- +title: Zero Copy +description: + Learn how to use Anchor's zero-copy deserialization feature to handle large + account data in Solana programs. +--- + +## Usage + +Zero copy is a deserialization feature that allows programs to read account data +directly from memory without copying it. This is particularly useful when +working with large accounts. + +To use zero-copy add the `bytemuck` crate to your dependencies. Add the +`min_const_generics` feature to allow working with arrays of any size in your +zero-copy types. + +```toml title="Cargo.toml" +[dependencies] +bytemuck = { version = "1.20.0", features = ["min_const_generics"] } +anchor-lang = "0.30.1" +``` + +### Define a Zero Copy Account + +To define an account type that uses zero-copy, annotate the struct with +[`#[account(zero_copy)]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L319). + +```rust +// [!code highlight] +#[account(zero_copy)] +pub struct Data { + // 10240 bytes - 8 bytes account discriminator + pub data: [u8; 10232], +} +``` + +The `#[account(zero_copy)]` attribute automatically implements several traits +required for zero-copy deserialization: + +```rust +// [!code highlight:4] +#[derive(Copy, Clone)] +#[derive(bytemuck::Zeroable)] +#[derive(bytemuck::Pod)] +#[repr(C)] +struct Data { + // --snip-- +} +``` + +### Use AccountLoader for Zero Copy Accounts + +To deserialize a zero-copy account, use +[`AccountLoader<'info, T>`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts/account_loader.rs#L96-L100), +where `T` is the zero-copy account type defined with the `#[account(zero_copy)]` +attribute. + +For example: + +```rust +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:AccountLoader] + // [!code highlight] + pub zero_copy_account: AccountLoader<'info, Data>, +} +``` + +#### Initialize a Zero Copy Account + +The `init` constraint can be used with the `AccountLoader` type to create a +zero-copy account. + +```rust +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account( + // [!code word:init:1] + // [!code highlight:4] + init, + // 10240 bytes is max space to allocate with init constraint + space = 8 + 10232, + payer = payer, + )] + pub data_account: AccountLoader<'info, Data>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + + + The `init` constraint is limited to allocating a maximum of 10240 bytes due to + CPI limitations. Under the hood, the `init` constraint makes a CPI call to the + SystemProgram to create the account. + + +When initializing a zero-copy account for the first time, use +[`load_init`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts/account_loader.rs#L194-L214) +to get a mutable reference to the account data. The `load_init` method also sets +the account discriminator. + +```rust +pub fn initialize(ctx: Context) -> Result<()> { + // [!code word:load_init] + // [!code highlight] + let account = &mut ctx.accounts.data_account.load_init()?; + account.data = [1; 10232]; + Ok(()) +} +``` + +For accounts that require more than 10240 bytes, use the +[`zero`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/accounts/constraints.rs#L200-L217) +constraint instead of `init`. The `zero` constraint verifies the account has not +been initialized by checking that its discriminator has not been set. + +```rust +#[derive(Accounts)] +pub struct Initialize<'info> { + // [!code word:zero] + // [!code highlight] + #[account(zero)] + pub data_account: AccountLoader<'info, Data>, +} +``` + +With the `zero` constraint, you'll need to first create the account in a +separate instruction by directly calling the System Program. This allows you to +create accounts up to Solana's maximum account size of 10MB (10_485_760 bytes), +bypassing the CPI limitation. + +Just as before, use `load_init` to get a mutable reference to the account data +and set the account discriminator. Since 8 bytes are reserved for the account +discriminator, the maximum data size is 10_485_752 bytes (10MB - 8 bytes). + +```rust +pub fn initialize(ctx: Context) -> Result<()> { + // [!code word:load_init] + // [!code highlight] + let account = &mut ctx.accounts.data_account.load_init()?; + account.data = [1; 10_485_752]; + Ok(()) +} +``` + +#### Update a Zero Copy Account + +Use +[`load_mut`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts/account_loader.rs#L170-L190) +when you need mutable access to update an existing zero-copy account: + +```rust +#[derive(Accounts)] +pub struct Update<'info> { + // [!code highlight] + #[account(mut)] + pub data_account: AccountLoader<'info, Data>, +} +``` + +```rust +pub fn update(ctx: Context) -> Result<()> { + // [!code word:load_mut] + // [!code highlight] + let account = &mut ctx.accounts.data_account.load_mut()?; + account.data = [2; 10232]; + Ok(()) +} +``` + +#### Read a Zero Copy Account + +Use +[`load`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts/account_loader.rs#L153-L167) +to only read the account data. + +```rust +#[derive(Accounts)] +pub struct ReadOnly<'info> { + pub data_account: AccountLoader<'info, Data>, +} +``` + +```rust +pub fn read_only(ctx: Context) -> Result<()> { + // [!code word:load] + // [!code highlight] + let account = &ctx.accounts.data_account.load()?; + msg!("First 10 bytes: {:?}", &account.data[..10]); + Ok(()) +} +``` + +## Examples + +The examples below demonstrate two approaches for initializing zero-copy +accounts in Anchor: + +1. Using the `init` constraint to initialize the account in a single instruction +2. Using the `zero` constraint to initialize an account with data greater than + 10240 bytes + +### Zero Copy + + + + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1"); + +#[program] +pub mod zero_copy { + use super::*; + pub fn initialize(ctx: Context) -> Result<()> { + let account = &mut ctx.accounts.data_account.load_init()?; + account.data = [1; 10232]; + Ok(()) + } + + pub fn update(ctx: Context) -> Result<()> { + let account = &mut ctx.accounts.data_account.load_mut()?; + account.data = [2; 10232]; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account( + init, + // 10240 bytes is max space to allocate with init constraint + space = 8 + 10232, + payer = payer, + )] + pub data_account: AccountLoader<'info, Data>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Update<'info> { + #[account(mut)] + pub data_account: AccountLoader<'info, Data>, +} + +#[account(zero_copy)] +pub struct Data { + // 10240 bytes - 8 bytes account discriminator + pub data: [u8; 10232], +} +``` + + + + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { ZeroCopy } from "../target/types/zero_copy"; + +describe("zero-copy", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.ZeroCopy as Program; + const dataAccount = anchor.web3.Keypair.generate(); + + it("Is initialized!", async () => { + const tx = await program.methods + .initialize() + .accounts({ + dataAccount: dataAccount.publicKey, + }) + .signers([dataAccount]) + .rpc(); + console.log("Your transaction signature", tx); + + const account = await program.account.data.fetch(dataAccount.publicKey); + console.log("Account", account); + }); + + it("Update!", async () => { + const tx = await program.methods + .update() + .accounts({ + dataAccount: dataAccount.publicKey, + }) + .rpc(); + console.log("Your transaction signature", tx); + + const account = await program.account.data.fetch(dataAccount.publicKey); + console.log("Account", account); + }); +}); +``` + + + + +### Initialize Large Account + +When initializing an account that requires more than 10,240 bytes of space, you +must split the initialization into two steps: + +1. Create the account in a separate instruction invoking the System Program +2. Initialize the account data in your program instruction + +Note that the maximum Solana account size is 10MB (10_485_760 bytes), 8 bytes +are reserved for the account discriminator. + + + + +```rust title="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH"); + +#[program] +pub mod zero_copy_two { + use super::*; + pub fn initialize(ctx: Context) -> Result<()> { + let account = &mut ctx.accounts.data_account.load_init()?; + account.data = [1; 10_485_752]; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(zero)] + pub data_account: AccountLoader<'info, Data>, +} + +#[account(zero_copy)] +pub struct Data { + // 10240 bytes - 8 bytes account discriminator + pub data: [u8; 10_485_752], +} +``` + + + + +```ts title="test.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { ZeroCopyTwo } from "../target/types/zero_copy_two"; + +describe("zero-copy-two", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.ZeroCopyTwo as Program; + const dataAccount = anchor.web3.Keypair.generate(); + + it("Is initialized!", async () => { + const space = 10_485_760; // 10MB max account size + const lamports = + await program.provider.connection.getMinimumBalanceForRentExemption( + space, + ); + + // [!code highlight:7] + const createAccountInstruction = anchor.web3.SystemProgram.createAccount({ + fromPubkey: program.provider.publicKey, + newAccountPubkey: dataAccount.publicKey, + space, + lamports, + programId: program.programId, + }); + + // [!code highlight:6] + const initializeInstruction = await program.methods + .initialize() + .accounts({ + dataAccount: dataAccount.publicKey, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add( + createAccountInstruction, + initializeInstruction, + ); + + const tx = await program.provider.sendAndConfirm(transaction, [ + dataAccount, + ]); + + console.log("Your transaction signature", tx); + + const account = await program.account.data.fetch(dataAccount.publicKey); + console.log("Account", account); + }); +}); +``` + + + diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx new file mode 100644 index 0000000000..c09636b55f --- /dev/null +++ b/docs/content/docs/index.mdx @@ -0,0 +1,35 @@ +--- +title: Introduction +description: + Anchor is a development framework for building secure Solana programs (smart + contracts) +--- + +import { Download, PanelsTopLeft, Database, Terminal } from "lucide-react"; + +Anchor is the leading development framework for building Solana programs (smart +contracts) and simplifies the process of writing, testing, deploying, and +interacting with Solana programs. + +The Anchor framework helps developers build production-ready applications faster +while reducing potential vulnerabilities through built-in security features. + +## Where to start? + + + +} title='Installation' href='/docs/installation'> + +Step-by-step guide to install Anchor framework. Set up your local development +environment. + + + +} title='Quickstart' href='/docs/quickstart/solpg'> + +Quickstart guide to start building Solana programs with Anchor. Start building +directly in your browser. No installation required. + + + + diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx new file mode 100644 index 0000000000..cce96f8268 --- /dev/null +++ b/docs/content/docs/installation.mdx @@ -0,0 +1,641 @@ +--- +title: Installation +description: + Learn how to install Rust, the Solana CLI, and Anchor Framework on Windows + (WSL), Linux, or Mac. +--- + +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; + +This section covers the steps to set up your local environment for Solana +development. + +## Install Dependencies + +- Windows users must first install WSL (Windows subsystem for Linux) and then + install the dependencies specified in the Linux section below. +- Linux users should first install the dependencies specified in the Linux + section below. +- Mac users should start with the Rust installation instructions below. + + + + To develop Solana programs on Windows **you must use + [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)** (Windows + subsystem for Linux). All additional dependencies must be installed through the + Linux terminal. + + Once WSL is installed, install the dependencies specified in the Linux section + below before proceeding to install Rust, Solana CLI, and Anchor CLI. + + To install WSL, run the following command in Windows PowerShell: + + ```shell + wsl --install + ``` + + The install process will prompt you to create a default user account. + + ![WSL Install](/docs/installation/wsl-install.png) + + By default, WSL installs Ubuntu. You can open a Linux terminal by searching + "Ubuntu" in the Search bar. + + ![WSL Ubuntu](/docs/installation/wsl-ubuntu-search.png) + + If your Ubuntu terminal looks like the image below, you may encounter an issue + where `ctrl + v` (paste keyboard shortcut) doesn't work in the terminal. + + ![Ubuntu Terminal](/docs/installation/wsl-ubuntu-terminal-1.png) + + If you encounter this issue, open Windows Terminal by searching for "Terminal" + in the Search bar. + + ![Windows Terminal](/docs/installation/wsl-windows-terminal.png) + + Next, close the Windows Terminal and reopen a Linux terminal by searching for + Ubuntu again. The terminal should now look like the image below, where + `ctrl + v` (paste keyboard shortcut) works. + + ![Ubuntu Terminal](/docs/installation/wsl-ubuntu-terminal-2.png) + + If you are using VS Code, the + [WSL extension](https://code.visualstudio.com/docs/remote/wsl-tutorial) enables + you to use WSL and VS Code together. + + ![WSL Setup in VS Code](/docs/installation/wsl-vscode.png) + + You should then see the following in the VS Code status bar: + + ![WSL: Ubuntu](/docs/installation/wsl-vscode-ubuntu.png) + + Once you have WSL set up, all additional dependencies must be installed through + the Linux terminal. Install the dependencies specified in the Linux section + below before proceeding to install Rust, Solana CLI, and Anchor CLI. + + + + The following dependencies are required for the Anchor CLI installation. + + First, run the following command: + + ```shell + sudo apt-get update + ``` + + Next, install the following dependencies: + + ```shell + sudo apt-get install -y \ + build-essential \ + pkg-config \ + libudev-dev llvm libclang-dev \ + protobuf-compiler libssl-dev + ``` + + If you encounter the following error when installing `protobuf-compiler`, make + sure you first run `sudo apt-get update`: + + ``` + Package protobuf-compiler is not available, but is referred to by another package. + This may mean that the package is missing, has been obsoleted, or + is only available from another source + ``` + + + + + + + +### Install Rust + +Solana programs are written in the +[Rust programming language](https://www.rust-lang.org/). + +The recommended installation method for Rust is +[rustup](https://www.rust-lang.org/tools/install). + +Run the following command to install Rust: + +```shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +``` + +You should see the following message after the installation completes: + + + + +``` +Rust is installed now. Great! + +To get started you may need to restart your current shell. +This would reload your PATH environment variable to include +Cargo's bin directory ($HOME/.cargo/bin). + +To configure your current shell, you need to source +the corresponding env file under $HOME/.cargo. + +This is usually done by running one of the following (note the leading DOT): +. "$HOME/.cargo/env" # For sh/bash/zsh/ash/dash/pdksh +source "$HOME/.cargo/env.fish" # For fish +``` + + + + +Run the following command to reload your PATH environment variable to include +Cargo's bin directory: + +```shell +. "$HOME/.cargo/env" +``` + +To verify that the installation was successful, check the Rust version: + +```shell +rustc --version +``` + +You should see output similar to the following: + +``` +rustc 1.80.1 (3f5fd8dd4 2024-08-06) +``` + + + + +### Install the Solana CLI + +The Solana CLI provides all the tools required to build and deploy Solana +programs. + +Install the Solana CLI tool suite using the official install command: + +```shell +sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" +``` + +You can replace `stable` with the release tag matching the software version of +your desired release (i.e. `v2.0.3`), or use one of the three symbolic channel +names: `stable`, `beta`, or `edge`. + +If it is your first time installing the Solana CLI, you may see the following +message prompting you to add a PATH environment variable: + +``` +Close and reopen your terminal to apply the PATH changes or run the following in your existing shell: + +export PATH="/Users/test/.local/share/solana/install/active_release/bin:$PATH" +``` + + + + +If you are using a Linux or WSL terminal, you can add the PATH environment +variable to your shell configuration file by running the command logged from the +installation or by restarting your terminal. + +```shell +export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" +``` + + + + +If you're on Mac using `zsh`, running the default `export PATH` command logged +from the installation does not persist once you close your terminal. + +Instead, you can add the PATH to your shell configuration file by running the +following command: + +```shell +echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.zshrc +``` + +Then run the following command to refresh the terminal session or restart your +terminal. + +```shell +source ~/.zshrc +``` + + + + +To verify that the installation was successful, check the Solana CLI version: + +```shell +solana --version +``` + +You should see output similar to the following: + +``` +solana-cli 1.18.22 (src:9efdd74b; feat:4215500110, client:Agave) +``` + +You can view all available versions on the +[Agave Github repo](https://github.com/anza-xyz/agave/releases). + + + +Agave is the validator client from [Anza](https://www.anza.xyz/), formerly known +as Solana Labs validator client. + + + +To later update the Solana CLI to the latest version, you can use the following +command: + +```shell +agave-install update +``` + + + + +### Install Anchor CLI + +Anchor is a framework for developing Solana +programs. The Anchor framework leverages Rust macros to simplify the process of +writing Solana programs. + +There are two ways to install the Anchor CLI and tooling: + +1. Using Anchor Version Manager (AVM) - is the **recommended installation** + method since it simplifies updating Anchor versions in the future +2. Without AVM - this requires more a manual process to update Anchor versions + later + + + + +The Anchor version manager (AVM) allows you to install and manage different +Anchor versions on your system, including more easily updating Anchor versions +in the future. + +Install AVM with the following command: + +```shell +cargo install --git https://github.com/coral-xyz/anchor avm --force +``` + +Test to ensure AVM was installed and is accessible: + +```shell +avm --version +``` + +Install the latest version of Anchor CLI using AVM: + +```shell +avm install latest +avm use latest +``` + +Or install a specific version of the Anchor CLI by declaring which version you +want to install: + +```shell +avm install 0.30.1 +avm use 0.30.1 +``` + +> Don't forget to run the `avm use` command to declare which Anchor CLI version +> should be used on your system. +> +> - If you installed the `latest` version, run `avm use latest`. +> - If you installed the version `0.30.1`, run `avm use 0.30.1`. + + + + + +Install a specific version of the Anchor CLI with the following command: + +```shell +cargo install --git https://github.com/coral-xyz/anchor --tag v0.30.1 anchor-cli +``` + + + + +You may see the following warning during installation. However, it does not +affect the installation process. + + + + +``` +warning: unexpected `cfg` condition name: `nightly` + --> cli/src/lib.rs:1:13 + | +1 | #![cfg_attr(nightly, feature(proc_macro_span))] + | ^^^^^^^ + | + = help: expected names are: `clippy`, `debug_assertions`, `doc`, `docsrs`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows` + = help: consider using a Cargo feature instead + = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: + [lints.rust] + unexpected_cfgs = { level = "warn", check-cfg = ['cfg(nightly)'] } + = help: or consider adding `println!("cargo::rustc-check-cfg=cfg(nightly)");` to the top of the `build.rs` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: `anchor-cli` (lib) generated 1 warning +``` + + + + +To verify that the installation was successful, check the Anchor CLI version: + +```shell +anchor --version +``` + +You should see output similar to the following: + +``` +anchor-cli 0.30.1 +``` + +When installing the Anchor CLI on Linux or WSL, you may encounter this error: + +``` +error: could not exec the linker cc = note: Permission denied (os error 13) +``` + +If you see this error message, follow these steps: + +1. Install the dependencies listed in the Linux section at the top of this page. +2. Retry installing the Anchor CLI. + +#### Node.js and Yarn + +Node.js and Yarn are required to run the default Anchor project test file +(TypeScript) created with the `anchor init` command. (Rust test template is also +available using `anchor init --test-template rust`) + + + + +The recommended way to install node is using +[Node Version Manager (nvm)](https://github.com/nvm-sh/nvm). + +Install nvm using the following command: + +```shell +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash +``` + +Restart your terminal and verify that nvm is installed: + +```shell +command -v nvm +``` + +Next, use `nvm` to install node: + +```shell +nvm install node +``` + +To verify that the installation was successful, check the Node version: + +``` +node --version +``` + +You should see output similar to the following: + +``` +v22.7.0 +``` + + + + +Install Yarn: + +```shell +npm install --global yarn +``` + +To verify that the installation was successful, check the Yarn version: + +``` +yarn --version +``` + +You should the following output: + +``` +1.22.1 +``` + + + + +When running `anchor build`, if you encounter `error: not a directory` similar +following: + +``` +error: not a directory: '.../solana-release/bin/sdk/sbf/dependencies/platform-tools/rust/lib' +``` + +Try these solutions: + +1. Force install using the following command: + +```shell +cargo build-sbf --force-tools-install +``` + +2. If the above doesn't work, clear the Solana cache: + +```shell +rm -rf ~/.cache/solana/* +``` + +After applying either solution, attempt to run `anchor build` again. + +If you are on Linux or WSL and encounter the following errors when running +`anchor test` after creating a new Anchor project, it's may be due to missing +Node.js or Yarn: + +``` +Permission denied (os error 13) +``` + +``` +No such file or directory (os error 2) +``` + + + + +## Solana CLI Basics + +This section will walk through some common Solana CLI commands to get you +started. + + + + +### Solana Config + +To see your current config: + +```shell +solana config get +``` + +You should see output similar to the following: + +``` +Config File: /Users/test/.config/solana/cli/config.yml +RPC URL: https://api.mainnet-beta.solana.com +WebSocket URL: wss://api.mainnet-beta.solana.com/ (computed) +Keypair Path: /Users/test/.config/solana/id.json +Commitment: confirmed +``` + +The RPC URL and Websocket URL specific the Solana cluster the CLI will make +requests to. By default this will be mainnet-beta. + +You can update the Solana CLI cluster using the following commands: + +``` +solana config set --url mainnet-beta +solana config set --url devnet +solana config set --url localhost +solana config set --url testnet +``` + +You can also use the following short options: + +``` +solana config set -um # For mainnet-beta +solana config set -ud # For devnet +solana config set -ul # For localhost +solana config set -ut # For testnet +``` + +The Keypair Path specifies the location of the default wallet used by the Solana +CLI (to pay transaction fees and deploy programs). The default path is +`~/.config/solana/id.json`. The next step walks through how to generate a +keypair at the default location. + + + + +### Create Wallet + +To interact with the Solana network using the Solana CLI, you need a Solana +wallet funded with SOL. + +To generate a keypair at the default Keypair Path, run the following command: + +```shell +solana-keygen new +``` + +You should see output similar to the following: + +``` +Generating a new keypair + +For added security, enter a BIP39 passphrase + +NOTE! This passphrase improves security of the recovery seed phrae NOT the +keypair file itself, which is stored as insecure plain text + +BIP39 Passphrase (empty for none): + +Wrote new keypair to /Users/test/.config/solana/id.json +=========================================================================== +pubkey: 8dBTPrjnkXyuQK3KDt9wrZBfizEZijmmUQXVHpFbVwGT +=========================================================================== +Save this seed phrase and your BIP39 passphrase to recover your new keypair: +cream bleak tortoise ocean nasty game gift forget fancy salon mimic amazing +=========================================================================== +``` + + + +If you already have a file system wallet saved at the default location, this +command will **NOT** override it unless you explicitly force override using the +`--force` flag. + + + +Once a keypair is generated, you can get the address (public key) of the keypair +with the following command: + +```shell +solana address +``` + + + + +### Airdrop SOL + +Once you've set up your local wallet, request an airdrop of SOL to fund your +wallet. You need SOL to pay for transaction fees and to deploy programs. + +Set your cluster to the devnet: + +```shell +solana config set -ud +``` + +Then request an airdrop of devnet SOL: + +```shell +solana airdrop 2 +``` + +To check your wallet's SOL balance, run the following command: + +```shell +solana balance +``` + + + +The `solana airdrop` command is currently limited to 5 SOL per request on +devnet. Errors are likely due to rate limits. + +Alternatively, you can get devnet SOL using the +[Solana Web Faucet](https://faucet.solana.com). + + + + + +### Run Local Validator + +The Solana CLI comes with the +[test validator](https://docs.solanalabs.com/cli/examples/test-validator) +built-in. Running a local validator will allow you to deploy and test your +programs locally. + +In a separate terminal, run the following command to start a local validator: + +```shell +solana-test-validator +``` + +Make sure to update the Solana CLI config to localhost before commands. + +```shell +solana config set -ul +``` + + + diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json new file mode 100644 index 0000000000..76e6143359 --- /dev/null +++ b/docs/content/docs/meta.json @@ -0,0 +1,22 @@ +{ + "title": "docs", + "description": "The description of root folder", + "pages": [ + "---Getting Started---", + "installation", + "quickstart", + + "---Core Concepts---", + "basics", + "clients", + "features", + + "---SPL Tokens---", + "tokens", + + "---References---", + "references", + "updates", + "contribution" + ] +} diff --git a/docs/content/docs/quickstart/index.mdx b/docs/content/docs/quickstart/index.mdx new file mode 100644 index 0000000000..cefe81ed07 --- /dev/null +++ b/docs/content/docs/quickstart/index.mdx @@ -0,0 +1,7 @@ +--- +title: Quickstart +description: + Learn how to quickly get started with Anchor, the leading Solana smart + contract development framework. +index: true +--- diff --git a/docs/content/docs/quickstart/local.mdx b/docs/content/docs/quickstart/local.mdx new file mode 100644 index 0000000000..6a9b857ac1 --- /dev/null +++ b/docs/content/docs/quickstart/local.mdx @@ -0,0 +1,410 @@ +--- +title: Local Development +description: + Learn how to build Solana programs using the Anchor framework on your local + machine. +--- + +The Anchor framework is a tool that simplifies the process of building Solana +programs. Whether you're new to blockchain development or an experienced +programmer, Anchor simplifies the process of writing, testing, and deploying +Solana programs. + +In this section, we'll walk through: + +- Creating a new Anchor project +- Building and testing your program +- Deploying to Solana clusters +- Understanding the project file structure + +## Prerequisites + +For detailed installation instructions, visit the +[installation](/docs/installation) page. + +Before you begin, ensure you have the following installed: + +- Rust: The programming language for building Solana programs. +- Solana CLI: Command-line tool for Solana development. +- Anchor CLI: Command-line tool for the Anchor framework. + +To verify Anchor CLI installation, open your terminal and run: + +```shell filename="Terminal" +anchor --version +``` + +Expected output: + +```shell filename="Terminal" +anchor-cli 0.30.1 +``` + +## Getting Started + +This section covers the basic steps to create, build, and test your first local +Anchor program. + + + + + +### Create a new Project + +To start a new project, use the `anchor init` command followed by your project's +name. This command creates a new directory with the specified name and sets up a +default program and test file. + +```shell filename="Terminal" +anchor init my-program +``` + +Navigate to the new project directory and open it in your code editor. + +```shell filename="Terminal" copy +cd my-project +``` + +The default Anchor program is located at `/programs/my-project/src/lib.rs`. + + + + +The value in the `declare_id!` macro is the program ID, a unique identifier for +your program. + +By default, it is the public key of the keypair generated in +`/target/deploy/my_project-keypair.json`. + +```rust filename="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg"); + +#[program] +pub mod my_project { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + msg!("Greetings from: {:?}", ctx.program_id); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize {} +``` + + + + +The default Typescript test file is located at `/tests/my-project.ts`. + + + + +This file demonstrates how to invoke the default program's `initialize` +instruction in Typescript. + +```ts filename="my-project.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { MyProject } from "../target/types/my_project"; + +describe("my-project", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.MyProject as Program; + + it("Is initialized!", async () => { + // Add your test here. + const tx = await program.methods.initialize().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + + + + +If you prefer Rust for testing, initialize your project with the +`--test-template rust` flag. + +```shell +anchor init --test-template rust my-program +``` + +The Rust test file will be at `/tests/src/test_initialize.rs`. + + + + +```rust filename="test_initialize.rs" +use std::str::FromStr; + +use anchor_client::{ + solana_sdk::{ + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + }, + Client, Cluster, +}; + +#[test] +fn test_initialize() { + let program_id = "3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg"; + let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap(); + let payer = read_keypair_file(&anchor_wallet).unwrap(); + + let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed()); + let program_id = Pubkey::from_str(program_id).unwrap(); + let program = client.program(program_id).unwrap(); + + let tx = program + .request() + .accounts(my_program::accounts::Initialize {}) + .args(my_program::instruction::Initialize {}) + .send() + .expect(""); + + println!("Your transaction signature {}", tx); +} +``` + + + + + + + +### Build the Program + +Build the program by running `anchor build`. + +```shell filename="Terminal" copy +anchor build +``` + +The compiled program will be at `/target/deploy/my_project.so`. The content of +this file is what gets stored on the Solana network (as an executable account) +when you deploy your program. + + + + +### Test the Program + +To test the program, run `anchor test`. + +```shell filename="Terminal" copy +anchor test +``` + +By default, the `Anchor.toml` config file specifies the `localnet` cluster. When +developing on `localnet`, `anchor test` will automatically: + +1. Start a local Solana validator +2. Build and deploy your program to the local cluster +3. Run the tests in the `tests` folder +4. Stop the local Solana validator + +Alternatively, you can manually start a local Solana validator and run tests +against it. This is useful if you want to keep the validator running while you +iterate on your program. It allows you to inspect accounts and transaction logs +on the [Solana Explorer](https://explorer.solana.com/?cluster=custom) while +developing locally. + +Open a new terminal and start a local Solana validator by running the +`solana-test-validator` command. + +```shell filename="Terminal" copy +solana-test-validator +``` + +In a separate terminal, run the tests against the local cluster. Use the +`--skip-local-validator` flag to skip starting the local validator since it's +already running. + +```shell filename="Terminal" copy +anchor test --skip-local-validator +``` + + + + +### Deploy to Devnet + +By default, the `Anchor.toml` config file in an Anchor project specifies the +localnet cluster. + +```toml filename="Anchor.toml" {14} +[toolchain] + +[features] +resolution = true +skip-lint = false + +[programs.localnet] +my_program = "3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" +``` + +To deploy your program to devnet, change the `cluster` value to `Devnet`. + + + +Note that deploying to devnet requires your wallet to have enough SOL to cover +deployment cost. You can get devnet SOL using the +[Web Faucet](https://faucet.solana.com/). + + + +```diff +-cluster = "Localnet" ++cluster = "Devnet" +``` + +```toml filename="Anchor.toml" +[provider] +cluster = "Devnet" +wallet = "~/.config/solana/id.json" +``` + +Now when you run `anchor deploy`, your program will be deployed to the devnet +cluster. The `anchor test` command will also use the cluster specified in the +`Anchor.toml` file. + +```shell +anchor deploy +``` + +To deploy to mainnet, simply update the `Anchor.toml` file to specify the +mainnet cluster. + +```toml filename="Anchor.toml" +[provider] +cluster = "Mainnet" +wallet = "~/.config/solana/id.json" +``` + + + + +### Update the Program + +Solana programs can be updated by redeploying the program to the same program +ID. + +To update a program, simply make changes to your program's code and run the +`anchor build` command to generated an updated `.so` file. + +```shell +anchor build +``` + +Then run the `anchor deploy` command to redeploy the updated program. + +```shell +anchor deploy +``` + + + + +### Close the Program + +To reclaim the SOL allocated to a program account, you can close your Solana +program. + +To close a program, use the `solana program close ` command. For +example: + +```shell +solana program close 3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg --bypass-warning +``` + +Note that once a program is closed, the program ID cannot be reused to deploy a +new program. + + + + + +## Project File Structure + +Below is an overview of default file structure in an Anchor workspace: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Programs Folder + +The `/programs` directory contains your project's Anchor programs. A single +workspace can contain multiple programs. + +### Tests Folder + +The `/tests` directory contains test files for your project. A default test file +is created for you when you create your project. + +### Target Folder + +The `/target` directory contains build outputs. The main subfolders include: + +- `/deploy`: Contains the keypair and program binary for your programs. +- `/idl`: Contains the JSON IDL for your programs. +- `/types`: Contains the TypeScript type for the IDL. + +### Anchor.toml File + +The `Anchor.toml` file configures workspace settings for your project. + +### .anchor Folder + +Includes a `program-logs` file that contains transaction logs from the last run +of test files. + +### App Folder + +The `/app` folder is an empty folder that can be optionally used for your +frontend code. diff --git a/docs/content/docs/quickstart/meta.json b/docs/content/docs/quickstart/meta.json new file mode 100644 index 0000000000..74e1fd3149 --- /dev/null +++ b/docs/content/docs/quickstart/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Quickstart", + "pages": ["solpg", "local"] +} diff --git a/docs/content/docs/quickstart/solpg.mdx b/docs/content/docs/quickstart/solpg.mdx new file mode 100644 index 0000000000..de16677b9d --- /dev/null +++ b/docs/content/docs/quickstart/solpg.mdx @@ -0,0 +1,462 @@ +--- +title: Solana Playground +description: + Learn how to build your first Solana program using the Anchor framework + directly in your browser. +--- + +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! + +Solana Playground (Solpg) is a browser-based development environment that allows +you to quickly develop, deploy, and test Solana programs! + +## Getting Started + +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 Connected](/docs/quickstart/pg-not-connected.png) + +#### 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 Wallet](/docs/quickstart/pg-create-wallet.png) + +You should now see your wallet's address, SOL balance, and connected cluster +(devnet by default) at the bottom of the window. + +![Connected](/docs/quickstart/pg-connected.png) + + + Your Playground Wallet will be saved in your browser's local storage. Clearing + your browser cache will remove your saved wallet. + + +Some definitions you may find helpful: + +- _wallet address_: a public key that serves as your unique identity on the + Solana blockchain. Just like an email address is used to receive emails, your + wallet address is used to receive SOL. +- _connection cluster_: a network of Solana nodes (computers running Solana + validator client). Devnet is the cluster for developer testing. + + + + +### 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 on the network where we 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: + +```shell filename="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](https://faucet.solana.com/). + +- 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 Airdrop](/docs/quickstart/faucet-airdrop.gif) + + + + + +### 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 Project](/docs/quickstart/pg-new-project.gif) + +You'll see a new project created with the program code in the `src/lib.rs` file. + +```rust filename="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, 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 +} +``` + + + + +For now, we'll only cover the high-level overview of the program code: + +- The `declare_id!` macro specifies the on-chain address of your program. It + will be automatically updated when we build the program in the next step. + + ```rust + declare_id!("11111111111111111111111111111111"); + ``` + +- The `#[program]` attribute annotates a module containing functions that + represent the program's instructions. + + ```rust + #[program] + mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); // Message will show up in the tx logs + Ok(()) + } + } + ``` + + In this example, the `initialize` instruction takes two parameters: + + 1. `ctx: Context` - Provides access to the accounts required for + this instruction, as specified in the `Initialize` struct. + 2. `data: u64` - An instruction parameter that will be passed in when the + instruction is invoked. + + The function body sets the `data` field of `new_account` to the provided + `data` argument and then prints a message to the program logs. + +- The `#[derive(Accounts)]` macro is used to annotate a struct that specifies + the accounts required for a particular instruction, where each field + represents a separate account. + + The field types (ex. `Signer<'info>`) and constraints (ex. `#[account(mut)]`) + are used by Anchor to automatically handle common security checks related to + account validation. + + ```rust + #[derive(Accounts)] + pub struct Initialize<'info> { + #[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>, + } + ``` + +- The `#[account]` attribute is used to annotate a struct that represents the + data structure of an account created and owned by the program. + + ```rust + #[account] + pub struct NewAccount { + data: u64 + } + ``` + + + + + + +### Build and Deploy Program + +To build the program, simply run `build` in the terminal. + +```shell filename="Terminal" +build +``` + +Notice that the address in `declare_id!()` has been updated. This is your +program's on-chain address. + + + + +```shell filename="Terminal" +$ build +Building... +Build successful. Completed in 1.46s. +``` + + + + +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](https://faucet.solana.com/). + +```shell filename="Terminal" +deploy +``` + + + + +```shell filename="Terminal" +$ deploy +Deploying... This could take a while depending on the program size and network conditions. +Warning: 1 transaction not confirmed, retrying... +Deployment successful. Completed in 19s. +``` + + + + +Alternatively, you can also use the `Build` and `Deploy` buttons on the +left-side panel. + +![Build and Deploy](/docs/quickstart/pg-build-deploy.png) + +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. + +```ts filename="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. + +```shell filename="Terminal" +test +``` + +You should see an output indicating that the test passed successfully. + + + + +```shell filename="Terminal" +$ test +Running tests... + hello_anchor.test.ts: + hello_anchor + Use 'solana confirm -v 3TewJtiUz1EgtT88pLJHvKFzqrzDNuHVi8CfD2mWmHEBAaMfC5NAaHdmr19qQYfTiBace6XUmADvR4Qrhe8gH5uc' to see the logs + On-chain data is: 42 + ✔ initialize (961ms) + 1 passing (963ms) +``` + + + + +You can also use the `Test` button on the left-side panel. + +![Run Test](/docs/quickstart/pg-test.png) + +You can then view the transaction logs by running the `solana confirm -v` +command and specifying the transaction hash (signature) from the test output: + +```shell filename="Terminal" +solana confirm -v [TxHash] +``` + +For example: + +```shell filename="Terminal" +solana confirm -v 3TewJtiUz1EgtT88pLJHvKFzqrzDNuHVi8CfD2mWmHEBAaMfC5NAaHdmr19qQYfTiBace6XUmADvR4Qrhe8gH5uc +``` + + + + +```shell filename="Terminal" {29-35} +$ solana confirm -v 3TewJtiUz1EgtT88pLJHvKFzqrzDNuHVi8CfD2mWmHEBAaMfC5NAaHdmr19qQYfTiBace6XUmADvR4Qrhe8gH5uc +RPC URL: https://api.devnet.solana.com +Default Signer: Playground Wallet +Commitment: confirmed + +Transaction executed in slot 308150984: + Block Time: 2024-06-25T12:52:05-05:00 + Version: legacy + Recent Blockhash: 7AnZvY37nMhCybTyVXJ1umcfHSZGbngnm4GZx6jNRTNH + Signature 0: 3TewJtiUz1EgtT88pLJHvKFzqrzDNuHVi8CfD2mWmHEBAaMfC5NAaHdmr19qQYfTiBace6XUmADvR4Qrhe8gH5uc + Signature 1: 3TrRbqeMYFCkjsxdPExxBkLAi9SB2pNUyg87ryBaTHzzYtGjbsAz9udfT9AkrjSo1ZjByJgJHBAdRVVTZv6B87PQ + Account 0: srw- 3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R (fee payer) + Account 1: srw- c7yy8zdP8oeZ2ewbSb8WWY2yWjDpg3B43jk3478Nv7J + Account 2: -r-- 11111111111111111111111111111111 + Account 3: -r-x 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r + Instruction 0 + Program: 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r (3) + Account 0: c7yy8zdP8oeZ2ewbSb8WWY2yWjDpg3B43jk3478Nv7J (1) + Account 1: 3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R (0) + Account 2: 11111111111111111111111111111111 (2) + Data: [175, 175, 109, 31, 13, 152, 155, 237, 42, 0, 0, 0, 0, 0, 0, 0] + Status: Ok + Fee: ◎0.00001 + Account 0 balance: ◎5.47001376 -> ◎5.46900152 + Account 1 balance: ◎0 -> ◎0.00100224 + Account 2 balance: ◎0.000000001 + Account 3 balance: ◎0.00139896 + Log Messages: + Program 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r invoke [1] + Program log: Instruction: Initialize + Program 11111111111111111111111111111111 invoke [2] + Program 11111111111111111111111111111111 success + Program log: Changed data to: 42! + Program 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r consumed 5661 of 200000 compute units + Program 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r success + +Confirmed +``` + + + + +Alternatively, you can view the transaction details on +[SolanaFM](https://solana.fm/) or +[Solana Explorer](https://explorer.solana.com/?cluster=devnet) by searching for +the transaction signature (hash). + + + 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!()`: + +```shell filename="Terminal" +solana program close [ProgramID] +``` + +For example: + +```shell filename="Terminal" +solana program close 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r +``` + + + + +```shell filename="Terminal" +$ solana program close 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r +Closed Program Id 2VvQ11q8xrn5tkPNyeraRsPaATdiPx8weLAD8aD4dn2r, 2.79511512 SOL reclaimed +``` + + + + + + + +Only the upgrade authority of the program can close it. The upgrade authority is +set when the program is deployed, and it's the only account with permission to +modify or close the program. If the upgrade authority is revoked, then the +program becomes immutable and can never be closed or upgraded. + +When deploying programs on Solana Playground, your Playground wallet is the +upgrade authority for all your programs. + + + + +Congratulations! You've just built and deployed your first Solana program using +the Anchor framework! + + + diff --git a/docs/content/docs/references/account-constraints.mdx b/docs/content/docs/references/account-constraints.mdx new file mode 100644 index 0000000000..8717f5b737 --- /dev/null +++ b/docs/content/docs/references/account-constraints.mdx @@ -0,0 +1,396 @@ +--- +title: Account Constraints +description: Anchor Account Constraints Examples +--- + +Minimal reference examples for Anchor account +[constraints](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html). + +See the account constraints +[source code](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/accounts/constraints.rs) +for implementation details. + +## Normal Constraints + +### `#[account(signer)]` + +```rust title="attribute" +#[account(signer)] +#[account(signer @ )] +``` + +Description: Checks the given account signed the transaction. Consider using the +Signer type if you would only have this constraint on the account. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/signer) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/signer) + +```rust title="attribute" +#[account(signer)] +#[account(signer @ )] +``` + +### `#[account(mut)]` + +Description: Checks the given account is mutable. Makes anchor persist any state +changes. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/mut) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/mut) + +```rust title="attribute" +#[account(mut)] +#[account(mut @ )] +``` + +### `#[account(init)]` + +Description: Creates the account via a CPI to the system program and initializes +it (sets its account discriminator). +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/init) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/init) + +```rust title="attribute" +#[account( + init, + payer = , + space = +)] +``` + +### `#[account(init_if_needed)]` + +Description: Same as init but only runs if the account does not exist yet. +Requires init-if-needed cargo feature. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/init_if_needed) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/init_if_needed) + +```rust title="attribute" +#[account( + init_if_needed, + payer = +)] + +#[account( + init_if_needed, + payer = , + space = +)] +``` + +### `#[account(seeds, bump)]` + +Description: Checks that given account is a PDA derived from the currently +executing program, the seeds, and if provided, the bump. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/seed-bump) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/seed-bump) + +```rust title="attribute" +#[account( + seeds = , + bump +)] + +#[account( + seeds = , + bump, + seeds::program = +)] + +#[account( + seeds = , + bump = +)] + +#[account( + seeds = , + bump = , + seeds::program = +)] +``` + +### `#[account(has_one = target)]` + +Description: Checks the target field on the account matches the key of the +target field in the Accounts struct. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/has_one) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/has_one) + +```rust title="attribute" +#[account( + has_one = +)] + +#[account( + has_one = @ +)] +``` + +### `#[account(address = expr)]` + +Description: Checks the account key matches the pubkey. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/address) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/address) + +```rust title="attribute" +#[account(address = )] +#[account(address = @ )] +``` + +### `#[account(owner = expr)]` + +Description: Checks the account owner matches expr. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/owner) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/owner) + +```rust title="attribute" +#[account(owner = )] +#[account(owner = @ )] +``` + +### `#[account(executable)]` + +Description: Checks the account is executable (i.e. the account is a program). +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/executable) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/executable) + +```rust title="attribute" +#[account(executable)] +``` + +### `#[account(zero)]` + +Description: Checks the account discriminator is zero. Use for accounts larger +than 10 Kibibyte. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/zero) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/zero) + +```rust title="attribute" +#[account(zero)] +``` + +### `#[account(close = target)]` + +Description: Closes the account by sending lamports to target and resetting +data. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/close) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/close) + +```rust title="attribute" +#[account(close = )] +``` + +### `#[account(constraint = expr)]` + +Description: Custom constraint that checks whether the given expression +evaluates to true. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/constraint) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/constraint) + +```rust title="attribute" +#[account(constraint = )] +#[account( + constraint = @ +)] +``` + +### `#[account(realloc)]` + +Description: Used to realloc program account space at the beginning of an +instruction. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/realloc) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/realloc) + +```rust title="attribute" +#[account( + realloc = , + realloc::payer = , + realloc::zero = +)] +``` + +## SPL Constraints + +### `#[account(token::*)]` + +Description: Create or validate token accounts with specified mint and +authority. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/token) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/token) + +```rust title="attribute" +#[account( + token::mint = , + token::authority = +)] + +#[account( + token::mint = , + token::authority = , + token::token_program = +)] +``` + +### `#[account(mint::*)]` + +Description: Create or validate mint accounts with specified parameters. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/mint) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/mint) + +```rust title="attribute" +#[account( + mint::authority = , + mint::decimals = +)] + +#[account( + mint::authority = , + mint::decimals = , + mint::freeze_authority = +)] +``` + +### `#[account(associated_token::*)]` + +Description: Create or validate associated token accounts. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/associated_token) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/associated_token) + +```rust title="attribute" +#[account( + associated_token::mint = , + associated_token::authority = +)] + +#[account( + associated_token::mint = , + associated_token::authority = , + associated_token::token_program = +)] +``` + +### `#[account(*::token_program = expr)]` + +Description: The token_program can optionally be overridden. +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/token_program) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/token_program) + +```rust title="attribute" +#[account(*::token_program = )] +``` + +## Instruction Attribute + +### `#[instruction(...)]` + +Description: You can access the instruction's arguments with the +`#[instruction(..)]` attribute. You must list them in the same order as in the +instruction handler but you can omit all arguments after the last one you need. +Skipping arguments will result in an error. + +Examples: +[Github](https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/instruction) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-constraints/instruction) + +```rust title="snippet" +// [!code word:input] +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context, input: String) -> Result<()> { + // --snip-- + } +} + +#[derive(Accounts)] +// [!code highlight] +#[instruction(input: String)] +pub struct Initialize<'info> { + #[account( + init, + payer = signer, + space = 8 + 4 + input.len(), + )] + pub new_account: Account<'info, DataAccount>, + // --snip-- +} +``` + +Valid Usage: + +```rust title="snippet" +// [!code word:input_one] +// [!code word:input_two] +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context, input_one: String, input_two: String) -> Result<()> { + // --snip-- + } +} + +#[derive(Accounts)] +// [!code highlight] +#[instruction(input_one: String, input_two: String)] +pub struct Initialize<'info> { + // --snip-- +} +``` + +```rust title="snippet" +// [!code word:input_one] +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context, input_one: String, input_two: String) -> Result<()> { + // --snip-- + } +} + +#[derive(Accounts)] +// [!code highlight] +#[instruction(input_one: String)] +pub struct Initialize<'info> { + // --snip-- +} +``` + +Invalid Usage, will result in an error: + +```rust title="snippet" +// [!code word:input_two] +#[program] +pub mod example { + use super::*; + + pub fn initialize(ctx: Context, input_one: String, input_two: String) -> Result<()> { + // --snip-- + } +} + +#[derive(Accounts)] +// [!code highlight] +#[instruction(input_two: String)] +pub struct Initialize<'info> { + // --snip-- +} +``` diff --git a/docs/content/docs/references/account-types.mdx b/docs/content/docs/references/account-types.mdx new file mode 100644 index 0000000000..3e668b0aca --- /dev/null +++ b/docs/content/docs/references/account-types.mdx @@ -0,0 +1,232 @@ +--- +title: Account Types +description: Anchor Account Type Examples +--- + +Minimal reference examples for Anchor +[account types](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/index.html). + +See the account types +[source code](https://github.com/coral-xyz/anchor/tree/v0.30.1/lang/src/accounts) +for implementation details. + +## Account Types + +### `Account<'info, T>` + +Description: Account container that checks ownership on deserialization +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Account) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Account) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word: Account] + // [!code highlight] + pub account: Account<'info, CustomAccountType>, +} + +#[account] +pub struct CustomAccountType { + data: u64, +} +``` + +### `AccountInfo<'info>` + +Description: AccountInfo can be used as a type but Unchecked Account should be +used instead +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/AccountInfo) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/AccountInfo) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + /// CHECK: AccountInfo is an unchecked account + // [!code word:AccountInfo] + // [!code highlight] + pub unchecked_account: AccountInfo<'info>, +} +``` + +### `AccountLoader<'info, T>` + +Description: Type facilitating on demand zero copy deserialization +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/AccountLoader) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/AccountLoader) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:AccountLoader] + // [!code highlight] + pub account: AccountLoader<'info, ZeroCopyAccountType>, +} + +#[account(zero_copy)] +pub struct ZeroCopyAccountType { + data: u64, +} +``` + +### `Box>` + +Description: Box type to save stack space +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Box) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Box) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:Box] + // [!code highlight] + pub account: Box>, +} +``` + +### `Interface<'info, T>` + +Description: Type validating that the account is one of a set of given +Programs +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Interface) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Interface) + +```rust title="snippet" +// Token program or Token2022 program +use anchor_spl::token_interface::TokenInterface; + +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word: Interface] + // [!code highlight] + pub program: Interface<'info, TokenInterface>, +} +``` + +### `InterfaceAccount<'info, T>` + +Description: Account container that checks ownership on deserialization +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/InterfaceAccount) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/InterfaceAccount) + +```rust title="snippet" +// Token program or Token2022 program Mint/TokenAccount +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; + +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:InterfaceAccount] + // [!code highlight:2] + pub mint: InterfaceAccount<'info, Mint>, + pub token: InterfaceAccount<'info, TokenAccount>, + pub program: Interface<'info, TokenInterface>, +} +``` + +### `Option>` + +Description: Option type for optional accounts +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Option) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Option) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:Option] + // [!code highlight] + pub account: Option>, +} +``` + +### `Program<'info, T>` + +Description: Type validating that the account is the given Program +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Program) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Program) + +```rust title="snippet" +use anchor_spl::token::Token; + +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:Program] + // [!code highlight:2] + pub system_program: Program<'info, System>, + pub token_program: Program<'info, Token>, +} +``` + +### `Signer<'info>` + +Description: Type validating that the account signed the transaction +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Signer) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Signer) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:Signer] + // [!code highlight] + pub signer: Signer<'info>, +} +``` + +### `SystemAccount<'info>` + +Description: Type validating that the account is owned by the system program +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/SystemAccount) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/SystemAccount) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:SystemAccount] + // [!code highlight] + pub account: SystemAccount<'info>, +} +``` + +### `Sysvar<'info, T>` + +Description: Type validating that the account is a sysvar and deserializing it +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/Sysvar) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/Sysvar) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // [!code word:Sysvar] + // [!code highlight:2] + pub rent: Sysvar<'info, Rent>, + pub clock: Sysvar<'info, Clock>, +} +``` + +### `UncheckedAccount<'info>` + +Description: Explicit wrapper for AccountInfo types to emphasize that no checks +are performed +Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/main/account-types/UncheckedAccount) +| +[Solpg](https://beta.solpg.io/https://github.com/solana-developers/anchor-examples/tree/main/account-types/UncheckedAccount) + +```rust title="snippet" +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + // CHECK: No checks are performed + // [!code word:UncheckedAccount] + // [!code highlight] + pub account: UncheckedAccount<'info>, +} +``` diff --git a/docs/src/pages/docs/manifest.md b/docs/content/docs/references/anchor-toml.mdx similarity index 68% rename from docs/src/pages/docs/manifest.md rename to docs/content/docs/references/anchor-toml.mdx index 4285bfb597..d8c54d60a5 100644 --- a/docs/src/pages/docs/manifest.md +++ b/docs/content/docs/references/anchor-toml.mdx @@ -1,6 +1,6 @@ --- -title: Anchor.toml Reference -description: Anchor - Anchor.toml Reference +title: Anchor.toml Configuration +description: Anchor workspace config reference documentation --- ## provider (required) @@ -17,7 +17,8 @@ wallet = "~/.config/solana/id.json" # The keypair used for all commands. ## scripts (required for testing) -Scripts that can be run with `anchor run