3

I tried a few variations and I cannot get the instruction to work or be defined correctly. I have this client code and I welcome suggestions about how to code the instruction variable - Error -

Uncaught error: instruction is not defined
import {TransactionInstruction} from '@solana/web3.js'
console.log(pg.PROGRAM_ID.toString());
// Get latest blockhash info
const blockhashInfo = await pg.connection.getLatestBlockhash();
console.log(blockhashInfo);
// Create transaction
const tx = new web3.Transaction({
  ...blockhashInfo,
});
console.log("tx ==",tx);
const programId = new web3.PublicKey(pg.PROGRAM_ID.toString());

// Add our hello world program instruction
const account1 = new web3.PublicKey("BkQTFDah5ogoy9FtcGKKvQr67cjrZABDYVY9om9o188s");
// 4dGDp3BuTaXiqJwwJhh9abUBBm6hMhRkidttr5N4Cemm
const account2 = new web3.PublicKey("4dGDp3BuTaXiqJwwJhh9abUBBm6hMhRkidttr5N4Cemm");

const amount = 1000;
 

// encode the payment instruction
 
const instructionObject = new TransactionInstruction({
  instruction, // this is not defined
  amount,
})
tx.add(
  new web3.TransactionInstruction({
    programId: pg.PROGRAM_ID,
    keys: [
      {pubkey: account1, isSigner: false, isWritable: true},
      {pubkey: account2, isSigner: false, isWritable: true},
      // add any additional keys here
    ],
    //data: Buffer.from([]),
      data: instructionObject.toBuffer(),

   
  })
);
console.log("tx added ", tx);
// Sign transaction
tx.sign(pg.wallet.keypair);
// Send the transaction to the Solana cluster
const txHash = await pg.connection.sendRawTransaction(tx.serialize());
console.log(txHash);



The Rust contract is as follows -

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    program_error::ProgramError,
    pubkey::Pubkey,
};

/// Custom program errors
#[derive(Debug, Clone, Copy)]
pub enum CustomError {
    InsufficientFundsForTransaction,
}

impl From<CustomError> for ProgramError {
    fn from(e: CustomError) -> Self {
        ProgramError::Custom(e as u32)
    }
}

/// Transfers lamports from one account (must be program owned)
/// to another account. The recipient can by any account
fn transfer_service_fee_lamports(
    from_account: &AccountInfo,
    to_account: &AccountInfo,
    amount_of_lamports: u64,
) -> ProgramResult {
    // Does the from account have enough lamports to transfer?
    if **from_account.try_borrow_lamports()? < amount_of_lamports {
        return Err(CustomError::InsufficientFundsForTransaction.into());
    }
    // Debit from_account and credit to_account
    **from_account.try_borrow_mut_lamports()? -= amount_of_lamports;
    **to_account.try_borrow_mut_lamports()? += amount_of_lamports;
    Ok(())
}

/// Primary function handler associated with instruction sent
/// to your program
fn instruction_handler(accounts: &[AccountInfo]) -> ProgramResult {
    // Get the 'from' and 'to' accounts
    let account_info_iter = &mut accounts.iter();
    let from_account = next_account_info(account_info_iter)?;
    let to_service_account = next_account_info(account_info_iter)?;

    // Extract a service 'fee' of 5 lamports for performing this instruction
    transfer_service_fee_lamports(from_account, to_service_account, 5u64)?;

    // Perform the primary instruction
    // ... etc.

    Ok(())
}

entrypoint!(process_instruction);

fn process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
    // Match the first byte of the instruction data to determine which instruction to execute
    match instruction_data[0] {
        0 => {
            instruction_handler(accounts)?;
        }
        // ... etc.
        _ => return Err(ProgramError::InvalidInstructionData),
    }

    Ok(())
}
2
  • Update your question with exactly what didn't happen the way you expected it to, including the text of error messages with full stack traces. Commented Feb 22, 2023 at 18:57
  • There is no error message from the given code because instruction is not defined.
    – trevoro
    Commented Feb 22, 2023 at 20:08

1 Answer 1

5

There is no initialization for instruction variable in your client code, it's normal that it throws an error.

Here is an example on how you can achieve what you want:

Program:

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke,
    pubkey::Pubkey,
    system_instruction,
};

#[derive(BorshSerialize, BorshDeserialize)]
pub enum MyInstruction {
    Transfer { amount: u64 },
}

entrypoint!(process_instruction);

pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    match MyInstruction::try_from_slice(instruction_data)? {
        MyInstruction::Transfer { amount } => process_transfer(accounts, amount),
    }
}

fn process_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    let from = next_account_info(accounts_iter)?.key;
    let to = next_account_info(accounts_iter)?.key;

    msg!("Transfering {} lamports from {} to {}", amount, from, to);

    let transfer_ix = system_instruction::transfer(from, to, amount);
    invoke(&transfer_ix, accounts)
}

Client:

class MyInstruction {
  constructor(obj: object) {
    for (const key in obj) {
      this[key] = obj[key];
    }
  }
}

const MyInstructionSchema = new Map([
  [
    MyInstruction,
    {
      kind: "struct",
      fields: [
        ["discriminator", "u8"],
        ["amount", "u64"],
      ],
    },
  ],
]);

const encodeIxData = (ix: object = {}) => {
  return Buffer.from(
    borsh.serialize(MyInstructionSchema, new MyInstruction(ix))
  );
};

const fromPubkey = pg.wallet.publicKey;
const toPubkey = new web3.PublicKey(
  "4dGDp3BuTaXiqJwwJhh9abUBBm6hMhRkidttr5N4Cemm"
);
// Transfer of 1 SOL
const amount = new BN(1 * web3.LAMPORTS_PER_SOL);

const tx = new web3.Transaction();
tx.add(
  new web3.TransactionInstruction({
    programId: pg.PROGRAM_ID,
    keys: [
      { pubkey: fromPubkey, isSigner: true, isWritable: true },
      { pubkey: toPubkey, isSigner: false, isWritable: true },
      {
        pubkey: web3.SystemProgram.programId,
        isSigner: false,
        isWritable: false,
      },
    ],
    data: encodeIxData({ discriminator: 0, amount }),
  })
);

const txHash = await web3.sendAndConfirmTransaction(pg.connection, tx, [
  pg.wallet.keypair,
]);
console.log("Tx signature:", txHash);
2
  • Thanks. I am testing and get this - "SOL balance: 8.288851 Uncaught error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: instruction spent from the balance of an account it does not own" I checked the pg wallet and that is valid, also I have a bal. I checked various vars and they all look ok. It works until the actual sendTxn part and then it flags this ownership issue. I am using the pg and I assumed that injects the credentials into the client?
    – trevoro
    Commented Feb 23, 2023 at 10:58
  • I tested again with the Rust code too and it all works now. Before I was using my old Rust code thinking it was a client only problem.
    – trevoro
    Commented Feb 23, 2023 at 11:18

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.