How to check instruction in Solana on-chain program? - solana

I am developing game, which guesses number and get reward if they success.
This is summary of my program.
First, user send amount of sol and his guessing number.
Second, Program get random number and store user's sol to vault.
Third, Program make random number, if user is right, gives him reward.
Here, how can I check if the user sent correct amount of sol in program?
This is test code for calling program.
const result = await program.rpc.play(
new anchor.BN(40),
new anchor.BN(0),
new anchor.BN(20000000),
_nonce, {
accounts: {
vault: vaultPDA,
user: provider.wallet.publicKey, // User wallet
storage: storageAccount.publicKey,
systemProgram: systemProgram
},
instructions: [
SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: vaultPDA,`enter code here`
lamports: 20000000`enter code here`
})
],
signers: [storageAccount]`enter code here`
}
)

The best solution would be to directly transfer the lamports inside of your program using a cross-program invocation, like this program: Cross-program invocation with unauthorized signer or writable account
Otherwise, from within your program, you can check the lamports on the AccountInfo passed, and make sure it's the proper number, similar to this example: https://solanacookbook.com/references/programs.html#transferring-lamports
The difference there is that you don't need to move the lamports.

Related

Solana: Check if deposit is finalized?

How can we check if a deposit is finalized using solana/web3.js? I've tried using getTransaction with commitment parameter finalized and I did get a response with slot number. However, it took a few more seconds for Solana Explorer to show status Finalized. This probably means receiving slot number in a response to getTransaction doesn't necessarily mean a deposit is finalized?
Have you tried using 'confirmed' instead?
https://solana-labs.github.io/solana-web3.js/modules.html#Finality
You can use Connection.confirmTransaction with the finalized commitment to check that a transaction has landed. For example:
const signature = await connection.sendTransaction(transaction, signers);
const status = await connection.confirmTransaction(
{
signature: signature,
blockhash: transaction.recentBlockhash,
lastValidBlockHeight: transaction.lastValidBlockHeight,
},
)
).value;
if (status.err) {
throw new Error(
`Transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
This was lifted from https://github.com/solana-labs/solana/blob/master/web3.js/src/util/send-and-confirm-transaction.ts

How can I use the `close_account` function properly in order to close an Associated Token Account?

I am trying to close an Associated Token Account (ATA) from inside a program. The ATA belongs to the program. I found a function called close_account, but I haven't figured out how to use it properly. I'm using Anchor.
The desired flow of my program is:
Send a token from the program ATA to the user ATA (I've done this successfully)
Close the program ATA that was used for the token
This is what the implementation of close_account looks like:
pub fn close_account<'a, 'b, 'c, 'info>(
ctx: CpiContext<'a, 'b, 'c, 'info, CloseAccount<'info>>,
) -> Result<()> {
let ix = spl_token::instruction::close_account(
&spl_token::ID,
ctx.accounts.account.key,
ctx.accounts.destination.key,
ctx.accounts.authority.key,
&[], // TODO: support multisig
)?;
solana_program::program::invoke_signed(
&ix,
&[
ctx.accounts.account.clone(),
ctx.accounts.destination.clone(),
ctx.accounts.authority.clone(),
],
ctx.signer_seeds,
)
.map_err(Into::into)
}
The CloseAccount struct looks as follows:
#[derive(Accounts)]
pub struct CloseAccount<'info> {
pub account: AccountInfo<'info>,
pub destination: AccountInfo<'info>,
pub authority: AccountInfo<'info>,
}
(I assume) the account is the ATA and the authority is my program - but what is the destination in this context? Why would closing an account need a destination and which account should I use as the destination account?
what is the destination in this context? Why would closing an account need a destination and which account should I use as the destination account?
Since token accounts are like any other account in Solana, they must have enough SOL to cover the minimum balance requirement. When closing an account, you must send that SOL somewhere else, hence the "destination". The best option is to use a wallet / SOL account that is only used for SOL and nothing else.
More info at https://spl.solana.com/token#closing-accounts

method: "hardhat_impersonateAccount" - What happens when you call this method with an address that doesn't exist?

async function impersonateAccount(acctAddress) {
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [acctAddress],
});
return await ethers.getSigner(acctAddress);
}
When forking the blockchain locally on Hardhat, the function above allows developers to impersonate the address passed as argument to it.
So you can create transactions as if you're the owner of the account.
What happens when forking the mainnet, and you pass an address that does not exist on the mainnet as an argument?
Would it throw an error?
Does it create the account for you locally and give you access?
It will create the account locally with a balance of 0 ETH.
I tried this with the Ropsten address 0xFD391b604E9456c0Ec4aC13Cc881FbAF68868eB2, which currently has 210 testnet ETH and does not exist on the mainnet.
With your code example it will return a valid signer, and if you check the balance of the signer's address it will have 0 ETH.

Could not create program address with signer seeds when creating a token account

I have a PDA and I'm trying to create a token account for it, using Solana Spl Associated Token Account(https://spl.solana.com/associated-token-account):
let (token_account_key, token_account_key_bump_seed) = Pubkey::find_program_address(&[&stake_info_bytes, &token_program_bytes, &mint_address_bytes], spl_associated_token_program.key);
Now I'm trying to create the account:
let account_rent = rent.minimum_balance(Account::LEN);
let authority_signature_seeds = [&stake_info_bytes[..32], &token_program_bytes[..32], &mint_address_bytes[..32], &[token_account_key_bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let create_ix = create_account(
feepayer.key,
token_account.key,
account_rent,
Account::LEN as u64,
spl_associated_token_program.key
);
invoke_signed(&create_ix, &[
spl_associated_token_program.clone(),
feepayer.clone(),
token_account.clone()
], signers);
But I'm getting this error:
> Program returned error: Could not create program address with signer seeds: Provided seeds do not result in a valid address
With PDAs, your program can only "sign" for PDAs generated using its program id. In this case, your program is using the associated token account program as the program id, when it should be using itself. So this should become:
let (token_account_key, token_account_key_bump_seed) = Pubkey::find_program_address(&[&stake_info_bytes, &token_program_bytes, &mint_address_bytes], my_program_id);
Using your program's id as my_program_id, and then you would actually use this as:
let create_ix = create_account(
feepayer.key,
token_account.key,
account_rent,
Account::LEN as u64,
spl_token_program.key
);
invoke_signed(&create_ix, &[
feepayer.clone(),
token_account.clone()
], signers);
Note: I'm assuming that you're trying to create a token account, which must be owned by the token program.
Otherwise, it would be possible to fake "signatures" for another program, which would be a huge security risk. For example, associated token accounts must be signed and created by the associated token account program.

My Token.createMintToInstruction is throwing "Error processing instruction 0: invalid account data for instruction"

I'm trying to mint some tokens on the frontend like this:
let transaction = new Transaction();
let mintToInstruction = Token.createMintToInstruction(
splToken.TOKEN_PROGRAM_ID,
myTokenMint.publicKey,
userAccount.publicKey,
airdropAdmin.publicKey,
[],
sendAmount.toNumber()
)
transaction.add(mintToInstruction);
let conn: Connection = ctx.connection;
const tx1 = await conn.sendTransaction(
transaction,
[airdropAdmin]
);
But I get an obscure error:
Error processing Instruction 0: invalid account data for instruction
What's happening?
One of the accounts you're passing in is not the account the Token Program expected.
Either:
The userAccount is incorrect. This must be a Token Account, did you use the user's System Account instead?
The myMintAccount is incorrect. Is this a real token mint?
Consider logging those public keys and putting them into the explorer. Does the userAccount say "Token Account" at the top? Does the myMintAccount say "Token Mint"?
The invalid account data for instruction typically happens when a program can't run unpack on the data inside the account you're passing in.
So either the Account::unpack is failing, or the the Mint::unpack is failing.

Resources