Change authority for accounts
Outcome
We will understand on how to transfer authority to others.
The are totally 4 types of authorities
- Mint Tokens - Authority to mint new tokens
- Freeze Accounts - Authority to freeze any account associated with the Mint
- Account Owner - Owner of a given token account
- CloseAccount - Authority to close a token account
We will look at each of these authority type and understand how to perform these operations
The outcome of this chapter is found here
Prerequisite
For the demonstration purpose, we will retain only create_mint
, transfer_mint
instructions.
So the remove the following from the previous chapter
burn_token
instructionBurnToken
context
And remove test case related to it.
Or you could clone the Chapter - 5 repo for getting started.
How to set authority for token mints ?
It's is a 2-step process.
- Create a
SetMintTokenAuthority
context - Create a
set_mint_token_authority
instruction
Step-1 : Create a SetMintTokenAuthority
context
Let us create a set mint token authority like below. We will transfer the mint token authority to anotherAuthority
. And we pass that account as Signer
in the context.
// Set Mint Token Authority context
#[derive(Accounts)]
pub struct SetMintTokenAuthority<'info> {
#[account(
mut,
seeds = [
b"spl-token-mint".as_ref(),
],
bump = vault.spl_token_mint_bump,
)]
pub spl_token_mint: Account<'info, Mint>, // ---> 1
#[account(
seeds = [
b"vault"
],
bump = vault.bump, // --> 2
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub payer: Signer<'info>, // ---> 3
pub another_authority: Signer<'info>, // ---> 4
pub system_program: Program<'info, System>, // ---> 5
pub token_program: Program<'info, Token>, // ---> 6
pub rent: Sysvar<'info, Rent>, // ---> 7
}
- We pass
spl_token_mint
account. We are changing this mint's authority - We
vault
account as it containsspl_token_mint_bump
value payer
account is the original authority ofspl_token_mint
another_authority
account to which we are passing the authority tosystem_program
to manage accounts- We invoke
set_authority
instruction oftoken_program
through CPI.
Step-2 : Create a set_mint_token_authority
instruction
Let us create an instruction to change the authority.
Import the following dependencies
use anchor_lang::prelude::*;
use anchor_spl::token::spl_token::instruction::AuthorityType;
use anchor_spl::{
associated_token::AssociatedToken,
token::{self, Mint, Token, TokenAccount},
And add the following instruction.
pub fn set_mint_authority(ctx: Context<SetMintTokenAuthority>) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::SetAuthority {
current_authority: ctx.accounts.payer.to_account_info(),
account_or_mint: ctx.accounts.spl_token_mint.to_account_info(),
},
);
token::set_authority(
cpi_context,
AuthorityType::MintTokens,
Some(ctx.accounts.another_authority.key()),
)?;
Ok(())
}
With this instruction above, it is easy to change the authority. We use AuthorityType::MintTokens
to change the authority. Let us test this out.
Before proceeding to write test cases, do make sure that you have added anotherWallet
as another wallet in the describe block.
const anotherWallet = anchor.web3.Keypair.generate(); // newly created another wallet
And add some sols to it in the before
block of the test file
before("Add sols to wallet ", async () => {
await addSols(provider, payer.publicKey); // add some sols before calling test cases
await addSols(provider, anotherWallet.publicKey); // add sols to another wallet
});
In the spl-token.ts
file add the following test case
//set mint authority to another wallet test
it("should set mint authority to anotherWallet", async () => {
const [splTokenMint, _1] = await findSplTokenMintAddress();
const [vaultMint, _2] = await findVaultAddress();
const tx = await program.methods
.setMintAuthority()
.accounts({
splTokenMint: splTokenMint,
vault: vaultMint,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
anotherAuthority: anotherWallet.publicKey,
payer: payer.publicKey,
})
.signers([payer, anotherWallet])
.rpc();
console.log("Your transaction signature", tx);
});
And run the following command
anchor test
You should be able to see the following output.
Now we will look at how to change the FreezeAccount
authority.
How to set Freeze Authority for an account ?
If we observe carefully, when we created mint
instruction and created spl_token_mint
we had set the freeze authority to payer
.
Now let us change that to another authority
.
Again, it is a 2-step process.
It's is a 2-step process.
- Create a
SetFreezeAccountAuthority
context - Create a
set_freeze_account_authority
instruction
Step-1 : Create a SetFreezeAccountAuthority
context
We create SetFreezeAccountAuthorityContext
as below.
// Set Freeze Account Authority context
#[derive(Accounts)]
pub struct SetFreezeAccountAuthority<'info> {
#[account(
mut,
seeds = [
b"spl-token-mint".as_ref(),
],
bump = vault.spl_token_mint_bump,
)]
pub spl_token_mint: Account<'info, Mint>, // ---> 1
#[account(
seeds = [
b"vault"
],
bump = vault.bump, // --> 2
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub payer: Signer<'info>, // ---> 3
pub another_authority: Signer<'info>, // ---> 4
pub system_program: Program<'info, System>, // ---> 5
pub token_program: Program<'info, Token>, // ---> 6
}
- We pass
spl_token_mint
account. We are changing freeze authority for this mint - We
vault
account as it containsspl_token_mint_bump
value payer
account is the original authority ofspl_token_mint
another_authority
account to which we are passing the authority tosystem_program
to manage accounts- We invoke
set_authority
instruction oftoken_program
through CPI.
Step-2: Create a set_freeze_account_authority
instruction
Let us create an instruction to change the authority.
pub fn set_freeze_account_authority(ctx: Context<SetFreezeAccountAuthority>) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::SetAuthority {
current_authority: ctx.accounts.payer.to_account_info(),
account_or_mint: ctx.accounts.spl_token_mint.to_account_info(),
},
);
token::set_authority(
cpi_context,
AuthorityType::FreezeAccount,
Some(ctx.accounts.another_authority.key()),
)?;
Ok(())
}
Let's test this out using the following test case. Write the test case in spl-token.ts
//set freeze account authority to another wallet test
it("should set freeze account authority to anotherWallet", async () => {
const [splTokenMint, _1] = await findSplTokenMintAddress();
const [vaultMint, _2] = await findVaultAddress();
const tx = await program.methods
.setFreezeAccountAuthority()
.accounts({
splTokenMint: splTokenMint,
vault: vaultMint,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
anotherAuthority: anotherWallet.publicKey,
payer: payer.publicKey,
})
.signers([payer, anotherWallet])
.rpc();
console.log("Your transaction signature", tx);
});
Run the following command
anchor test
You should be able to see the following
Next, we will look at AccountOwner
authority type.
How to set AccountOwner
authority to a token account?
This is used in the cases where we want to delegate token account ownership to another authority.
In the following code example, we will transfer the payer_mint_ata
authority to another_authority
account.
We will follow the 2-step process again.
- Create a
SetAccountOwnerAuthority
context. - Create a
set_account_owner
instruction.
Step-1 : Create a SetAccountOwnerAuthority
context.
Let create the context as below.
// Set Account Owner Authority context
#[derive(Accounts)]
pub struct SetAccountOwnerAuthority<'info> {
#[account(
mut,
seeds = [
b"spl-token-mint".as_ref(),
],
bump = vault.spl_token_mint_bump,
)]
pub spl_token_mint: Account<'info, Mint>, // ---> 1
#[account(
seeds = [
b"vault"
],
bump = vault.bump, // --> 2
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub payer: Signer<'info>, // ---> 3
#[account(
mut,
associated_token::mint = spl_token_mint,
associated_token::authority = payer
)]
pub payer_mint_ata: Account<'info, TokenAccount>, // ---> 4
pub another_authority: Signer<'info>, // ---> 5
pub system_program: Program<'info, System>, // ---> 6
pub token_program: Program<'info, Token>, // ---> 7
}
- We pass
spl_token_mint
account. This is used bypayer_mint_ata
account - We
vault
account as it containsspl_token_mint_bump
value payer
account is the original authority ofpayer_mint_ata
payer_mint_ata
account authority is passed toanother_authority
another_authority
account to which we are passing the authority tosystem_program
to manage accounts- We invoke
set_authority
instruction oftoken_program
through CPI.
Step-2 : Create a set_account_owner
instruction.
Let us create an instruction to achieve this.
pub fn set_account_owner_authority(ctx: Context<SetAccountOwnerAuthority>) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::SetAuthority {
current_authority: ctx.accounts.payer.to_account_info(),
account_or_mint: ctx.accounts.payer_mint_ata.to_account_info(),
},
);
token::set_authority(
cpi_context,
AuthorityType::AccountOwner, // authority type is AccountOwner
Some(ctx.accounts.another_authority.key()),
)?;
Ok(())
}
As you can above, we have set the AuthorityType
to AccountOwner
.
Write a test in spl-token.ts
to test the instruction.
//set account owner to another wallet test
it("should set account owner of payer_mint_ata to another wallet ", async () => {
try {
const [splTokenMint, _1] = await findSplTokenMintAddress();
const [vaultMint, _2] = await findVaultAddress();
const [payerMintAta, _3] = await findAssociatedTokenAccount(
payer.publicKey,
splTokenMint
);
const tx = await program.methods
.setAccountOwnerAuthority()
.accounts({
splTokenMint: splTokenMint,
vault: vaultMint,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
payerMintAta: payerMintAta,
payer: payer.publicKey,
anotherAuthority: anotherWallet.publicKey,
})
.signers([payer, anotherWallet])
.rpc();
console.log("Your transaction signature", tx);
} catch (err) {
console.log(err);
}
});
Run the following command to test
anchor test
You should be able to see the following output.
Next we will look at CloseAccount
Authority type.
How to set a CloseAccount Authority for a token account ?
We can set a Close Account authority for a token account. For the below example, we will set the another_mint_ata
close authority to payer
as authority.
We use this authority to close and transfer remaining lamports
to the authority account.
Let us follow the 2-step process to achieve our goal
- Create a
CloseAccountAuthority
context - Create an instruction
close_account_authority
Step-1 : Create a CloseAccountAuthority
context.
To create a CloseAccountAuthority
context, we will create a struct
in the lib.rs
file as below.
// Set Close Account Authority context
#[derive(Accounts)]
pub struct SetCloseAccountAuthority<'info> {
#[account(
mut,
seeds = [
b"spl-token-mint".as_ref(),
],
bump = vault.spl_token_mint_bump,
)]
pub spl_token_mint: Account<'info, Mint>, // ---> 1
#[account(
seeds = [
b"vault"
],
bump = vault.bump, // --> 2
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub payer: Signer<'info>, // ---> 3
#[account(
init,
associated_token::mint = spl_token_mint,
associated_token::authority = another_authority,
payer = payer
)]
pub another_mint_ata: Account<'info, TokenAccount>, // ---> 4
pub another_authority: Signer<'info>, // ---> 5
pub system_program: Program<'info, System>, // ---> 6
pub token_program: Program<'info, Token>, // ---> 7
pub associated_token_program : Program<'info, AssociatedToken>, // ---> 8,
pub rent: Sysvar<'info, Rent>, // ---> 9
}
- We pass
spl_token_mint
account. - We
vault
account as it containsspl_token_mint_bump
value payer
account paysanother_mint_ata
account creation.another_mint_ata
account from which we want to transfer close authority to payeranother_authority
account. We use this to createanother_mint_ata
account.system_program
to manage accounts- We invoke
set_authority
instruction oftoken_program
through CPI. associated_token_account
is for creatinganother_mint_ata
accountrent
used bysystem_program
to create a new account.
Step-2 : Create an instruction close_account_authority
Let us create an instruction to set close authority on another_mint_ata
.
pub fn set_close_account_authority(ctx: Context<SetCloseAccountAuthority>) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::SetAuthority {
current_authority: ctx.accounts.another_authority.to_account_info(),
account_or_mint: ctx.accounts.another_mint_ata.to_account_info(),
},
);
token::set_authority(
cpi_context,
AuthorityType::CloseAccount,
Some(ctx.accounts.payer.key()),
)?;
Ok(())
}
In the above instruction we are setting CloseAccount
authority to payer
.
We write the test case for this to test in spl-token.ts
file.
it("should set close account authority of another_mint_ata to payer wallet", async () => {
try {
const [splTokenMint, _1] = await findSplTokenMintAddress();
const [vaultMint, _2] = await findVaultAddress();
const [another_mint_ata, _3] = await findAssociatedTokenAccount(
anotherWallet.publicKey,
splTokenMint
);
const tx = await program.methods
.setCloseAccountAuthority()
.accounts({
splTokenMint: splTokenMint,
vault: vaultMint,
tokenProgram: TOKEN_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
anotherMintAta: another_mint_ata,
payer: payer.publicKey,
anotherAuthority: anotherWallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
})
.signers([payer, anotherWallet])
.rpc();
console.log("Your transaction signature", tx);
} catch (err) {
console.log(err);
}
});
Let us run the test case by running the following command
anchor test
You should be able to see the following after running the command.
That's it for SetAuthority
functionality. In the next chapter we will look at approve
instruction for delegating tokens to another account.