3

Youtube solana pda

I've been watching this tutorial for a while trying to build that muscle memory and replicate the code as I understand the concepts, the rust side is not that difficult, maybe when the seeds are explained but overall, I can do that code in about 10 min.

use anchor_lang::prelude::*;

declare_id!("kYXFfefS45EGgtBwxkdCujNJXUebrKG88okdWc34aDA");

#[program]
pub mod pda_example {
    use super::*;

    //ix create
    pub fn create_ledger(
        ctx: Context<Create>, //<-struct name
        color: String,
    ) -> Result<()> {
        let ledger_account = &mut ctx.accounts.ledger_account;

        ledger_account.color = color;
        ledger_account.balance = 0;

        Ok(())

    }

    //ix modify
    pub fn modify_ledger(
        ctx: Context<Modify>,
        new_balance: u32,
    ) -> Result<()> {

        let ledger_account = &mut ctx.accounts.ledger_account;
        ledger_account.balance = new_balance;
        Ok(())
    }
}

//structs
#[derive(Accounts)]
#[instruction(color: String)]
pub struct Create<'info> {
    #[account(
        init,
        payer = wallet,
        space = 80,
        seeds = [
            wallet.key().as_ref(),
            b"_",
            color.as_ref()
        ],
        bump
    )]
    pub ledger_account: Account<'info, Ledger>,
    #[account(mut)]
    pub wallet: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Modify<'info> {
    #[account(mut)]
    pub ledger_account: Account<'info, Ledger>,
    #[account(mut)]
    pub wallet: Signer<'info>,
}

//data account
#[account]
pub struct Ledger {
    color: String,
    balance: u32,
}

The TS part though has been confusing as soon as start the createLedgerAccount function is where I have trouble understanding. For example in the await program.methods.createLedger why is that method called like that? Is that something part of docs?

Then, when adding the try and catch is something more related to solana/anchor or more into TypeScript?

import * as anchor from "@project-serum/anchor";
import { publicKey } from "@project-serum/anchor/dist/cjs/utils";

import { PdaExample } from "../target/types/pda_example";

//substring of a pubic key
function shortKey(key: anchor.web3.PublicKey) {
  return key.toString().substring(0, 8);
}

describe("pda-example", () => {
  // Configure the client to use the local cluster.
  let provider = anchor.AnchorProvider.env();

  anchor.setProvider(provider);

  const program = anchor.workspace.PdaExample as anchor.Program<PdaExample>;

  async function generateKeypair() {
    let key = anchor.web3.Keypair.generate();
    await provider.connection.requestAirdrop(
      key.publicKey,
      2 * anchor.web3.LAMPORTS_PER_SOL
    );
    //Help understanding setTimeout
    await new Promise(resolve => setTimeout(resolve, 3 * 1000) );
    return key;
  }


  //derive
  //asysnc function with parameters from data account
  async function derivePda(
    color: string,
    pubkey: anchor.web3.PublicKey,
  ) {
    let [pda, _] = await anchor.web3.PublicKey.findProgramAddressSync(
      //in rust you have
      //wallet.key, b"_", color
      //question: Does the seeds always have to match between the programs?
      [
        pubkey.toBuffer(),
        Buffer.from("_"),
        Buffer.from(color),
      ],
      program.programId
    );
    return pda;
  }

  //create
  //async functions with parameters from one of the ix in the rust side
  //some of the parameters are what hold the PublicKey and something that holds the Keypair
  //after the parameters, you build a await response for a program method

  async function createLedgerAccount(
    color: string,
    pda: anchor.web3.PublicKey,
    wallet: anchor.web3.Keypair,
  ) {
    //Question: where did .createLedger come from?
    //Question: why pda is not pubkey as in the fn above, both are using .PublicKey
    await program.methods.createLedger(color)
    .accounts({
      ledgerAccount: pda,
      wallet: wallet.publicKey,
    })
    .signers([wallet])
    .rpc();
  }

  //modify

  async function modifyLedger(
    color: string,
    newBalance: number,
    wallet: anchor.web3.Keypair,
  ) {
    //below
    console.log("--------------------------------------------------");
    let data;
    let pda = await derivePda(color, wallet.publicKey);

    console.log(`Checking if account ${shortKey(pda)} exists for color: ${color}...`);
    try {

      data = await program.account.ledger.fetch(pda);
      console.log("It does.");

    } catch (e) {

      console.log("It does NOT. Creating...");
      await createLedgerAccount(color, pda, wallet);
      data = await program.account.ledger.fetch(pda);
    };

    console.log("Success.");
    console.log("Data:")
    console.log(`    Color: ${data.color}   Balance: ${data.balance}`);
    console.log(`Modifying balance of ${data.color} from ${data.balance} to ${newBalance}`);

    await program.methods.modifyLedger(newBalance)
    .accounts({
      ledgerAccount: pda,
      wallet: wallet.publicKey,
    })
    .signers([wallet])
    .rpc();

      data = await program.account.ledger.fetch(pda);
      console.log("New Data:")
      console.log(`    Color: ${data.color}   Balance: ${data.balance}`);
      console.log("Success.");
  }

  it("Is initialized!", async () => {
    const testKeypair1 = await generateKeypair();
    await modifyLedger("red", 2, testKeypair1);
    await modifyLedger("red", 4, testKeypair1);
    await modifyLedger("blue", 2, testKeypair1);

    const testKeypair2 = await generateKeypair();
    await modifyLedger("red", 3, testKeypair2);
    await modifyLedger("green", 3, testKeypair2);

  });
});

2 Answers 2

2

The function is called createLedger because your function is called like that in the program:

#[program]
pub mod pda_example {
    use super::*;

    //ix create
    pub fn create_ledger(
        ctx: Context<Create>, //<-struct name
        color: String,
    ) -> Result<()> {
        let ledger_account = &mut ctx.accounts.ledger_account;

        ledger_account.color = color;
        ledger_account.balance = 0;

        Ok(())

    }

    ...
}

The Create struct describes the transaction context and its dependencies.

I don't understand the second part of your question. The tryblock is put before the modify because you need to create the ledger before attempting to modify it. It's not really specific to Solana or TS

1

When you anchor build a project, it helpfully creates a list of the functions you can call and puts those in methods, and also converts them to CamelCase since that is the standard in TS.

SetTimeout stalls the program for a while.

The seeds do have to match in Rust/TS, and a PDA is program unique: it takes the program ID as one of the arguments used to generate the PDA. A PDA is a pubkey with no corresponding secret key, but that's a more complex topic.

Trying to see if a PDA exists by loading it, and creating if it does not exist is a common pattern to "get or init" a pda, that's what's happening with the try/catch.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.