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

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.