2

I understand that in Solana, an account has an holder, which is the private key to the public key representing the account. An account also has anowner which is the program that is allowed to modify the data and lamport fields of the account.

I understand what a transaction is created to modify an account, such a transaction must be signed by the holder, that is, the private key corresponding to the public key of the account to be modified, and this transaction can only be processed successfully if sent to the programid that corresponds to the owner of the account.

Please if the above assumption is wrong, do correct me.

What I now do not understand is how this example code works.

On line 36 you have the following code:

    let createIx = SystemProgram.createAccount({
      fromPubkey: feePayer.publicKey,
      newAccountPubkey: counterKey,
      /** Amount of lamports to transfer to the created account */
      lamports: await connection.getMinimumBalanceForRentExemption(8),
      /** Amount of space in bytes to allocate to the created account */
      space: 8,
      /** Public key of the program to assign as the owner of the created account */
      programId: programId,
    });

I interpret this to mean, an instruction to create an account, where the public key is counterKey which was created previous in the code, and the owner will be programId, which was also created earlier in the code.

Previously the code also creates a fee payer, which it does by first creating a new key pair, funds it with some SOL and then add the key pair to an array of signers

  const feePayer = new Keypair();

  console.log("Requesting Airdrop of 1 SOL...");
  await connection.requestAirdrop(feePayer.publicKey, 2e9);
  console.log("Airdrop received");

  const counter = new Keypair();
  let counterKey = counter.publicKey;
  let tx = new Transaction();
  let signers = [feePayer];

It adds this feePayer to an array of signers.

Then on line 46 it also adds the key pair for the account that is to be created to the signers array. Basically this:

signers.push(counter);
tx.add(createIx);

It also adds the instruction to create an account to tx which represents the transactions.

So at this point we have a signers array that contains both the feePayer and counter and a createIx transaction.

So first question is at this point.

First Question:

Who signs the createIx transaction? The feePayer or the counter

Further down in the code on line 54 you have the following:

  const incrIx = new TransactionInstruction({
    keys: [
      {
        pubkey: counterKey,
        isSigner: false,
        isWritable: true,
      },
    ],
    programId: programId,
    data: idx,
  });

  tx.add(incrIx);

  let txid = await sendAndConfirmTransaction(connection, tx, signers, {
    skipPreflight: true,
    preflightCommitment: "confirmed",
    commitment: "confirmed",
  });

Which is a transaction instruction to increment the counter account. Now my second question:

Second Question:

Why does it set isSigner to false when passing in counterKey? That is:

{
    pubkey: counterKey,
    isSigner: false, // <- why is this false?
    isWritable: true,
}

should isSigner not be true? since we are going to be updating the counter account? should this instruction not be signed by the private key corresponding to the counter account?

Now for the third question:

Third Question:

I attempted to answer the first and second question above, by reasoning maybe the answer is in the order things were added to signers array and tx object. Basically:

position: | 1st       | 2nd     |
signers:  | feePayer  | counter |
tx:       | createIx  | incrIx  |

Meaning, feePayer signs the createIx transaction, while counter signs the incrIx transactions. If that is the case, then why have:

    keys: [
      {
        pubkey: counterKey,
        isSigner: false,
        isWritable: true,
      },
    ]

When creating TransactionInstruction?

Also in what scenario will isSigner ever be set to true, if it is the order of things in the signers array that determine which key signs.

1 Answer 1

1
  1. A signature applies to the whole transaction not just individual instructions. So an account that signed for instruction 1 also signed for instruction 2, 3, ...
  2. A list of program account limitations is here. The system program also follows these, it just so happens that all accounts are owned by the system program by default and that the system programs requires the account to sign to be created for another program. Technically if the system program was written a different way it could not require the signature to create the account. No signature is required for a program to change its own accounts' data.
  3. The reason isSinger is set to false is for the instruction unification that building the transaction does. In the library it actually looks for unique accounts and if any instruction needs it as a signer or marks it as writable then that is carried through. So if in program programId you read whether counterKey is a signer you'd see true even though you set the AccountMeta to false because the first instruction required it as a signer.

TL;DR

You sign transactions not instructions and AccountMetas are combined on transaction build.

1
  • unfortunately this does not help answer my questions :( I'll start with the first one. Who actually signs the transactions in the code example? the feePayer or counter? And if as you said, an account sign for the whole transaction, why was two accounts (feePayer and counter) passed in? Commented Sep 9, 2022 at 3:42

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.