0

I'm trying to build an app that will hold a user's wallet private key to sign for transactions and pay for the account from a centralized wallet that I own. I'm trying to avoid them having to approve every transaction in their wallet. I want their wallet to be associated so I can restrict edits to it and to associate it the data to them however, I want to pay for the transaction from a centralized wallet. I'm stuck however and could use some help.

Here's the error I'm getting. I think I understand that the problem is coming from the PDA addresses aren't the same but, I'm stuck... Thanks in advance!

Error: AnchorError caused by account: new_account. Error Code: ConstraintSeeds. Error Number: 2006. Error Message: A seeds constraint was violated. Program log: Left: Program log: AhrfSUPi2gFzLX5v9BjYKid3BF6cGXw3JZCBFBAHj1Wm Program log: Right: Program log: CcjK29oCeU3D3Wf2BHdcXgKngVpmmgNnK5BveekWxEqz

Here's my code.

use anchor_lang::prelude::*;
pub mod constants;
pub mod states;

use crate::{constants::*, states::*};

declare_id!("4xNpqEwbZ5HhdXWWyg3NwEcVz4STahhbcHZwE1EAwNtq");

#[program]
mod terrapin_station {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64, bump: u8) -> Result<()> {
        ctx.accounts.new_account.data = data;
        ctx.accounts.new_account.bump = bump;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init,
                payer = payer,
                space = 8 + 8 + 1,
                seeds = [DEAD_CASTER, new_account.key().as_ref()],
                bump)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

states.rs

use anchor_lang::prelude::*;

#[account]
pub struct NewAccount {
    pub data: u64,
    pub bump: u8,
}

constants.rs

use anchor_lang::prelude::*;

#[constant]
pub const DEAD_CASTER: &[u8] = b"dead_caster";

My Test looks like this.

import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
import { utf8 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"
import { PublicKey, Keypair } from "@solana/web3.js";
import { TerrapinStation } from "../target/types/terrapin_station";
import fs from 'fs';
import path from 'path';

const loadKeypairFromFile = (filePath: string): Keypair => {
    const keypairString = fs.readFileSync(filePath, 'utf8');
    const secretKey = Uint8Array.from(JSON.parse(keypairString));
    return Keypair.fromSecretKey(secretKey);
};

describe("terrapin-station", () => {
    const keypairPath = '/Users/chris/.config/solana/terra-test.json';

    const provider = AnchorProvider.env();
    anchor.setProvider(provider);
    const program = anchor.workspace.TerrapinStation as Program<TerrapinStation>;

    let newAccount: Keypair;
    let deadcasterAddress: PublicKey;
    let bump: number;

    before(async () => {
        // Load the payer keypair
        const payer = loadKeypairFromFile(path.resolve(__dirname, keypairPath));

        // Generate a new keypair for the new account
        newAccount = Keypair.generate();

        // Derive PDA using a static string and the new account's public key
        [deadcasterAddress, bump] = PublicKey.findProgramAddressSync(
            [utf8.encode('dead_caster'), newAccount.publicKey.toBuffer()],
            program.programId
        );

        console.log("Derived PDA:", deadcasterAddress.toString(), "Bump:", bump);
    });

    it("Create a new account", async () => {
        const payer = loadKeypairFromFile(path.resolve(__dirname, keypairPath));

        // Create the transaction to initialize the new account
        const tx = await program.methods.initialize(new BN(42), bump)
            .accounts({
                newAccount: deadcasterAddress,
                signer: newAccount.publicKey,
                payer: payer.publicKey,
                systemProgram: anchor.web3.SystemProgram.programId,
            })
            .signers([payer, newAccount])
            .rpc();

        console.log("Transaction signature for initialization:", tx);
    });

    it("Find the created account", async () => {
        // Fetch the account data using the derived PDA
        const accountData = await program.account.newAccount.fetch(deadcasterAddress);
        console.log(`Fetched account data: ${accountData.data}`);
    });

});

2 Answers 2

1

I ended up sorting out my problem. First, I read the documentation located here https://solana.com/docs/core/pda (FTFM) LOL. Anyways, this code is working to create a PDA and allow another wallet to payer for the creation.

lib.rs

use anchor_lang::prelude::*;
pub mod constants;
pub mod states;

use crate::{constants::*, states::*};

declare_id!("4xNpqEwbZ5HhdXWWyg3NwEcVz4STahhbcHZwE1EAwNtq");

#[program]
mod terrapin_station {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account_data = &mut ctx.accounts.pda_account;
        // store the address of the `user`
        account_data.user = *ctx.accounts.user.key;
        // store who paid for the account
        account_data.payer = *ctx.accounts.payer.key;
        // store the canonical bump
        account_data.bump = ctx.bumps.pda_account;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
    init,
    // set the seeds to derive the PDA
    seeds = [DEAD_CASTER, user.key().as_ref()],
    // use the canonical bump
    bump,
    payer = payer,
    space = 8 + DeadCasterAccount::INIT_SPACE
    )]
    pub pda_account: Account<'info, DeadCasterAccount>,
    pub system_program: Program<'info, System>,
}

