I'm trying to create an Mint having my program as authority and I'm struggling to get my CPI calls right.
Here is a toy example of what I have so far:
use anchor_lang::prelude::*;
use anchor_spl::token::{
self, set_authority, spl_token::instruction::AuthorityType, SetAuthority, Token,
};
use program::MintTest;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod mint_test {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let bump = *ctx.bumps.get("mint").unwrap();
let seeds = vec![bump];
let seeds = vec![b"some-seed".as_ref(), seeds.as_slice()];
let seeds = vec![seeds.as_slice()];
let seeds = seeds.as_slice(); // ❓ slightly unrelated but I'd love to understand why all this nesting is required 🤔
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_accounts = SetAuthority {
account_or_mint: ctx.accounts.mint.to_account_info(),
current_authority: ctx.accounts.program.to_account_info(),
};
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, seeds);
set_authority(cpi_ctx, AuthorityType::MintTokens, None)?; // ❌ This fails 🙁
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = signer,
mint::decimals = 0,
mint::authority = program,
seeds = [b"some-seed".as_ref()],
bump,
)]
pub mint: Account<'info, token::Mint>,
#[account(mut)]
pub signer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
pub program: Program<'info, MintTest>,
}
The mint is created correctly, but the any CPI call fails with Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
(set_authority
is just an example, I tried other CPIs like mint_to
without more success 😔).
It does work if I set the TX signer as authority so I assume I'm doing something wrong with my signer seeds but I just can't figure it out and I have been stuck on this for hours now.
Here is my TS test as well:
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { MintTest } from "../target/types/mint_test";
describe("mint-test", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MintTest as Program<MintTest>;
it("Is initialized!", async () => {
const [mint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from("some-seed")],
program.programId
);
const tx = await program.methods
.initialize()
.accounts({
mint,
program: program.programId,
})
.rpc();
console.log("Your transaction signature", tx);
});
});
Thank you in advance for your help 😇
So, it just seems like my understanding of all this was a bit off. A program just cannot sign, so our program can't be our mint's authority.
However, we can assign a PDA as the owner of our mint and use the seeds used to find that PDA's address to "sign" instructions.
The following works:
use anchor_lang::prelude::*;
use anchor_spl::token::{
self, set_authority, spl_token::instruction::AuthorityType, SetAuthority, Token,
};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod mint_test {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let bump = *ctx.bumps.get("auth").unwrap();
let seeds = vec![bump];
let seeds = vec![b"auth".as_ref(), seeds.as_slice()];
let seeds = vec![seeds.as_slice()];
let seeds = seeds.as_slice();
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_accounts = SetAuthority {
account_or_mint: ctx.accounts.mint.to_account_info(),
current_authority: ctx.accounts.auth.to_account_info(),
};
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, seeds);
set_authority(cpi_ctx, AuthorityType::MintTokens, None)?;
Ok(())
}
}
#[account]
pub struct Auth {}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
space = 8,
payer = signer,
seeds = [b"auth".as_ref()],
bump,
)]
pub auth: Account<'info, Auth>,
#[account(
init,
payer = signer,
mint::decimals = 0,
mint::authority = auth,
seeds = [b"some-seed".as_ref()],
bump,
)]
pub mint: Account<'info, token::Mint>,
#[account(mut)]
pub signer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
And the test:
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { MintTest } from "../target/types/mint_test";
describe("mint-test", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MintTest as Program<MintTest>;
it("Is initialized!", async () => {
const [auth] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from("auth")],
program.programId
);
const [mint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from("some-seed")],
program.programId
);
const tx = await program.methods
.initialize()
.accounts({
mint,
auth,
})
.rpc();
console.log("Your transaction signature", tx);
});
});
Hopefully that's helpful to someone.