Skip to content

Commit

Permalink
update quickstart section with codehike (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZYJLiu authored Mar 1, 2025
1 parent a9ba75c commit 21ebb4b
Show file tree
Hide file tree
Showing 7 changed files with 708 additions and 394 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ coverage
# yalc
.yalc
yalc.lock

# docskit component syntax reference
content/docs/docskit.mdx
140 changes: 90 additions & 50 deletions content/docs/intro/quick-start/cross-program-invocation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ this section, with just the PDA section completed.
### Modify Update Instruction

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

Begin by updating the `lib.rs` file to bring into scope items from the
`system_program` module.
Expand All @@ -57,7 +57,7 @@ use anchor_lang::system_program::{transfer, Transfer};
</Accordion>
</Accordions>

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

Expand Down Expand Up @@ -102,9 +102,9 @@ pub struct Update<'info> {
</Accordion>
<Accordion title="Explanation">

We're adding a new account called `vault_account` to our `Update` struct. This
account serves as a program-controlled "vault" that will receive SOL from users
when they update their messages.
We're adding a new account called `vault_account` to our _rs`Update`_ struct.
This account serves as a program-controlled "vault" that will receive SOL from
users when they update their messages.

By using a PDA for the vault, we create a program-controlled account unique to
each user, enabling us to manage user funds within our program's logic.
Expand All @@ -114,10 +114,10 @@ each user, enabling us to manage user funds within our program's logic.
Key aspects of the `vault_account`:

- The address of the account is a PDA derived using seeds
`[b"vault", user.key().as_ref()]`
_rs`[b"vault", user.key().as_ref()]`_
- As a PDA, it has no private key, so only our program can "sign" for the
address when performing CPIs
- As a `SystemAccount` type, it's owned by the System Program like regular
- As a _rs`SystemAccount`_ type, it's owned by the System Program like regular
wallet accounts

This setup allows our program to:
Expand All @@ -134,18 +134,42 @@ this PDA in a CPI.
Next, implement the CPI logic in the `update` instruction to transfer 0.001 SOL
from the user's account to the vault account.

<WithNotes>

```rs title="lib.rs"
// !tooltip[/transfer_accounts/] transfer_accounts
let transfer_accounts = Transfer {
from: ctx.accounts.user.to_account_info(),
to: ctx.accounts.vault_account.to_account_info(),
};

// !tooltip[/cpi_context/] cpi_context
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
transfer_accounts,
);

// !tooltip[/transfer/] transfer
transfer(cpi_context, 1_000_000)?;
```

### !transfer_accounts

The _rs`Transfer`_ struct specifies the required accounts for the System
Program's transfer instruction.

### !cpi_context

The _rs`CpiContext`_ struct is used to specify the program and accounts for a
Cross Program Invocation (CPI).

### !transfer

The _rs`transfer()`_ function is used to invoke the System Program's transfer
instruction.

</WithNotes>

<Accordions>
<Accordion title="Diff">

Expand Down Expand Up @@ -176,8 +200,8 @@ invoke the System Program's `transfer` instruction. This demonstrates how to
perform a CPI from within our program, enabling the composability of Solana
programs.

The `Transfer` struct specifies the required accounts for the System Program's
transfer instruction:
The _rs`Transfer`_ struct specifies the required accounts for the System
Program's transfer instruction:

- `from` - The user's account (source of funds)
- `to` - The vault account (destination of funds)
Expand All @@ -189,10 +213,10 @@ transfer instruction:
};
```

The `CpiContext` specifies:
The _rs`CpiContext`_ specifies:

- The program to be invoked (System Program)
- The accounts required in the CPI (defined in the `Transfer` struct)
- The accounts required in the CPI (defined in the _rs`Transfer`_ struct)

```rs title="lib.rs"
let cpi_context = CpiContext::new(
Expand All @@ -211,33 +235,35 @@ Program, passing in the:
transfer(cpi_context, 1_000_000)?;
```

---
<Callout>

The setup for a CPI matches how client-side instructions are built, where we
specify the program, accounts, and instruction data for a particular instruction
to invoke. When our program's `update` instruction is invoked, it internally
invokes the System Program's transfer instruction.

</Callout>

</Accordion>
</Accordions>

Rebuild the program.

```shell title="Terminal"
build
```terminal
$ build
```

</Step>
<Step>

### Modify Delete Instruction

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

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

```rs title="lib.rs"
#[account(
Expand Down Expand Up @@ -293,11 +319,14 @@ SOL back to the user.
</Accordion>
</Accordions>

<WithNotes>

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

```rs title="lib.rs"
let user_key = ctx.accounts.user.key();
// !tooltip[/signer_seeds/] signer_seeds
let signer_seeds: &[&[&[u8]]] =
&[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];

Expand All @@ -308,12 +337,30 @@ let transfer_accounts = Transfer {
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
transfer_accounts,
// !tooltip[/with_signer/] with_signer
).with_signer(signer_seeds);
transfer(cpi_context, ctx.accounts.vault_account.lamports())?;
```

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

The _rs`signer_seeds`_ specify the optional seeds and bump seeds used to derive
the PDA.

### !with_signer

The _rs`with_signer()`_ method passes the signer seeds with the CPI.

This allows a program to "sign" for a PDA that was derived using its program ID.

During instruction processing, the runtime will verify that the provided signer
seeds correctly derive to the PDA's address. If verified, that PDA account will
be marked as a signer for the duration of the CPI.

</WithNotes>

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

<Accordions>
<Accordion title="Diff">
Expand Down Expand Up @@ -356,8 +403,8 @@ let signer_seeds: &[&[&[u8]]] =
&[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];
```

The `Transfer` struct specifies the required accounts for the System Program's
transfer instruction:
The _rs`Transfer`_ struct specifies the required accounts for the System
Program's transfer instruction:

- from: The vault account (source of funds)
- to: The user's account (destination of funds)
Expand All @@ -369,7 +416,7 @@ transfer instruction:
};
```

The `CpiContext` specifies:
The _rs`CpiContext`_ specifies:

- The program to be invoked (System Program)
- The accounts involved in the transfer (defined in the Transfer struct)
Expand All @@ -382,8 +429,8 @@ The `CpiContext` specifies:
).with_signer(signer_seeds);
```

