I'm attempting to process a transaction in my solana contract. The way it seems I should do this is by using createAccountWithSeed
to generate a transfer account owned by both the program (8DqELvN5TFeMtNJciUYvGqso2CyG5M6XNWxh3HRr3Vjv) and payer. So I create the new transfer account to send through to the solana program processor to execute the transaction. But when I pass the transfer account through to my Rust program the check_account_owner
states that the account is owned by the System Program (11111111111111111111111111111111) rather than my program.
So my problem is two-fold:
Here's the JS for the createAccountWithSeed
in the client.
const transferAcc = await PublicKey.createWithSeed(
payer.publicKey,
"payer",
PROGRAM_ID,
);
await connection.requestAirdrop(transferAcc, 100000);
SystemProgram.createAccountWithSeed({
basePubkey: payer.publicKey,
fromPubkey: payer.publicKey,
lamports: 100000,
newAccountPubkey: transferAcc,
programId: PROGRAM_ID,
seed: "payer",
space: 1024,
});
const accInfo = await connection.getAccountInfo(transferAcc);
console.log(
`Paying from acc: ${transferAcc.toBase58()}, Owned by: ${accInfo?.owner.toBase58()}`
);
And here's the Rust code which is attempting to make the transfer.
pub fn process_payment(
program_id: &Pubkey,
accounts: &[AccountInfo],
payment_fee: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let token_program = next_account_info(account_info_iter)?;
let payer_acc = next_account_info(account_info_iter)?;
let transfer_acc = next_account_info(account_info_iter)?;
let receiver_acc = next_account_info(account_info_iter)?;
if !payer_acc.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if *token_program.key != id() {
return Err(SosolError::IncorrectTokenProgramId.into());
}
check_account_owner(payer_payment_acc, &program_id)?;
msg!("Calling the token program to transfer tokens to the receiver...");
token_transfer(
token_program.clone(),
transfer_acc.clone(),
receiver_account_key.clone(),
payer_acc.clone(),
payment_fee,
)?;
Ok(())
}
/// Issue a spl_token `Transfer` instruction.
#[allow(clippy::too_many_arguments)]
fn token_transfer<'a>(
token_program: AccountInfo<'a>,
source: AccountInfo<'a>,
destination: AccountInfo<'a>,
authority: AccountInfo<'a>,
amount: u64,
) -> Result<(), ProgramError> {
let ix = transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?;
invoke(&ix, &[source, destination, authority, token_program])
}
The error logs state:
Program log: Expected account to be owned by program 8DqELvN5TFeMtNJciUYvGqso2CyG5M6XNWxh3HRr3Vjv, received 11111111111111111111111111111111
Program log: CUSTOM-ERROR: The account did not have the expected program id
OK, so the reason why the transfer account is owned by the system program rather than the my program is because I was creating the account outside of the transaction. The key is to add the createAccountWithSeed
(or actually just createAccount
for me as I actually want a fresh account for each transaction) method into your transaction chain like so:
const transaction = new Transaction();
const transferAcc = new Keypair();
const transferAccPubKey = transferAcc.publicKey;
transaction.add(
SystemProgram.createAccount({
fromPubkey: payerAccount.publicKey,
newAccountPubkey: transferAccPubKey,
lamports: paymentFee,
space: dataLayout.span,
programId: PROGRAM_ID,
})
);
The runloop is a really good partner resource for assistance with this. Once you've added all the transaction items into the transaction item you'll be sending it using:
return await sendAndConfirmTransaction(connection, transaction, [
payerAccount, transferAcc
]);
So look for that if you're struggling where to insert the transaction.add
method.
It took me ages to figure this so hope it helps someone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With