学了这么多天了
还是没有入门
我在干吗
我们来看一下CPI
Cross-Program Invocations
比如有2个program,token和acme
token实现了pay()方法,acme实现了launch_missiles()方法
acme可以调用token的pay()方法
比如
mod acme {
use token_instruction;
fn launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
...
}
fn pay_and_launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
let alice_pubkey = accounts[1].key;
let instruction = token_instruction::pay(&alice_pubkey);
invoke(&instruction, accounts)?;
launch_missiles(accounts)?;
}
我们来看一下mpl的代码
首先是entrypoint入口
entrypoint!(process_instruction);
fn process_instruction<'a>(
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'a>],
instruction_data: &[u8],
) -> ProgramResult {
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
// catch the error so we can print it
error.print::<MetadataError>();
return Err(error);
}
Ok(())
}
指令进来以后,就走了process的process_instruction方法
pub fn process_instruction<'a>(
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'a>],
input: &[u8],
) -> ProgramResult {
let instruction = MetadataInstruction::try_from_slice(input)?;
match instruction {
MetadataInstruction::CreateMetadataAccount(args) => {
msg!("(Deprecated as of 1.1.0) Instruction: Create Metadata Accounts");
process_deprecated_create_metadata_accounts(
program_id,
accounts,
args.data,
false,
args.is_mutable,
)
}
然后把input解析出来,match一下MetadataInstruction
然后还有创建instruction的方法
/// Creates an CreateMetadataAccounts instruction
#[allow(clippy::too_many_arguments)]
pub fn create_metadata_accounts_v2(
program_id: Pubkey,
metadata_account: Pubkey,
mint: Pubkey,
mint_authority: Pubkey,
payer: Pubkey,
update_authority: Pubkey,
name: String,
symbol: String,
uri: String,
creators: Option<Vec<Creator>>,
seller_fee_basis_points: u16,
update_authority_is_signer: bool,
is_mutable: bool,
collection: Option<Collection>,
uses: Option<Uses>,
) -> Instruction {
Instruction {
program_id,
accounts: vec![
AccountMeta::new(metadata_account, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(mint_authority, true),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(update_authority, update_authority_is_signer),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
data: MetadataInstruction::CreateMetadataAccountV2(CreateMetadataAccountArgsV2 {
data: DataV2 {
name,
symbol,
uri,
seller_fee_basis_points,
creators,
collection,
uses,
},
is_mutable,
})
.try_to_vec()
.unwrap(),
}
}
我们看下invoke这个方法
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
invoke_signed(instruction, account_infos, &[])
}
传入一个指令和account_infos
用anchor来调用metaplex的mpl合约
我们来看下示例代码
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke;
use mpl_token_metadata::instruction::{create_master_edition_v3, create_metadata_accounts_v2};
use anchor_spl::token;
use anchor_spl::token::{MintTo, Token};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod nft {
use super::*;
pub fn mint_nft(
ctx: Context<MintNFT>,
creator_key: Pubkey,
uri: String,
title: String,
) -> Result<()> {
msg!("Initializing Mint Ticket");
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.payer.to_account_info(),
};
msg!("CPI Accounts Assigned");
let cpi_program = ctx.accounts.token_program.to_account_info();
msg!("CPI Program Assigned");
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
msg!("CPI Context Assigned");
token::mint_to(cpi_ctx, 1)?;
msg!("Token Minted !!!");
let account_info = vec![
ctx.accounts.metadata.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
msg!("Account Info Assigned");
let creator = vec![
mpl_token_metadata::state::Creator {
address: creator_key,
verified: false,
share: 100,
},
mpl_token_metadata::state::Creator {
address: ctx.accounts.mint_authority.key(),
verified: false,
share: 0,
},
];
msg!("Creator Assigned");
let symbol = std::string::ToString::to_string("symb");
invoke(
&create_metadata_accounts_v2(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.metadata.key(),
ctx.accounts.mint.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.payer.key(),
ctx.accounts.payer.key(),
title,
symbol,
uri,
Some(creator),
1,
true,
false,
None,
None,
),
account_info.as_slice(),
)?;
msg!("Metadata Account Created !!!");
let master_edition_infos = vec![
ctx.accounts.master_edition.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.metadata.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
msg!("Master Edition Account Infos Assigned");
invoke(
&create_master_edition_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.master_edition.key(),
ctx.accounts.mint.key(),
ctx.accounts.payer.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.metadata.key(),
ctx.accounts.payer.key(),
Some(0),
),
master_edition_infos.as_slice(),
)?;
msg!("Master Edition Nft Minted !!!");
Ok(())
}
}
#[derive(Accounts)]
pub struct MintNFT<'info> {
#[account(mut)]
pub mint_authority: Signer<'info>,
#[account(mut)]
pub mint: AccountInfo<'info>,
pub token_program: Program<'info, Token>,
#[account(mut)]
pub metadata: AccountInfo<'info>,
#[account(mut)]
pub token_account: AccountInfo<'info>,
pub token_metadata_program: AccountInfo<'info>,
#[account(mut)]
pub payer: AccountInfo<'info>,
pub system_program: Program<'info, System>,
pub rent: AccountInfo<'info>,
#[account(mut)]
pub master_edition: AccountInfo<'info>,
}
如果我们把create_metadata_account和create_master_edition分开成2个部分
我们先来看create_metadata_account
/// Create Metadata object.
#[account(0, writable, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")]
#[account(1, name="mint", desc="Mint of token asset")]
#[account(2, signer, name="mint_authority", desc="Mint authority")]
#[account(3, signer, name="payer", desc="payer")]
#[account(4, name="update_authority", desc="update authority info")]
#[account(5, name="system_program", desc="System program")]
#[account(6, name="rent", desc="Rent info")]
CreateMetadataAccountV2(CreateMetadataAccountArgsV2),
看下mpl如何创建这个指令
/// Creates an CreateMetadataAccounts instruction
#[allow(clippy::too_many_arguments)]
pub fn create_metadata_accounts_v2(
program_id: Pubkey,
metadata_account: Pubkey,
mint: Pubkey,
mint_authority: Pubkey,
payer: Pubkey,
update_authority: Pubkey,
name: String,
symbol: String,
uri: String,
creators: Option<Vec<Creator>>,
seller_fee_basis_points: u16,
update_authority_is_signer: bool,
is_mutable: bool,
collection: Option<Collection>,
uses: Option<Uses>,
) -> Instruction {
Instruction {
program_id,
accounts: vec![
AccountMeta::new(metadata_account, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(mint_authority, true),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(update_authority, update_authority_is_signer),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
data: MetadataInstruction::CreateMetadataAccountV2(CreateMetadataAccountArgsV2 {
data: DataV2 {
name,
symbol,
uri,
seller_fee_basis_points,
creators,
collection,
uses,
},
is_mutable,
})
.try_to_vec()
.unwrap(),
}
}
token program mint
然后我们来看一下token的mint
#[derive(Accounts)]
pub struct MintTo<'info> {
pub mint: AccountInfo<'info>,
pub to: AccountInfo<'info>,
pub authority: AccountInfo<'info>,
}
我们看cpiContext是如何创建的
impl<'a, 'b, 'c, 'info, T> CpiContext<'a, 'b, 'c, 'info, T>
where
T: ToAccountMetas + ToAccountInfos<'info>,
{
pub fn new(program: AccountInfo<'info>, accounts: T) -> Self {
Self {
accounts,
program,
remaining_accounts: Vec::new(),
signer_seeds: &[],
}
}
传一个program,然后再传accounts
再看一下anchor_spl的mint_to方法
pub fn mint_to<'a, 'b, 'c, 'info>(
ctx: CpiContext<'a, 'b, 'c, 'info, MintTo<'info>>,
amount: u64,
) -> Result<()> {
let ix = spl_token::instruction::mint_to(
&spl_token::ID,
ctx.accounts.mint.key,
ctx.accounts.to.key,
ctx.accounts.authority.key,
&[],
amount,
)?;
solana_program::program::invoke_signed(
&ix,
&[
ctx.accounts.to.clone(),
ctx.accounts.mint.clone(),
ctx.accounts.authority.clone(),
],
ctx.signer_seeds,
)
.map_err(Into::into)
}
然后我们调用一下
pub fn mint_token(
ctx: Context<MintToken>,
) -> Result<()> {
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.payer.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::mint_to(cpi_ctx, 1)?;
Ok(())
}
先是program,然后是accounts
然后用cpiContext去调用mint_to
再看一下参数
#[derive(Accounts)]
pub struct MintToken<'info> {
pub token_program: Program<'info, Token>,
pub mint: AccountInfo<'info>,
pub token_account: AccountInfo<'info>,
pub payer: AccountInfo<'info>,
}
然后我们看一下create_metadata_account
accounts是7个
/// Create Metadata object.
#[account(0, writable, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")]
#[account(1, name="mint", desc="Mint of token asset")]
#[account(2, signer, name="mint_authority", desc="Mint authority")]
#[account(3, signer, name="payer", desc="payer")]
#[account(4, name="update_authority", desc="update authority info")]
#[account(5, name="system_program", desc="System program")]
#[account(6, name="rent", desc="Rent info")]
CreateMetadataAccountV2(CreateMetadataAccountArgsV2),
let account_info = vec![
ctx.accounts.metadata.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.update_authority.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
然后我们看create_master_edition的accounts
/// Register a Metadata as a Master Edition V2, which means Edition V2s can be minted.
/// Henceforth, no further tokens will be mintable from this primary mint. Will throw an error if more than one
/// token exists, and will throw an error if less than one token exists in this primary mint.
#[account(0, writable, name="edition", desc="Unallocated edition V2 account with address as pda of ['metadata', program id, mint, 'edition']")]
#[account(1, writable, name="mint", desc="Metadata mint")]
#[account(2, signer, name="update_authority", desc="Update authority")]
#[account(3, signer, name="mint_authority", desc="Mint authority on the metadata's mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")]
#[account(4, signer, name="payer", desc="payer")]
#[account(5, writable, name="metadata", desc="Metadata account")]
#[account(6, name="token_program", desc="Token program")]
#[account(7, name="system_program", desc="System program")]
#[account(8, name="rent", desc="Rent info")]
CreateMasterEditionV3(CreateMasterEditionArgs),
9个account
let master_edition_infos = vec![
ctx.accounts.edition.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.update_authority.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.metadata.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
再看instruction
invoke(
&create_master_edition_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.edition.key(),
ctx.accounts.mint.key(),
ctx.accounts.update_authority.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.metadata.key(),
ctx.accounts.payer.key(),
Some(0),
),
master_edition_infos.as_slice(),
)?;