The transfer function then invokes the transfer instruction on the System
Program, passing:
The _rs`transfer()`_ function then invokes the transfer instruction on the
System Program, passing:

- The `cpi_context` (program, accounts, and PDA signer)
- The amount to transfer (the entire balance of the vault account)
Expand All @@ -402,8 +449,8 @@ transfer of all funds from the vault back to the user.

Rebuild the program.

```shell title="Terminal"
build
```terminal
$ build
```

</Step>
Expand All @@ -415,27 +462,20 @@ After making these changes, we need to redeploy our updated program. This
ensures that our modified program is available for testing. On Solana, updating
a program simply requires deploying the program at the same program ID.

<Callout type="info">
<Callout>

Ensure your Playground wallet has devnet SOL. Get devnet SOL from the
[Solana Faucet](https://faucet.solana.com/).

</Callout>

```shell title="Terminal"
deploy
```

<Accordions>
<Accordion title="Output">

```bash
```terminal
$ deploy
Deploying... This could take a while depending on the program size and network conditions.
Deployment successful. Completed in 17s.
```

</Accordion>
<Accordions>
<Accordion title="Explanation">

Only the upgrade authority of the program can update it. The upgrade authority
Expand Down Expand Up @@ -496,18 +536,23 @@ describe("pda", () => {

#### Modify Update Test

Then, update the update instruction to include the `vaultAccount`.
<WithMentions>

Then, update the update instruction to include the [`vaultAccount`](mention:one)

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

</WithMentions>

<Accordions>
<Accordion title="Diff">

Expand All @@ -526,18 +571,23 @@ const transactionSignature = await program.methods

#### Modify Delete Test

Then, update the delete instruction to include the `vaultAccount`.
<WithMentions>

Then, update the delete instruction to include the [`vaultAccount`](mention:one)

```ts title="anchor.test.ts" {5}
const transactionSignature = await program.methods
.delete()
.accounts({
messageAccount: messagePda,
// !mention one
vaultAccount: vaultPda,
})
.rpc({ commitment: "confirmed" });
```

</WithMentions>

<Accordions>
<Accordion title="Diff">

Expand All @@ -562,14 +612,7 @@ const transactionSignature = await program.methods
After making these changes, run the tests to ensure everything is working as
expected:

```shell title="Terminal"
test
```
<Accordions>
<Accordion title="Output">
```bash
```terminal
$ test
Running tests...
anchor.test.ts:
Expand All @@ -594,9 +637,6 @@ Running tests...
3 passing (3s)
```

</Accordion>
</Accordions>
You can then inspect the SolanaFM links to view the transaction details, where
you'll find the CPIs for the transfer instructions within the update and delete
instructions.
Expand Down
Loading

0 comments on commit 21ebb4b

Please sign in to comment.