From b637a9f33e382a1f3a1d380b69a234d5d3c6872d Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Thu, 18 Jan 2024 00:30:24 -0500 Subject: [PATCH] Adding support for setMint IX for backfilling. --- .../generated/accounts/inscriptionMetadata.ts | 9 +- .../js/src/generated/errors/mplInscription.ts | 36 ++ .../js/src/generated/instructions/index.ts | 1 + .../js/src/generated/instructions/setMint.ts | 129 +++++ .../accounts/inscription_metadata.rs | 3 +- .../src/generated/errors/mpl_inscription.rs | 6 + .../rust/src/generated/instructions/mod.rs | 2 + .../src/generated/instructions/set_mint.rs | 443 ++++++++++++++++++ idls/mpl_inscription.json | 68 ++- programs/mpl-inscription/src/error.rs | 4 + .../mpl-inscription/src/instruction/mod.rs | 8 + .../src/processor/initialize_from_mint.rs | 1 + programs/mpl-inscription/src/processor/mod.rs | 6 + .../mpl-inscription/src/processor/set_mint.rs | 97 ++++ programs/mpl-inscription/src/state.rs | 6 +- 15 files changed, 813 insertions(+), 6 deletions(-) create mode 100644 clients/js/src/generated/instructions/setMint.ts create mode 100644 clients/rust/src/generated/instructions/set_mint.rs create mode 100644 programs/mpl-inscription/src/processor/set_mint.rs diff --git a/clients/js/src/generated/accounts/inscriptionMetadata.ts b/clients/js/src/generated/accounts/inscriptionMetadata.ts index 164e969..30deb86 100644 --- a/clients/js/src/generated/accounts/inscriptionMetadata.ts +++ b/clients/js/src/generated/accounts/inscriptionMetadata.ts @@ -54,6 +54,7 @@ export type InscriptionMetadataAccountData = { inscriptionBump: Option; updateAuthorities: Array; associatedInscriptions: Array; + mint: Option; padding: Array; }; @@ -66,6 +67,7 @@ export type InscriptionMetadataAccountDataArgs = { inscriptionBump: OptionOrNullable; updateAuthorities: Array; associatedInscriptions: Array; + mint: OptionOrNullable; padding: Array; }; @@ -83,7 +85,8 @@ export function getInscriptionMetadataAccountDataSerializer(): Serializer< ['inscriptionBump', option(u8())], ['updateAuthorities', array(publicKeySerializer())], ['associatedInscriptions', array(getAssociatedInscriptionSerializer())], - ['padding', array(u8(), { size: 8 })], + ['mint', option(publicKeySerializer())], + ['padding', array(u8(), { size: 7 })], ], { description: 'InscriptionMetadataAccountData' } ) as Serializer< @@ -176,6 +179,7 @@ export function getInscriptionMetadataGpaBuilder( inscriptionBump: OptionOrNullable; updateAuthorities: Array; associatedInscriptions: Array; + mint: OptionOrNullable; padding: Array; }>({ key: [0, getKeySerializer()], @@ -189,7 +193,8 @@ export function getInscriptionMetadataGpaBuilder( null, array(getAssociatedInscriptionSerializer()), ], - padding: [null, array(u8(), { size: 8 })], + mint: [null, option(publicKeySerializer())], + padding: [null, array(u8(), { size: 7 })], }) .deserializeUsing((account) => deserializeInscriptionMetadata(account) diff --git a/clients/js/src/generated/errors/mplInscription.ts b/clients/js/src/generated/errors/mplInscription.ts index 472d96d..13c79f8 100644 --- a/clients/js/src/generated/errors/mplInscription.ts +++ b/clients/js/src/generated/errors/mplInscription.ts @@ -243,6 +243,42 @@ export class AuthorityAlreadyExistsError extends ProgramError { codeToErrorMap.set(0x10, AuthorityAlreadyExistsError); nameToErrorMap.set('AuthorityAlreadyExists', AuthorityAlreadyExistsError); +/** RemainingAssociatedInscriptionAccounts: Cannot close Inscription accounts until all Associated Inscriptions are closed. */ +export class RemainingAssociatedInscriptionAccountsError extends ProgramError { + readonly name: string = 'RemainingAssociatedInscriptionAccounts'; + + readonly code: number = 0x11; // 17 + + constructor(program: Program, cause?: Error) { + super( + 'Cannot close Inscription accounts until all Associated Inscriptions are closed.', + program, + cause + ); + } +} +codeToErrorMap.set(0x11, RemainingAssociatedInscriptionAccountsError); +nameToErrorMap.set( + 'RemainingAssociatedInscriptionAccounts', + RemainingAssociatedInscriptionAccountsError +); + +/** InvalidInscriptionMetadataAccount: The inscription metadata account is invalid. */ +export class InvalidInscriptionMetadataAccountError extends ProgramError { + readonly name: string = 'InvalidInscriptionMetadataAccount'; + + readonly code: number = 0x12; // 18 + + constructor(program: Program, cause?: Error) { + super('The inscription metadata account is invalid.', program, cause); + } +} +codeToErrorMap.set(0x12, InvalidInscriptionMetadataAccountError); +nameToErrorMap.set( + 'InvalidInscriptionMetadataAccount', + InvalidInscriptionMetadataAccountError +); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 6493c67..23c610f 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -13,4 +13,5 @@ export * from './close'; export * from './createShard'; export * from './initializeAssociatedInscription'; export * from './removeAuthority'; +export * from './setMint'; export * from './writeData'; diff --git a/clients/js/src/generated/instructions/setMint.ts b/clients/js/src/generated/instructions/setMint.ts new file mode 100644 index 0000000..7b0b14c --- /dev/null +++ b/clients/js/src/generated/instructions/setMint.ts @@ -0,0 +1,129 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type SetMintInstructionAccounts = { + /** The account where data is stored. */ + mintInscriptionAccount: PublicKey | Pda; + /** The account to store the inscription account's metadata in. */ + inscriptionMetadataAccount: PublicKey | Pda; + /** The mint that will be used to derive the PDA. */ + mintAccount: PublicKey | Pda; + /** The account that will pay for the transaction and rent. */ + payer?: Signer; + /** System program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type SetMintInstructionData = { discriminator: number }; + +export type SetMintInstructionDataArgs = {}; + +export function getSetMintInstructionDataSerializer(): Serializer< + SetMintInstructionDataArgs, + SetMintInstructionData +> { + return mapSerializer( + struct([['discriminator', u8()]], { + description: 'SetMintInstructionData', + }), + (value) => ({ ...value, discriminator: 10 }) + ) as Serializer; +} + +// Instruction. +export function setMint( + context: Pick, + input: SetMintInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplInscription', + '1NSCRfGeyo7wPUazGbaPBUsTM49e1k2aXewHGARfzSo' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + mintInscriptionAccount: { + index: 0, + isWritable: false, + value: input.mintInscriptionAccount ?? null, + }, + inscriptionMetadataAccount: { + index: 1, + isWritable: true, + value: input.inscriptionMetadataAccount ?? null, + }, + mintAccount: { + index: 2, + isWritable: false, + value: input.mintAccount ?? null, + }, + payer: { index: 3, isWritable: true, value: input.payer ?? null }, + systemProgram: { + index: 4, + isWritable: false, + value: input.systemProgram ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getSetMintInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/rust/src/generated/accounts/inscription_metadata.rs b/clients/rust/src/generated/accounts/inscription_metadata.rs index 24b05b7..0dba3a6 100644 --- a/clients/rust/src/generated/accounts/inscription_metadata.rs +++ b/clients/rust/src/generated/accounts/inscription_metadata.rs @@ -27,7 +27,8 @@ pub struct InscriptionMetadata { pub inscription_bump: Option, pub update_authorities: Vec, pub associated_inscriptions: Vec, - pub padding: [u8; 8], + pub mint: Option, + pub padding: [u8; 7], } impl InscriptionMetadata { diff --git a/clients/rust/src/generated/errors/mpl_inscription.rs b/clients/rust/src/generated/errors/mpl_inscription.rs index 2367349..2106764 100644 --- a/clients/rust/src/generated/errors/mpl_inscription.rs +++ b/clients/rust/src/generated/errors/mpl_inscription.rs @@ -61,6 +61,12 @@ pub enum MplInscriptionError { /// 16 (0x10) - The authority already exists. #[error("The authority already exists.")] AuthorityAlreadyExists, + /// 17 (0x11) - Cannot close Inscription accounts until all Associated Inscriptions are closed. + #[error("Cannot close Inscription accounts until all Associated Inscriptions are closed.")] + RemainingAssociatedInscriptionAccounts, + /// 18 (0x12) - The inscription metadata account is invalid. + #[error("The inscription metadata account is invalid.")] + InvalidInscriptionMetadataAccount, } impl solana_program::program_error::PrintProgramError for MplInscriptionError { diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index a06b4a6..3217101 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod initialize; pub(crate) mod initialize_associated_inscription; pub(crate) mod initialize_from_mint; pub(crate) mod remove_authority; +pub(crate) mod set_mint; pub(crate) mod write_data; pub use self::add_authority::*; @@ -25,4 +26,5 @@ pub use self::initialize::*; pub use self::initialize_associated_inscription::*; pub use self::initialize_from_mint::*; pub use self::remove_authority::*; +pub use self::set_mint::*; pub use self::write_data::*; diff --git a/clients/rust/src/generated/instructions/set_mint.rs b/clients/rust/src/generated/instructions/set_mint.rs new file mode 100644 index 0000000..0584003 --- /dev/null +++ b/clients/rust/src/generated/instructions/set_mint.rs @@ -0,0 +1,443 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct SetMint { + /// The account where data is stored. + pub mint_inscription_account: solana_program::pubkey::Pubkey, + /// The account to store the inscription account's metadata in. + pub inscription_metadata_account: solana_program::pubkey::Pubkey, + /// The mint that will be used to derive the PDA. + pub mint_account: solana_program::pubkey::Pubkey, + /// The account that will pay for the transaction and rent. + pub payer: solana_program::pubkey::Pubkey, + /// System program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl SetMint { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.mint_inscription_account, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.inscription_metadata_account, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.mint_account, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = SetMintInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_INSCRIPTION_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct SetMintInstructionData { + discriminator: u8, +} + +impl SetMintInstructionData { + fn new() -> Self { + Self { discriminator: 10 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct SetMintBuilder { + mint_inscription_account: Option, + inscription_metadata_account: Option, + mint_account: Option, + payer: Option, + system_program: Option, + __remaining_accounts: Vec, +} + +impl SetMintBuilder { + pub fn new() -> Self { + Self::default() + } + /// The account where data is stored. + #[inline(always)] + pub fn mint_inscription_account( + &mut self, + mint_inscription_account: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.mint_inscription_account = Some(mint_inscription_account); + self + } + /// The account to store the inscription account's metadata in. + #[inline(always)] + pub fn inscription_metadata_account( + &mut self, + inscription_metadata_account: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.inscription_metadata_account = Some(inscription_metadata_account); + self + } + /// The mint that will be used to derive the PDA. + #[inline(always)] + pub fn mint_account(&mut self, mint_account: solana_program::pubkey::Pubkey) -> &mut Self { + self.mint_account = Some(mint_account); + self + } + /// The account that will pay for the transaction and rent. + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// System program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = SetMint { + mint_inscription_account: self + .mint_inscription_account + .expect("mint_inscription_account is not set"), + inscription_metadata_account: self + .inscription_metadata_account + .expect("inscription_metadata_account is not set"), + mint_account: self.mint_account.expect("mint_account is not set"), + payer: self.payer.expect("payer is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `set_mint` CPI accounts. +pub struct SetMintCpiAccounts<'a, 'b> { + /// The account where data is stored. + pub mint_inscription_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The account to store the inscription account's metadata in. + pub inscription_metadata_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The mint that will be used to derive the PDA. + pub mint_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The account that will pay for the transaction and rent. + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// System program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_mint` CPI instruction. +pub struct SetMintCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The account where data is stored. + pub mint_inscription_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The account to store the inscription account's metadata in. + pub inscription_metadata_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The mint that will be used to derive the PDA. + pub mint_account: &'b solana_program::account_info::AccountInfo<'a>, + /// The account that will pay for the transaction and rent. + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// System program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> SetMintCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetMintCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + mint_inscription_account: accounts.mint_inscription_account, + inscription_metadata_account: accounts.inscription_metadata_account, + mint_account: accounts.mint_account, + payer: accounts.payer, + system_program: accounts.system_program, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.mint_inscription_account.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.inscription_metadata_account.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.mint_account.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = SetMintInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_INSCRIPTION_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.mint_inscription_account.clone()); + account_infos.push(self.inscription_metadata_account.clone()); + account_infos.push(self.mint_account.clone()); + account_infos.push(self.payer.clone()); + account_infos.push(self.system_program.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `set_mint` CPI instruction builder. +pub struct SetMintCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetMintCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetMintCpiBuilderInstruction { + __program: program, + mint_inscription_account: None, + inscription_metadata_account: None, + mint_account: None, + payer: None, + system_program: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The account where data is stored. + #[inline(always)] + pub fn mint_inscription_account( + &mut self, + mint_inscription_account: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.mint_inscription_account = Some(mint_inscription_account); + self + } + /// The account to store the inscription account's metadata in. + #[inline(always)] + pub fn inscription_metadata_account( + &mut self, + inscription_metadata_account: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.inscription_metadata_account = Some(inscription_metadata_account); + self + } + /// The mint that will be used to derive the PDA. + #[inline(always)] + pub fn mint_account( + &mut self, + mint_account: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.mint_account = Some(mint_account); + self + } + /// The account that will pay for the transaction and rent. + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// System program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = SetMintCpi { + __program: self.instruction.__program, + + mint_inscription_account: self + .instruction + .mint_inscription_account + .expect("mint_inscription_account is not set"), + + inscription_metadata_account: self + .instruction + .inscription_metadata_account + .expect("inscription_metadata_account is not set"), + + mint_account: self + .instruction + .mint_account + .expect("mint_account is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct SetMintCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + mint_inscription_account: Option<&'b solana_program::account_info::AccountInfo<'a>>, + inscription_metadata_account: Option<&'b solana_program::account_info::AccountInfo<'a>>, + mint_account: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/idls/mpl_inscription.json b/idls/mpl_inscription.json index 69d8e41..8e16e77 100644 --- a/idls/mpl_inscription.json +++ b/idls/mpl_inscription.json @@ -567,6 +567,56 @@ "type": "u8", "value": 9 } + }, + { + "name": "SetMint", + "accounts": [ + { + "name": "mintInscriptionAccount", + "isMut": false, + "isSigner": false, + "docs": [ + "The account where data is stored." + ] + }, + { + "name": "inscriptionMetadataAccount", + "isMut": true, + "isSigner": false, + "docs": [ + "The account to store the inscription account's metadata in." + ] + }, + { + "name": "mintAccount", + "isMut": false, + "isSigner": false, + "docs": [ + "The mint that will be used to derive the PDA." + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account that will pay for the transaction and rent." + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System program" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 10 + } } ], "accounts": [ @@ -619,12 +669,18 @@ } } }, + { + "name": "mint", + "type": { + "option": "publicKey" + } + }, { "name": "padding", "type": { "array": [ "u8", - 8 + 7 ] } } @@ -908,6 +964,16 @@ "code": 16, "name": "AuthorityAlreadyExists", "msg": "The authority already exists." + }, + { + "code": 17, + "name": "RemainingAssociatedInscriptionAccounts", + "msg": "Cannot close Inscription accounts until all Associated Inscriptions are closed." + }, + { + "code": 18, + "name": "InvalidInscriptionMetadataAccount", + "msg": "The inscription metadata account is invalid." } ], "metadata": { diff --git a/programs/mpl-inscription/src/error.rs b/programs/mpl-inscription/src/error.rs index 4547d90..72ee86e 100644 --- a/programs/mpl-inscription/src/error.rs +++ b/programs/mpl-inscription/src/error.rs @@ -79,6 +79,10 @@ pub enum MplInscriptionError { /// 17 - Remaining Associated Inscription Accounts #[error("Cannot close Inscription accounts until all Associated Inscriptions are closed.")] RemainingAssociatedInscriptionAccounts, + + /// 18 - Invalid Inscription Metadata Account + #[error("The inscription metadata account is invalid.")] + InvalidInscriptionMetadataAccount, } impl PrintProgramError for MplInscriptionError { diff --git a/programs/mpl-inscription/src/instruction/mod.rs b/programs/mpl-inscription/src/instruction/mod.rs index ead117c..47d95b1 100644 --- a/programs/mpl-inscription/src/instruction/mod.rs +++ b/programs/mpl-inscription/src/instruction/mod.rs @@ -85,6 +85,14 @@ pub enum MplInscriptionInstruction { #[account(3, optional, signer, name="authority", desc="The authority of the inscription account.")] #[account(4, name="system_program", desc = "System program")] Allocate(AllocateArgs), + + /// Set the mint for the inscription metadata account. + #[account(0, name="mint_inscription_account", desc = "The account where data is stored.")] + #[account(1, writable, name="inscription_metadata_account", desc = "The account to store the inscription account's metadata in.")] + #[account(2, name="mint_account", desc="The mint that will be used to derive the PDA.")] + #[account(3, writable, signer, name="payer", desc="The account that will pay for the transaction and rent.")] + #[account(4, name="system_program", desc = "System program")] + SetMint, } #[repr(C)] diff --git a/programs/mpl-inscription/src/processor/initialize_from_mint.rs b/programs/mpl-inscription/src/processor/initialize_from_mint.rs index 11bc327..337ba40 100644 --- a/programs/mpl-inscription/src/processor/initialize_from_mint.rs +++ b/programs/mpl-inscription/src/processor/initialize_from_mint.rs @@ -115,6 +115,7 @@ pub(crate) fn process_initialize_from_mint<'a>(accounts: &'a [AccountInfo<'a>]) bump, inscription_bump: Some(inscription_bump), update_authorities: vec![token_metadata.update_authority], + mint: Some(*ctx.accounts.mint_account.key), ..InscriptionMetadata::default() }; diff --git a/programs/mpl-inscription/src/processor/mod.rs b/programs/mpl-inscription/src/processor/mod.rs index c04238f..df7dec5 100644 --- a/programs/mpl-inscription/src/processor/mod.rs +++ b/programs/mpl-inscription/src/processor/mod.rs @@ -11,6 +11,7 @@ mod initialize; mod initialize_associated_inscription; mod initialize_from_mint; mod remove_authority; +mod set_mint; mod write_data; use add_authority::*; @@ -22,6 +23,7 @@ use initialize::*; use initialize_associated_inscription::*; use initialize_from_mint::*; use remove_authority::*; +use set_mint::*; use write_data::*; pub struct Processor; @@ -74,6 +76,10 @@ impl Processor { msg!("Instruction: Allocate"); process_allocate(accounts, args) } + MplInscriptionInstruction::SetMint => { + msg!("Instruction: SetMint"); + process_set_mint(accounts) + } } } } diff --git a/programs/mpl-inscription/src/processor/set_mint.rs b/programs/mpl-inscription/src/processor/set_mint.rs new file mode 100644 index 0000000..63d0d35 --- /dev/null +++ b/programs/mpl-inscription/src/processor/set_mint.rs @@ -0,0 +1,97 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use mpl_utils::{ + assert_derivation, assert_owned_by, assert_signer, resize_or_reallocate_account_raw, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy, +}; + +use crate::{ + error::MplInscriptionError, + instruction::accounts::SetMintAccounts, + state::{InscriptionMetadata, Key, PREFIX}, +}; + +pub(crate) fn process_set_mint<'a>(accounts: &'a [AccountInfo<'a>]) -> ProgramResult { + let ctx = &SetMintAccounts::context(accounts)?; + + // Check that the account is already initialized. + if (ctx.accounts.mint_inscription_account.owner != &crate::ID) + || ctx.accounts.mint_inscription_account.data_is_empty() + { + return Err(MplInscriptionError::NotInitialized.into()); + } + + // Check that the account is already initialized. + if (ctx.accounts.inscription_metadata_account.owner != &crate::ID) + || ctx.accounts.inscription_metadata_account.data_is_empty() + { + return Err(MplInscriptionError::NotInitialized.into()); + } + + assert_owned_by( + ctx.accounts.mint_account, + &spl_token::ID, + MplInscriptionError::IncorrectOwner, + )?; + + // Verify that the derived address is correct for the metadata account. + let _inscription_bump = assert_derivation( + &crate::ID, + ctx.accounts.mint_inscription_account, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + ctx.accounts.mint_account.key.as_ref(), + ], + MplInscriptionError::DerivedKeyInvalid, + )?; + + // Verify that the derived address is correct for the metadata account. + let _bump = assert_derivation( + &crate::ID, + ctx.accounts.inscription_metadata_account, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + ctx.accounts.mint_inscription_account.key.as_ref(), + ], + MplInscriptionError::DerivedKeyInvalid, + )?; + + assert_signer(ctx.accounts.payer)?; + + // Initialize the inscription metadata. + let mut inscription_metadata = InscriptionMetadata::try_from_slice( + &ctx.accounts.inscription_metadata_account.data.borrow(), + )?; + + // Check that the account is a valid inscription metadata account. + if inscription_metadata.key != Key::MintInscriptionMetadataAccount { + return Err(MplInscriptionError::InvalidInscriptionMetadataAccount.into()); + } + + inscription_metadata.mint = Some(*ctx.accounts.mint_account.key); + + let serialized_metadata = &inscription_metadata.try_to_vec()?; + + resize_or_reallocate_account_raw( + ctx.accounts.inscription_metadata_account, + ctx.accounts.payer, + ctx.accounts.system_program, + serialized_metadata.len(), + )?; + + // Write the inscription metadata to the metadata account. + sol_memcpy( + &mut ctx + .accounts + .inscription_metadata_account + .try_borrow_mut_data()?, + serialized_metadata, + serialized_metadata.len(), + ); + + Ok(()) +} diff --git a/programs/mpl-inscription/src/state.rs b/programs/mpl-inscription/src/state.rs index bda47b6..3cef858 100644 --- a/programs/mpl-inscription/src/state.rs +++ b/programs/mpl-inscription/src/state.rs @@ -43,7 +43,8 @@ pub struct InscriptionMetadata { pub inscription_bump: Option, pub update_authorities: Vec, pub associated_inscriptions: Vec, - pub _padding: [u8; 8], + pub mint: Option, + pub _padding: [u8; 7], } impl Default for InscriptionMetadata { @@ -57,7 +58,8 @@ impl Default for InscriptionMetadata { inscription_bump: None, update_authorities: vec![], associated_inscriptions: vec![], - _padding: [0; 8], + mint: None, + _padding: [0; 7], } } }