2

I'm writing some tests for an anchor program. I'm passing into the program 2 signers because it seems like the IDL is asking for it, but I don't quite understand how / why this works.

Any help in understanding if 2+ signers is required and why it is required would be greatly appreciated

Typescript Test

const targetUserProfileAccount: Keypair = Keypair.generate();
type PrivacyStatus = { public: {} } | { private: {} };

it('should initialize a user', async () => {
    const followers = [new PublicKey("B3tV9TyDvr6jzWU9nN9qUbBzvvs3Cy6gG2z2a6Zfz3Lw")]; // Replace with actual public keys
    const privacyStatus: PrivacyStatus = { public: {} };
    const tx = await program.rpc.initializeUser(followers, privacyStatus, {
      accounts: {
        userProfile: targetUserProfileAccount.publicKey, // Replace with actual use account public key
        user: payer.publicKey, // payer of transaction
        systemProgram: anchor.web3.SystemProgram.programId,
      },
      signers: [targetUserProfileAccount, payer], // chat account and payer are the signers
    });
    expect(tx).to.exist;
  });

anchor rust code:

 // Initializes a new user with given members and privacy settings
 pub fn initialize_user(ctx: Context<InitializeUser>, follow_requests: Vec<Pubkey>, privacy_status: PrivacyStatus) -> Result<()> {
    let user = &mut ctx.accounts.user_profile;
    user.follow_requests = follow_requests;
    user.privacy_status = privacy_status;
    Ok(())
}

#[derive(Accounts)]
/// Initializes a new chat with the specified members and privacy settings.
pub struct InitializeUser<'info> {
    /// A user profile is initialized here.
    /// The space is allocated for the account discriminator + follow_requets vector + privacy status.
    #[account(init, payer = user, space = 8 + (32 * 8) + 1)]
    pub user_profile: Account<'info, UserProfile>,
    /// The user who is creating the profile. This account will pay for the transaction.
    #[account(mut)]
    pub user: Signer<'info>,
    /// Reference to the system program which allows creating accounts.
    pub system_program: Program<'info, System>,
}

IDL:

{
      "name": "initializeUser",
      "accounts": [
        {
          "name": "userProfile",
          "isMut": true,
          "isSigner": true,
          "docs": [
            "A user profile is initialized here.",
            "The space is allocated for the account discriminator + follow_requets vector + privacy status."
          ]
        },
        {
          "name": "user",
          "isMut": true,
          "isSigner": true,
          "docs": [
            "The user who is creating the profile. This account will pay for the transaction."
          ]
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false,
          "docs": [
            "Reference to the system program which allows creating accounts."
          ]
        }
      ],
      "args": [
        {
          "name": "followRequests",
          "type": {
            "vec": "publicKey"
          }
        },
        {
          "name": "privacyStatus",
          "type": {
            "defined": "PrivacyStatus"
          }
        }
      ]
    },
0

1 Answer 1

1

The reason your initialize_user instruction requires two signers is because of the init and payer constraints on the user_profile account.

    #[account(init, payer = user, space = 8 + (32 * 8) + 1)]
    pub user_profile: Account<'info, UserProfile>,

Under the hood, the init constraint makes a Cross Program Invocation (CPI) to the System Program to create the account.

The payer constraint specifies the account that pays the lamports required for the space on the new account.

Since you're using the public key of a new keypair as the address of the new account (targetUserProfileAccount), the keypair has to be included as a signer on the transaction. This is effectively to prove that you control the secret key for the address you're using to create the new account.

The second required signer is the user account, which is specified as the payer for the new user_profile account. This user must be a signer because lamports are deducted from the account.

Also, it looks like your test file is using the old syntax (program.rpc). The updated syntax looks like this:

await program.methods
  .instructionName(instructionDataInputs)
  .accounts({})
  .signers([])
  .rpc()

You can use the examples here for reference.

2
  • thank you! this makes sense. correct me if I'm wrong but if this were a PDA only one signer would be required, not a secondary target user profile account as we don't need to prove ownership over that key.
    – AceGravity
    Commented Dec 24, 2023 at 20:43
  • yep that's right, a PDA doesn't have a corresponding private key to sign with, but the program from which a PDA is derived can "sign" for it's own PDAs when making a CPI
    – john
    Commented Dec 24, 2023 at 21:20

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.