state.rs

use anchor_lang::prelude::*;

#[account]
#[derive(InitSpace)]
pub struct DeadCasterAccount {
    pub user: Pubkey,
    pub payer: Pubkey,
    pub bump: u8,
}

constants.rs

use anchor_lang::prelude::*;

#[constant]
pub const DEAD_CASTER: &[u8] = b"dead_caster";

tests.ts

import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
import { PublicKey, Keypair } from "@solana/web3.js";
import { TerrapinStation } from "../target/types/terrapin_station";
import fs from 'fs';
import path from 'path';

const loadKeypairFromFile = (filePath: string): Keypair => {
    const keypairString = fs.readFileSync(filePath, 'utf8');
    const secretKey = Uint8Array.from(JSON.parse(keypairString));
    return Keypair.fromSecretKey(secretKey);
};

describe("terrapin-station", () => {
    const keypairPath = '/Users/chris/.config/solana/terra-test.json';

    const provider = AnchorProvider.env();
    anchor.setProvider(provider);
    const program = anchor.workspace.TerrapinStation as Program<TerrapinStation>;

    let newAccountWallet: Keypair;
    let deadcasterAddress: PublicKey;

    before(async () => {
        // Load the payer keypair
        const payer = loadKeypairFromFile(path.resolve(__dirname, keypairPath));

        // Generate a new keypair for the new account
        newAccountWallet = Keypair.generate();

        // Derive PDA using a static string and the new account's public key
        [deadcasterAddress] = PublicKey.findProgramAddressSync(
            [Buffer.from('dead_caster'), newAccountWallet.publicKey.toBuffer()],
            program.programId
        );

        console.log("Derived PDA:", deadcasterAddress.toString());
    });

    it("Create a new account", async () => {
        const payer = loadKeypairFromFile(path.resolve(__dirname, keypairPath));

        const transactionSignature = await program.methods
            .initialize()
            .accounts({
                user: newAccountWallet.publicKey,
                payer: payer.publicKey,
                pdaAccount: deadcasterAddress,
            })
            .signers([newAccountWallet, payer])
            .rpc();

        console.log("Transaction Signature:", transactionSignature);
    });

    it("Fetch Account", async () => {
        const pdaAccount = await program.account.deadCasterAccount.fetch(deadcasterAddress);
        console.log(JSON.stringify(pdaAccount, null, 2));
    });

});

Hope this helps someone else!

0

Could you change the PDA derivation to

[deadcasterAddress, bump] = PublicKey.findProgramAddressSync(
            [Buffer.from('dead_caster'), newAccount.publicKey.toBuffer()],
            program.programId
        );

You are using utf byte array.

1

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