diff --git a/Cargo.lock b/Cargo.lock index 93b1cd86..fbcb77f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2700,7 +2700,7 @@ dependencies = [ [[package]] name = "mpl-core" -version = "0.11.0" +version = "0.10.1-alpha.2" dependencies = [ "anchor-lang", "assert_matches", diff --git a/clients/js/src/generated/accounts/groupV1.ts b/clients/js/src/generated/accounts/groupV1.ts new file mode 100644 index 00000000..2a4c1d9c --- /dev/null +++ b/clients/js/src/generated/accounts/groupV1.ts @@ -0,0 +1,160 @@ +/** + * 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 { + Account, + Context, + Pda, + PublicKey, + RpcAccount, + RpcGetAccountOptions, + RpcGetAccountsOptions, + assertAccountExists, + deserializeAccount, + gpaBuilder, + publicKey as toPublicKey, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + publicKey as publicKeySerializer, + string, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '../types'; + +export type GroupV1 = Account; + +export type GroupV1AccountData = { + key: Key; + updateAuthority: PublicKey; + name: string; + uri: string; + collections: Array; + groups: Array; + parentGroups: Array; + assets: Array; +}; + +export type GroupV1AccountDataArgs = { + key: KeyArgs; + updateAuthority: PublicKey; + name: string; + uri: string; + collections: Array; + groups: Array; + parentGroups: Array; + assets: Array; +}; + +export function getGroupV1AccountDataSerializer(): Serializer< + GroupV1AccountDataArgs, + GroupV1AccountData +> { + return struct( + [ + ['key', getKeySerializer()], + ['updateAuthority', publicKeySerializer()], + ['name', string()], + ['uri', string()], + ['collections', array(publicKeySerializer())], + ['groups', array(publicKeySerializer())], + ['parentGroups', array(publicKeySerializer())], + ['assets', array(publicKeySerializer())], + ], + { description: 'GroupV1AccountData' } + ) as Serializer; +} + +export function deserializeGroupV1(rawAccount: RpcAccount): GroupV1 { + return deserializeAccount(rawAccount, getGroupV1AccountDataSerializer()); +} + +export async function fetchGroupV1( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + assertAccountExists(maybeAccount, 'GroupV1'); + return deserializeGroupV1(maybeAccount); +} + +export async function safeFetchGroupV1( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + return maybeAccount.exists ? deserializeGroupV1(maybeAccount) : null; +} + +export async function fetchAllGroupV1( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts.map((maybeAccount) => { + assertAccountExists(maybeAccount, 'GroupV1'); + return deserializeGroupV1(maybeAccount); + }); +} + +export async function safeFetchAllGroupV1( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts + .filter((maybeAccount) => maybeAccount.exists) + .map((maybeAccount) => deserializeGroupV1(maybeAccount as RpcAccount)); +} + +export function getGroupV1GpaBuilder( + context: Pick +) { + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + return gpaBuilder(context, programId) + .registerFields<{ + key: KeyArgs; + updateAuthority: PublicKey; + name: string; + uri: string; + collections: Array; + groups: Array; + parentGroups: Array; + assets: Array; + }>({ + key: [0, getKeySerializer()], + updateAuthority: [1, publicKeySerializer()], + name: [33, string()], + uri: [null, string()], + collections: [null, array(publicKeySerializer())], + groups: [null, array(publicKeySerializer())], + parentGroups: [null, array(publicKeySerializer())], + assets: [null, array(publicKeySerializer())], + }) + .deserializeUsing((account) => deserializeGroupV1(account)); +} diff --git a/clients/js/src/generated/accounts/index.ts b/clients/js/src/generated/accounts/index.ts index aa39fc2a..f2b2868a 100644 --- a/clients/js/src/generated/accounts/index.ts +++ b/clients/js/src/generated/accounts/index.ts @@ -9,6 +9,7 @@ export * from './assetSigner'; export * from './assetV1'; export * from './collectionV1'; +export * from './groupV1'; export * from './hashedAssetV1'; export * from './pluginHeaderV1'; export * from './pluginRegistryV1'; diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index 50026e57..4e4f3fd9 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -739,6 +739,36 @@ export class BlockedByBubblegumV2Error extends ProgramError { codeToErrorMap.set(0x32, BlockedByBubblegumV2Error); nameToErrorMap.set('BlockedByBubblegumV2', BlockedByBubblegumV2Error); +/** GroupMustBeEmpty: Group must be empty to be closed */ +export class GroupMustBeEmptyError extends ProgramError { + override readonly name: string = 'GroupMustBeEmpty'; + + readonly code: number = 0x33; // 51 + + constructor(program: Program, cause?: Error) { + super('Group must be empty to be closed', program, cause); + } +} +codeToErrorMap.set(0x33, GroupMustBeEmptyError); +nameToErrorMap.set('GroupMustBeEmpty', GroupMustBeEmptyError); + +/** DuplicateEntry: Duplicate entry provided when adding relationships to a group */ +export class DuplicateEntryError extends ProgramError { + override readonly name: string = 'DuplicateEntry'; + + readonly code: number = 0x34; // 52 + + constructor(program: Program, cause?: Error) { + super( + 'Duplicate entry provided when adding relationships to a group', + program, + cause + ); + } +} +codeToErrorMap.set(0x34, DuplicateEntryError); +nameToErrorMap.set('DuplicateEntry', DuplicateEntryError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/addAssetsToGroupV1.ts b/clients/js/src/generated/instructions/addAssetsToGroupV1.ts new file mode 100644 index 00000000..52b69aa6 --- /dev/null +++ b/clients/js/src/generated/instructions/addAssetsToGroupV1.ts @@ -0,0 +1,133 @@ +/** + * 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 AddAssetsToGroupV1InstructionAccounts = { + /** The address of the group to modify */ + group: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the group/assets */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type AddAssetsToGroupV1InstructionData = { discriminator: number }; + +export type AddAssetsToGroupV1InstructionDataArgs = {}; + +export function getAddAssetsToGroupV1InstructionDataSerializer(): Serializer< + AddAssetsToGroupV1InstructionDataArgs, + AddAssetsToGroupV1InstructionData +> { + return mapSerializer< + AddAssetsToGroupV1InstructionDataArgs, + any, + AddAssetsToGroupV1InstructionData + >( + struct([['discriminator', u8()]], { + description: 'AddAssetsToGroupV1InstructionData', + }), + (value) => ({ ...value, discriminator: 35 }) + ) as Serializer< + AddAssetsToGroupV1InstructionDataArgs, + AddAssetsToGroupV1InstructionData + >; +} + +// Instruction. +export function addAssetsToGroupV1( + context: Pick, + input: AddAssetsToGroupV1InstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // 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 = getAddAssetsToGroupV1InstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addCollectionsToGroupV1.ts b/clients/js/src/generated/instructions/addCollectionsToGroupV1.ts new file mode 100644 index 00000000..fb230ec1 --- /dev/null +++ b/clients/js/src/generated/instructions/addCollectionsToGroupV1.ts @@ -0,0 +1,135 @@ +/** + * 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 AddCollectionsToGroupV1InstructionAccounts = { + /** The address of the group to modify */ + group: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the group/collections */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type AddCollectionsToGroupV1InstructionData = { discriminator: number }; + +export type AddCollectionsToGroupV1InstructionDataArgs = {}; + +export function getAddCollectionsToGroupV1InstructionDataSerializer(): Serializer< + AddCollectionsToGroupV1InstructionDataArgs, + AddCollectionsToGroupV1InstructionData +> { + return mapSerializer< + AddCollectionsToGroupV1InstructionDataArgs, + any, + AddCollectionsToGroupV1InstructionData + >( + struct([['discriminator', u8()]], { + description: 'AddCollectionsToGroupV1InstructionData', + }), + (value) => ({ ...value, discriminator: 33 }) + ) as Serializer< + AddCollectionsToGroupV1InstructionDataArgs, + AddCollectionsToGroupV1InstructionData + >; +} + +// Instruction. +export function addCollectionsToGroupV1( + context: Pick, + input: AddCollectionsToGroupV1InstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // 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 = getAddCollectionsToGroupV1InstructionDataSerializer().serialize( + {} + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addGroupExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/addGroupExternalPluginAdapterV1.ts new file mode 100644 index 00000000..3a864ebd --- /dev/null +++ b/clients/js/src/generated/instructions/addGroupExternalPluginAdapterV1.ts @@ -0,0 +1,167 @@ +/** + * 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'; +import { + BaseExternalPluginAdapterInitInfo, + BaseExternalPluginAdapterInitInfoArgs, + getBaseExternalPluginAdapterInitInfoSerializer, +} from '../types'; + +// Accounts. +export type AddGroupExternalPluginAdapterV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type AddGroupExternalPluginAdapterV1InstructionData = { + discriminator: number; + initInfo: BaseExternalPluginAdapterInitInfo; +}; + +export type AddGroupExternalPluginAdapterV1InstructionDataArgs = { + initInfo: BaseExternalPluginAdapterInitInfoArgs; +}; + +export function getAddGroupExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + AddGroupExternalPluginAdapterV1InstructionDataArgs, + AddGroupExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + AddGroupExternalPluginAdapterV1InstructionDataArgs, + any, + AddGroupExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['initInfo', getBaseExternalPluginAdapterInitInfoSerializer()], + ], + { description: 'AddGroupExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 44 }) + ) as Serializer< + AddGroupExternalPluginAdapterV1InstructionDataArgs, + AddGroupExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type AddGroupExternalPluginAdapterV1InstructionArgs = + AddGroupExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function addGroupExternalPluginAdapterV1( + context: Pick, + input: AddGroupExternalPluginAdapterV1InstructionAccounts & + AddGroupExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: AddGroupExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // 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 = + getAddGroupExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as AddGroupExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addGroupPluginV1.ts b/clients/js/src/generated/instructions/addGroupPluginV1.ts new file mode 100644 index 00000000..256f0b53 --- /dev/null +++ b/clients/js/src/generated/instructions/addGroupPluginV1.ts @@ -0,0 +1,172 @@ +/** + * 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, + Option, + OptionOrNullable, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + option, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + Plugin, + PluginArgs, + getBasePluginAuthoritySerializer, + getPluginSerializer, +} from '../types'; + +// Accounts. +export type AddGroupPluginV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type AddGroupPluginV1InstructionData = { + discriminator: number; + plugin: Plugin; + initAuthority: Option; +}; + +export type AddGroupPluginV1InstructionDataArgs = { + plugin: PluginArgs; + initAuthority: OptionOrNullable; +}; + +export function getAddGroupPluginV1InstructionDataSerializer(): Serializer< + AddGroupPluginV1InstructionDataArgs, + AddGroupPluginV1InstructionData +> { + return mapSerializer< + AddGroupPluginV1InstructionDataArgs, + any, + AddGroupPluginV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['plugin', getPluginSerializer()], + ['initAuthority', option(getBasePluginAuthoritySerializer())], + ], + { description: 'AddGroupPluginV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 39 }) + ) as Serializer< + AddGroupPluginV1InstructionDataArgs, + AddGroupPluginV1InstructionData + >; +} + +// Args. +export type AddGroupPluginV1InstructionArgs = + AddGroupPluginV1InstructionDataArgs; + +// Instruction. +export function addGroupPluginV1( + context: Pick, + input: AddGroupPluginV1InstructionAccounts & AddGroupPluginV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: AddGroupPluginV1InstructionArgs = { ...input }; + + // 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 = getAddGroupPluginV1InstructionDataSerializer().serialize( + resolvedArgs as AddGroupPluginV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addGroupsToGroupV1.ts b/clients/js/src/generated/instructions/addGroupsToGroupV1.ts new file mode 100644 index 00000000..f5ec482d --- /dev/null +++ b/clients/js/src/generated/instructions/addGroupsToGroupV1.ts @@ -0,0 +1,154 @@ +/** + * 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, + array, + mapSerializer, + publicKey as publicKeySerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type AddGroupsToGroupV1InstructionAccounts = { + /** The address of the parent group to modify */ + parentGroup: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the groups */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type AddGroupsToGroupV1InstructionData = { + discriminator: number; + groups: Array; +}; + +export type AddGroupsToGroupV1InstructionDataArgs = { + groups: Array; +}; + +export function getAddGroupsToGroupV1InstructionDataSerializer(): Serializer< + AddGroupsToGroupV1InstructionDataArgs, + AddGroupsToGroupV1InstructionData +> { + return mapSerializer< + AddGroupsToGroupV1InstructionDataArgs, + any, + AddGroupsToGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['groups', array(publicKeySerializer())], + ], + { description: 'AddGroupsToGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 37 }) + ) as Serializer< + AddGroupsToGroupV1InstructionDataArgs, + AddGroupsToGroupV1InstructionData + >; +} + +// Args. +export type AddGroupsToGroupV1InstructionArgs = + AddGroupsToGroupV1InstructionDataArgs; + +// Instruction. +export function addGroupsToGroupV1( + context: Pick, + input: AddGroupsToGroupV1InstructionAccounts & + AddGroupsToGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + parentGroup: { + index: 0, + isWritable: true as boolean, + value: input.parentGroup ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: AddGroupsToGroupV1InstructionArgs = { ...input }; + + // 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 = getAddGroupsToGroupV1InstructionDataSerializer().serialize( + resolvedArgs as AddGroupsToGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/approveGroupPluginAuthorityV1.ts b/clients/js/src/generated/instructions/approveGroupPluginAuthorityV1.ts new file mode 100644 index 00000000..1708921a --- /dev/null +++ b/clients/js/src/generated/instructions/approveGroupPluginAuthorityV1.ts @@ -0,0 +1,173 @@ +/** + * 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'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + PluginType, + PluginTypeArgs, + getBasePluginAuthoritySerializer, + getPluginTypeSerializer, +} from '../types'; + +// Accounts. +export type ApproveGroupPluginAuthorityV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type ApproveGroupPluginAuthorityV1InstructionData = { + discriminator: number; + pluginType: PluginType; + newAuthority: BasePluginAuthority; +}; + +export type ApproveGroupPluginAuthorityV1InstructionDataArgs = { + pluginType: PluginTypeArgs; + newAuthority: BasePluginAuthorityArgs; +}; + +export function getApproveGroupPluginAuthorityV1InstructionDataSerializer(): Serializer< + ApproveGroupPluginAuthorityV1InstructionDataArgs, + ApproveGroupPluginAuthorityV1InstructionData +> { + return mapSerializer< + ApproveGroupPluginAuthorityV1InstructionDataArgs, + any, + ApproveGroupPluginAuthorityV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['pluginType', getPluginTypeSerializer()], + ['newAuthority', getBasePluginAuthoritySerializer()], + ], + { description: 'ApproveGroupPluginAuthorityV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 42 }) + ) as Serializer< + ApproveGroupPluginAuthorityV1InstructionDataArgs, + ApproveGroupPluginAuthorityV1InstructionData + >; +} + +// Args. +export type ApproveGroupPluginAuthorityV1InstructionArgs = + ApproveGroupPluginAuthorityV1InstructionDataArgs; + +// Instruction. +export function approveGroupPluginAuthorityV1( + context: Pick, + input: ApproveGroupPluginAuthorityV1InstructionAccounts & + ApproveGroupPluginAuthorityV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: ApproveGroupPluginAuthorityV1InstructionArgs = { + ...input, + }; + + // 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 = + getApproveGroupPluginAuthorityV1InstructionDataSerializer().serialize( + resolvedArgs as ApproveGroupPluginAuthorityV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/closeGroupV1.ts b/clients/js/src/generated/instructions/closeGroupV1.ts new file mode 100644 index 00000000..febe5c0a --- /dev/null +++ b/clients/js/src/generated/instructions/closeGroupV1.ts @@ -0,0 +1,116 @@ +/** + * 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 CloseGroupV1InstructionAccounts = { + /** The address of the group to close */ + group: PublicKey | Pda; + /** The account receiving reclaimed lamports */ + payer?: Signer; + /** The update authority or update delegate of the group */ + authority?: Signer; +}; + +// Data. +export type CloseGroupV1InstructionData = { discriminator: number }; + +export type CloseGroupV1InstructionDataArgs = {}; + +export function getCloseGroupV1InstructionDataSerializer(): Serializer< + CloseGroupV1InstructionDataArgs, + CloseGroupV1InstructionData +> { + return mapSerializer< + CloseGroupV1InstructionDataArgs, + any, + CloseGroupV1InstructionData + >( + struct([['discriminator', u8()]], { + description: 'CloseGroupV1InstructionData', + }), + (value) => ({ ...value, discriminator: 48 }) + ) as Serializer; +} + +// Instruction. +export function closeGroupV1( + context: Pick, + input: CloseGroupV1InstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + + // 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 = getCloseGroupV1InstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/createGroupV1.ts b/clients/js/src/generated/instructions/createGroupV1.ts new file mode 100644 index 00000000..f2a81b41 --- /dev/null +++ b/clients/js/src/generated/instructions/createGroupV1.ts @@ -0,0 +1,163 @@ +/** + * 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, + array, + mapSerializer, + string, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + RelationshipEntry, + RelationshipEntryArgs, + getRelationshipEntrySerializer, +} from '../types'; + +// Accounts. +export type CreateGroupV1InstructionAccounts = { + /** The address of the new group */ + group: Signer; + /** The authority of the new group */ + updateAuthority?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type CreateGroupV1InstructionData = { + discriminator: number; + name: string; + uri: string; + relationships: Array; +}; + +export type CreateGroupV1InstructionDataArgs = { + name: string; + uri: string; + relationships: Array; +}; + +export function getCreateGroupV1InstructionDataSerializer(): Serializer< + CreateGroupV1InstructionDataArgs, + CreateGroupV1InstructionData +> { + return mapSerializer< + CreateGroupV1InstructionDataArgs, + any, + CreateGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['name', string()], + ['uri', string()], + ['relationships', array(getRelationshipEntrySerializer())], + ], + { description: 'CreateGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 47 }) + ) as Serializer< + CreateGroupV1InstructionDataArgs, + CreateGroupV1InstructionData + >; +} + +// Args. +export type CreateGroupV1InstructionArgs = CreateGroupV1InstructionDataArgs; + +// Instruction. +export function createGroupV1( + context: Pick, + input: CreateGroupV1InstructionAccounts & CreateGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + updateAuthority: { + index: 1, + isWritable: false as boolean, + value: input.updateAuthority ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: CreateGroupV1InstructionArgs = { ...input }; + + // 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 = getCreateGroupV1InstructionDataSerializer().serialize( + resolvedArgs as CreateGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 09be2d90..221d64ee 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -6,27 +6,41 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './addAssetsToGroupV1'; export * from './addCollectionExternalPluginAdapterV1'; export * from './addCollectionPluginV1'; +export * from './addCollectionsToGroupV1'; export * from './addExternalPluginAdapterV1'; +export * from './addGroupExternalPluginAdapterV1'; +export * from './addGroupPluginV1'; +export * from './addGroupsToGroupV1'; export * from './addPluginV1'; export * from './approveCollectionPluginAuthorityV1'; +export * from './approveGroupPluginAuthorityV1'; export * from './approvePluginAuthorityV1'; export * from './burnCollectionV1'; export * from './burnV1'; +export * from './closeGroupV1'; export * from './collect'; export * from './compressV1'; export * from './createCollectionV1'; export * from './createCollectionV2'; +export * from './createGroupV1'; export * from './createV1'; export * from './createV2'; export * from './decompressV1'; export * from './executeV1'; +export * from './removeAssetsFromGroupV1'; export * from './removeCollectionExternalPluginAdapterV1'; export * from './removeCollectionPluginV1'; +export * from './removeCollectionsFromGroupV1'; export * from './removeExternalPluginAdapterV1'; +export * from './removeGroupExternalPluginAdapterV1'; +export * from './removeGroupPluginV1'; +export * from './removeGroupsFromGroupV1'; export * from './removePluginV1'; export * from './revokeCollectionPluginAuthorityV1'; +export * from './revokeGroupPluginAuthorityV1'; export * from './revokePluginAuthorityV1'; export * from './transferV1'; export * from './updateCollectionExternalPluginAdapterV1'; @@ -34,8 +48,11 @@ export * from './updateCollectionInfoV1'; export * from './updateCollectionPluginV1'; export * from './updateCollectionV1'; export * from './updateExternalPluginAdapterV1'; +export * from './updateGroupPluginV1'; +export * from './updateGroupV1'; export * from './updatePluginV1'; export * from './updateV1'; export * from './updateV2'; export * from './writeCollectionExternalPluginAdapterDataV1'; export * from './writeExternalPluginAdapterDataV1'; +export * from './writeGroupExternalPluginAdapterDataV1'; diff --git a/clients/js/src/generated/instructions/removeAssetsFromGroupV1.ts b/clients/js/src/generated/instructions/removeAssetsFromGroupV1.ts new file mode 100644 index 00000000..c07b8eff --- /dev/null +++ b/clients/js/src/generated/instructions/removeAssetsFromGroupV1.ts @@ -0,0 +1,154 @@ +/** + * 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, + array, + mapSerializer, + publicKey as publicKeySerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type RemoveAssetsFromGroupV1InstructionAccounts = { + /** The address of the group to modify */ + group: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the group/assets */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type RemoveAssetsFromGroupV1InstructionData = { + discriminator: number; + assets: Array; +}; + +export type RemoveAssetsFromGroupV1InstructionDataArgs = { + assets: Array; +}; + +export function getRemoveAssetsFromGroupV1InstructionDataSerializer(): Serializer< + RemoveAssetsFromGroupV1InstructionDataArgs, + RemoveAssetsFromGroupV1InstructionData +> { + return mapSerializer< + RemoveAssetsFromGroupV1InstructionDataArgs, + any, + RemoveAssetsFromGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['assets', array(publicKeySerializer())], + ], + { description: 'RemoveAssetsFromGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 36 }) + ) as Serializer< + RemoveAssetsFromGroupV1InstructionDataArgs, + RemoveAssetsFromGroupV1InstructionData + >; +} + +// Args. +export type RemoveAssetsFromGroupV1InstructionArgs = + RemoveAssetsFromGroupV1InstructionDataArgs; + +// Instruction. +export function removeAssetsFromGroupV1( + context: Pick, + input: RemoveAssetsFromGroupV1InstructionAccounts & + RemoveAssetsFromGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveAssetsFromGroupV1InstructionArgs = { ...input }; + + // 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 = getRemoveAssetsFromGroupV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveAssetsFromGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/removeCollectionsFromGroupV1.ts b/clients/js/src/generated/instructions/removeCollectionsFromGroupV1.ts new file mode 100644 index 00000000..0149e83f --- /dev/null +++ b/clients/js/src/generated/instructions/removeCollectionsFromGroupV1.ts @@ -0,0 +1,157 @@ +/** + * 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, + array, + mapSerializer, + publicKey as publicKeySerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type RemoveCollectionsFromGroupV1InstructionAccounts = { + /** The address of the group to modify */ + group: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the group/collections */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type RemoveCollectionsFromGroupV1InstructionData = { + discriminator: number; + collections: Array; +}; + +export type RemoveCollectionsFromGroupV1InstructionDataArgs = { + collections: Array; +}; + +export function getRemoveCollectionsFromGroupV1InstructionDataSerializer(): Serializer< + RemoveCollectionsFromGroupV1InstructionDataArgs, + RemoveCollectionsFromGroupV1InstructionData +> { + return mapSerializer< + RemoveCollectionsFromGroupV1InstructionDataArgs, + any, + RemoveCollectionsFromGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['collections', array(publicKeySerializer())], + ], + { description: 'RemoveCollectionsFromGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 34 }) + ) as Serializer< + RemoveCollectionsFromGroupV1InstructionDataArgs, + RemoveCollectionsFromGroupV1InstructionData + >; +} + +// Args. +export type RemoveCollectionsFromGroupV1InstructionArgs = + RemoveCollectionsFromGroupV1InstructionDataArgs; + +// Instruction. +export function removeCollectionsFromGroupV1( + context: Pick, + input: RemoveCollectionsFromGroupV1InstructionAccounts & + RemoveCollectionsFromGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveCollectionsFromGroupV1InstructionArgs = { + ...input, + }; + + // 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 = + getRemoveCollectionsFromGroupV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveCollectionsFromGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/removeGroupExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/removeGroupExternalPluginAdapterV1.ts new file mode 100644 index 00000000..e58fdb2f --- /dev/null +++ b/clients/js/src/generated/instructions/removeGroupExternalPluginAdapterV1.ts @@ -0,0 +1,167 @@ +/** + * 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'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type RemoveGroupExternalPluginAdapterV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type RemoveGroupExternalPluginAdapterV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; +}; + +export type RemoveGroupExternalPluginAdapterV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; +}; + +export function getRemoveGroupExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + RemoveGroupExternalPluginAdapterV1InstructionDataArgs, + RemoveGroupExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + RemoveGroupExternalPluginAdapterV1InstructionDataArgs, + any, + RemoveGroupExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ], + { description: 'RemoveGroupExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 45 }) + ) as Serializer< + RemoveGroupExternalPluginAdapterV1InstructionDataArgs, + RemoveGroupExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type RemoveGroupExternalPluginAdapterV1InstructionArgs = + RemoveGroupExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function removeGroupExternalPluginAdapterV1( + context: Pick, + input: RemoveGroupExternalPluginAdapterV1InstructionAccounts & + RemoveGroupExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveGroupExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // 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 = + getRemoveGroupExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveGroupExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/removeGroupPluginV1.ts b/clients/js/src/generated/instructions/removeGroupPluginV1.ts new file mode 100644 index 00000000..451e86d9 --- /dev/null +++ b/clients/js/src/generated/instructions/removeGroupPluginV1.ts @@ -0,0 +1,160 @@ +/** + * 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'; +import { PluginType, PluginTypeArgs, getPluginTypeSerializer } from '../types'; + +// Accounts. +export type RemoveGroupPluginV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type RemoveGroupPluginV1InstructionData = { + discriminator: number; + pluginType: PluginType; +}; + +export type RemoveGroupPluginV1InstructionDataArgs = { + pluginType: PluginTypeArgs; +}; + +export function getRemoveGroupPluginV1InstructionDataSerializer(): Serializer< + RemoveGroupPluginV1InstructionDataArgs, + RemoveGroupPluginV1InstructionData +> { + return mapSerializer< + RemoveGroupPluginV1InstructionDataArgs, + any, + RemoveGroupPluginV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['pluginType', getPluginTypeSerializer()], + ], + { description: 'RemoveGroupPluginV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 40 }) + ) as Serializer< + RemoveGroupPluginV1InstructionDataArgs, + RemoveGroupPluginV1InstructionData + >; +} + +// Args. +export type RemoveGroupPluginV1InstructionArgs = + RemoveGroupPluginV1InstructionDataArgs; + +// Instruction. +export function removeGroupPluginV1( + context: Pick, + input: RemoveGroupPluginV1InstructionAccounts & + RemoveGroupPluginV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveGroupPluginV1InstructionArgs = { ...input }; + + // 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 = getRemoveGroupPluginV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveGroupPluginV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/removeGroupsFromGroupV1.ts b/clients/js/src/generated/instructions/removeGroupsFromGroupV1.ts new file mode 100644 index 00000000..dd0db85d --- /dev/null +++ b/clients/js/src/generated/instructions/removeGroupsFromGroupV1.ts @@ -0,0 +1,154 @@ +/** + * 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, + array, + mapSerializer, + publicKey as publicKeySerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type RemoveGroupsFromGroupV1InstructionAccounts = { + /** The address of the parent group to modify */ + parentGroup: PublicKey | Pda; + /** The account paying for storage fees */ + payer?: Signer; + /** The update authority or delegate of the groups */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type RemoveGroupsFromGroupV1InstructionData = { + discriminator: number; + groups: Array; +}; + +export type RemoveGroupsFromGroupV1InstructionDataArgs = { + groups: Array; +}; + +export function getRemoveGroupsFromGroupV1InstructionDataSerializer(): Serializer< + RemoveGroupsFromGroupV1InstructionDataArgs, + RemoveGroupsFromGroupV1InstructionData +> { + return mapSerializer< + RemoveGroupsFromGroupV1InstructionDataArgs, + any, + RemoveGroupsFromGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['groups', array(publicKeySerializer())], + ], + { description: 'RemoveGroupsFromGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 38 }) + ) as Serializer< + RemoveGroupsFromGroupV1InstructionDataArgs, + RemoveGroupsFromGroupV1InstructionData + >; +} + +// Args. +export type RemoveGroupsFromGroupV1InstructionArgs = + RemoveGroupsFromGroupV1InstructionDataArgs; + +// Instruction. +export function removeGroupsFromGroupV1( + context: Pick, + input: RemoveGroupsFromGroupV1InstructionAccounts & + RemoveGroupsFromGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + parentGroup: { + index: 0, + isWritable: true as boolean, + value: input.parentGroup ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveGroupsFromGroupV1InstructionArgs = { ...input }; + + // 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 = getRemoveGroupsFromGroupV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveGroupsFromGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/revokeGroupPluginAuthorityV1.ts b/clients/js/src/generated/instructions/revokeGroupPluginAuthorityV1.ts new file mode 100644 index 00000000..286ba66e --- /dev/null +++ b/clients/js/src/generated/instructions/revokeGroupPluginAuthorityV1.ts @@ -0,0 +1,163 @@ +/** + * 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'; +import { PluginType, PluginTypeArgs, getPluginTypeSerializer } from '../types'; + +// Accounts. +export type RevokeGroupPluginAuthorityV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type RevokeGroupPluginAuthorityV1InstructionData = { + discriminator: number; + pluginType: PluginType; +}; + +export type RevokeGroupPluginAuthorityV1InstructionDataArgs = { + pluginType: PluginTypeArgs; +}; + +export function getRevokeGroupPluginAuthorityV1InstructionDataSerializer(): Serializer< + RevokeGroupPluginAuthorityV1InstructionDataArgs, + RevokeGroupPluginAuthorityV1InstructionData +> { + return mapSerializer< + RevokeGroupPluginAuthorityV1InstructionDataArgs, + any, + RevokeGroupPluginAuthorityV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['pluginType', getPluginTypeSerializer()], + ], + { description: 'RevokeGroupPluginAuthorityV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 43 }) + ) as Serializer< + RevokeGroupPluginAuthorityV1InstructionDataArgs, + RevokeGroupPluginAuthorityV1InstructionData + >; +} + +// Args. +export type RevokeGroupPluginAuthorityV1InstructionArgs = + RevokeGroupPluginAuthorityV1InstructionDataArgs; + +// Instruction. +export function revokeGroupPluginAuthorityV1( + context: Pick, + input: RevokeGroupPluginAuthorityV1InstructionAccounts & + RevokeGroupPluginAuthorityV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RevokeGroupPluginAuthorityV1InstructionArgs = { + ...input, + }; + + // 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 = + getRevokeGroupPluginAuthorityV1InstructionDataSerializer().serialize( + resolvedArgs as RevokeGroupPluginAuthorityV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/updateGroupPluginV1.ts b/clients/js/src/generated/instructions/updateGroupPluginV1.ts new file mode 100644 index 00000000..922579e9 --- /dev/null +++ b/clients/js/src/generated/instructions/updateGroupPluginV1.ts @@ -0,0 +1,158 @@ +/** + * 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'; +import { Plugin, PluginArgs, getPluginSerializer } from '../types'; + +// Accounts. +export type UpdateGroupPluginV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or delegate of the group */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type UpdateGroupPluginV1InstructionData = { + discriminator: number; + plugin: Plugin; +}; + +export type UpdateGroupPluginV1InstructionDataArgs = { plugin: PluginArgs }; + +export function getUpdateGroupPluginV1InstructionDataSerializer(): Serializer< + UpdateGroupPluginV1InstructionDataArgs, + UpdateGroupPluginV1InstructionData +> { + return mapSerializer< + UpdateGroupPluginV1InstructionDataArgs, + any, + UpdateGroupPluginV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['plugin', getPluginSerializer()], + ], + { description: 'UpdateGroupPluginV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 41 }) + ) as Serializer< + UpdateGroupPluginV1InstructionDataArgs, + UpdateGroupPluginV1InstructionData + >; +} + +// Args. +export type UpdateGroupPluginV1InstructionArgs = + UpdateGroupPluginV1InstructionDataArgs; + +// Instruction. +export function updateGroupPluginV1( + context: Pick, + input: UpdateGroupPluginV1InstructionAccounts & + UpdateGroupPluginV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: UpdateGroupPluginV1InstructionArgs = { ...input }; + + // 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 = getUpdateGroupPluginV1InstructionDataSerializer().serialize( + resolvedArgs as UpdateGroupPluginV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/updateGroupV1.ts b/clients/js/src/generated/instructions/updateGroupV1.ts new file mode 100644 index 00000000..c08b1f59 --- /dev/null +++ b/clients/js/src/generated/instructions/updateGroupV1.ts @@ -0,0 +1,164 @@ +/** + * 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, + Option, + OptionOrNullable, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + option, + string, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type UpdateGroupV1InstructionAccounts = { + /** The address of the group to update */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The update authority or update delegate of the group */ + authority?: Signer; + /** The new update authority of the group */ + newUpdateAuthority?: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type UpdateGroupV1InstructionData = { + discriminator: number; + newName: Option; + newUri: Option; +}; + +export type UpdateGroupV1InstructionDataArgs = { + newName: OptionOrNullable; + newUri: OptionOrNullable; +}; + +export function getUpdateGroupV1InstructionDataSerializer(): Serializer< + UpdateGroupV1InstructionDataArgs, + UpdateGroupV1InstructionData +> { + return mapSerializer< + UpdateGroupV1InstructionDataArgs, + any, + UpdateGroupV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['newName', option(string())], + ['newUri', option(string())], + ], + { description: 'UpdateGroupV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 49 }) + ) as Serializer< + UpdateGroupV1InstructionDataArgs, + UpdateGroupV1InstructionData + >; +} + +// Args. +export type UpdateGroupV1InstructionArgs = UpdateGroupV1InstructionDataArgs; + +// Instruction. +export function updateGroupV1( + context: Pick, + input: UpdateGroupV1InstructionAccounts & UpdateGroupV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + newUpdateAuthority: { + index: 3, + isWritable: false as boolean, + value: input.newUpdateAuthority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: UpdateGroupV1InstructionArgs = { ...input }; + + // 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 = getUpdateGroupV1InstructionDataSerializer().serialize( + resolvedArgs as UpdateGroupV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/writeGroupExternalPluginAdapterDataV1.ts b/clients/js/src/generated/instructions/writeGroupExternalPluginAdapterDataV1.ts new file mode 100644 index 00000000..7b35e25e --- /dev/null +++ b/clients/js/src/generated/instructions/writeGroupExternalPluginAdapterDataV1.ts @@ -0,0 +1,182 @@ +/** + * 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, + Option, + OptionOrNullable, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + bytes, + mapSerializer, + option, + struct, + u32, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type WriteGroupExternalPluginAdapterDataV1InstructionAccounts = { + /** The address of the group */ + group: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The data authority or update authority of the group */ + authority?: Signer; + /** Buffer account containing data */ + buffer?: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type WriteGroupExternalPluginAdapterDataV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; + data: Option; +}; + +export type WriteGroupExternalPluginAdapterDataV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; + data: OptionOrNullable; +}; + +export function getWriteGroupExternalPluginAdapterDataV1InstructionDataSerializer(): Serializer< + WriteGroupExternalPluginAdapterDataV1InstructionDataArgs, + WriteGroupExternalPluginAdapterDataV1InstructionData +> { + return mapSerializer< + WriteGroupExternalPluginAdapterDataV1InstructionDataArgs, + any, + WriteGroupExternalPluginAdapterDataV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ['data', option(bytes({ size: u32() }))], + ], + { description: 'WriteGroupExternalPluginAdapterDataV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 46 }) + ) as Serializer< + WriteGroupExternalPluginAdapterDataV1InstructionDataArgs, + WriteGroupExternalPluginAdapterDataV1InstructionData + >; +} + +// Args. +export type WriteGroupExternalPluginAdapterDataV1InstructionArgs = + WriteGroupExternalPluginAdapterDataV1InstructionDataArgs; + +// Instruction. +export function writeGroupExternalPluginAdapterDataV1( + context: Pick, + input: WriteGroupExternalPluginAdapterDataV1InstructionAccounts & + WriteGroupExternalPluginAdapterDataV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + group: { + index: 0, + isWritable: true as boolean, + value: input.group ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + buffer: { + index: 3, + isWritable: false as boolean, + value: input.buffer ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: WriteGroupExternalPluginAdapterDataV1InstructionArgs = { + ...input, + }; + + // 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 = + getWriteGroupExternalPluginAdapterDataV1InstructionDataSerializer().serialize( + resolvedArgs as WriteGroupExternalPluginAdapterDataV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/types/groups.ts b/clients/js/src/generated/types/groups.ts new file mode 100644 index 00000000..ccd4ad06 --- /dev/null +++ b/clients/js/src/generated/types/groups.ts @@ -0,0 +1,25 @@ +/** + * 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 { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; + +export type Groups = { groups: Array }; + +export type GroupsArgs = Groups; + +export function getGroupsSerializer(): Serializer { + return struct([['groups', array(publicKeySerializer())]], { + description: 'Groups', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 719d2813..ff1f726a 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -55,6 +55,7 @@ export * from './externalRegistryRecord'; export * from './externalValidationResult'; export * from './freezeDelegate'; export * from './freezeExecute'; +export * from './groups'; export * from './hashablePluginSchema'; export * from './hashedAssetSchema'; export * from './hookableLifecycleEvent'; @@ -69,6 +70,8 @@ export * from './plugin'; export * from './pluginAuthorityPair'; export * from './pluginType'; export * from './registryRecord'; +export * from './relationshipEntry'; +export * from './relationshipKind'; export * from './transferDelegate'; export * from './updateDelegate'; export * from './updateType'; diff --git a/clients/js/src/generated/types/key.ts b/clients/js/src/generated/types/key.ts index 8bebbc41..85098f9c 100644 --- a/clients/js/src/generated/types/key.ts +++ b/clients/js/src/generated/types/key.ts @@ -15,6 +15,7 @@ export enum Key { PluginHeaderV1, PluginRegistryV1, CollectionV1, + GroupV1, } export type KeyArgs = Key; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 7fb825c9..e6bed57e 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -35,6 +35,8 @@ import { FreezeDelegateArgs, FreezeExecute, FreezeExecuteArgs, + Groups, + GroupsArgs, ImmutableMetadata, ImmutableMetadataArgs, PermanentBurnDelegate, @@ -61,6 +63,7 @@ import { getEditionSerializer, getFreezeDelegateSerializer, getFreezeExecuteSerializer, + getGroupsSerializer, getImmutableMetadataSerializer, getPermanentBurnDelegateSerializer, getPermanentFreezeDelegateSerializer, @@ -89,7 +92,8 @@ export type Plugin = | { __kind: 'Autograph'; fields: [Autograph] } | { __kind: 'BubblegumV2'; fields: [BubblegumV2] } | { __kind: 'FreezeExecute'; fields: [FreezeExecute] } - | { __kind: 'PermanentFreezeExecute'; fields: [PermanentFreezeExecute] }; + | { __kind: 'PermanentFreezeExecute'; fields: [PermanentFreezeExecute] } + | { __kind: 'Groups'; fields: [Groups] }; export type PluginArgs = | { __kind: 'Royalties'; fields: [BaseRoyaltiesArgs] } @@ -112,7 +116,8 @@ export type PluginArgs = | { __kind: 'Autograph'; fields: [AutographArgs] } | { __kind: 'BubblegumV2'; fields: [BubblegumV2Args] } | { __kind: 'FreezeExecute'; fields: [FreezeExecuteArgs] } - | { __kind: 'PermanentFreezeExecute'; fields: [PermanentFreezeExecuteArgs] }; + | { __kind: 'PermanentFreezeExecute'; fields: [PermanentFreezeExecuteArgs] } + | { __kind: 'Groups'; fields: [GroupsArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -225,6 +230,12 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getPermanentFreezeExecuteSerializer()])], ]), ], + [ + 'Groups', + struct>([ + ['fields', tuple([getGroupsSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -306,6 +317,10 @@ export function plugin( kind: 'PermanentFreezeExecute', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'Groups', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; export function plugin( kind: K, data?: any diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts index ad784d87..448157eb 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -27,6 +27,7 @@ export enum PluginType { BubblegumV2, FreezeExecute, PermanentFreezeExecute, + Groups, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/generated/types/relationshipEntry.ts b/clients/js/src/generated/types/relationshipEntry.ts new file mode 100644 index 00000000..5dac465e --- /dev/null +++ b/clients/js/src/generated/types/relationshipEntry.ts @@ -0,0 +1,39 @@ +/** + * 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 { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + RelationshipKind, + RelationshipKindArgs, + getRelationshipKindSerializer, +} from '.'; + +export type RelationshipEntry = { kind: RelationshipKind; key: PublicKey }; + +export type RelationshipEntryArgs = { + kind: RelationshipKindArgs; + key: PublicKey; +}; + +export function getRelationshipEntrySerializer(): Serializer< + RelationshipEntryArgs, + RelationshipEntry +> { + return struct( + [ + ['kind', getRelationshipKindSerializer()], + ['key', publicKeySerializer()], + ], + { description: 'RelationshipEntry' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/relationshipKind.ts b/clients/js/src/generated/types/relationshipKind.ts new file mode 100644 index 00000000..1795b2a9 --- /dev/null +++ b/clients/js/src/generated/types/relationshipKind.ts @@ -0,0 +1,27 @@ +/** + * 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 { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum RelationshipKind { + Collection, + ChildGroup, + ParentGroup, + Asset, +} + +export type RelationshipKindArgs = RelationshipKind; + +export function getRelationshipKindSerializer(): Serializer< + RelationshipKindArgs, + RelationshipKind +> { + return scalarEnum(RelationshipKind, { + description: 'RelationshipKind', + }) as Serializer; +} diff --git a/clients/js/src/hooked/groupAccountData.ts b/clients/js/src/hooked/groupAccountData.ts new file mode 100644 index 00000000..b841c22b --- /dev/null +++ b/clients/js/src/hooked/groupAccountData.ts @@ -0,0 +1,99 @@ +import { Serializer } from '@metaplex-foundation/umi/serializers'; + +import { + Key, + PluginHeaderV1, + PluginHeaderV1AccountData, + getPluginHeaderV1AccountDataSerializer, +} from '../generated'; + +import { + GroupV1AccountData as GenGroupV1AccountData, + GroupV1AccountDataArgs as GenGroupV1AccountDataArgs, + getGroupV1AccountDataSerializer as genGetGroupV1AccountDataSerializer, +} from '../generated/accounts/groupV1'; + +import { + ExternalPluginAdaptersList, + GroupPluginsList, + registryRecordsToPluginsList, +} from '../plugins'; +import { externalRegistryRecordsToExternalPluginAdapterList } from '../plugins/externalPluginAdapters'; +import { + PluginRegistryV1AccountData, + getPluginRegistryV1AccountDataSerializer, +} from './pluginRegistryV1Data'; + +export type GroupV1AccountData = GenGroupV1AccountData & + GroupPluginsList & + ExternalPluginAdaptersList & { + pluginHeader?: Omit; + }; + +export type GroupV1AccountDataArgs = GenGroupV1AccountDataArgs & { + pluginHeader?: Omit; +}; + +export const getGroupV1AccountDataSerializer = (): Serializer< + GroupV1AccountDataArgs, + GroupV1AccountData +> => ({ + description: 'GroupAccountData', + fixedSize: null, + maxSize: null, + serialize: () => { + throw new Error('Operation not supported.'); + }, + deserialize: ( + buffer: Uint8Array, + offset = 0 + ): [GroupV1AccountData, number] => { + // Deserialize base group data + const [group, groupOffset] = + genGetGroupV1AccountDataSerializer().deserialize(buffer, offset); + + if (group.key !== Key.GroupV1) { + throw new Error(`Expected a Group account, got key: ${group.key}`); + } + + let pluginHeader: PluginHeaderV1AccountData | undefined; + let pluginRegistry: PluginRegistryV1AccountData | undefined; + let pluginsList: GroupPluginsList | undefined; + let externalPluginAdaptersList: ExternalPluginAdaptersList | undefined; + let finalOffset = groupOffset; + + if (buffer.length !== groupOffset) { + [pluginHeader] = getPluginHeaderV1AccountDataSerializer().deserialize( + buffer, + groupOffset + ); + + [pluginRegistry, finalOffset] = + getPluginRegistryV1AccountDataSerializer().deserialize( + buffer, + Number(pluginHeader.pluginRegistryOffset) + ); + + pluginsList = registryRecordsToPluginsList( + pluginRegistry.registry, + buffer + ); + + externalPluginAdaptersList = + externalRegistryRecordsToExternalPluginAdapterList( + pluginRegistry.externalRegistry, + buffer + ); + } + + return [ + { + pluginHeader, + ...pluginsList, + ...externalPluginAdaptersList, + ...group, + }, + finalOffset, + ]; + }, +}); diff --git a/clients/js/src/hooked/index.ts b/clients/js/src/hooked/index.ts index cb0aeb61..af77509d 100644 --- a/clients/js/src/hooked/index.ts +++ b/clients/js/src/hooked/index.ts @@ -1,3 +1,4 @@ export * from './assetAccountData'; export * from './collectionAccountData'; +export * from './groupAccountData'; export * from './pluginRegistryV1Data'; diff --git a/clients/js/src/instructions/collection/index.ts b/clients/js/src/instructions/collection/index.ts index fbca7eba..608b9d8a 100644 --- a/clients/js/src/instructions/collection/index.ts +++ b/clients/js/src/instructions/collection/index.ts @@ -6,3 +6,4 @@ export * from './removeCollectionPlugin'; export * from './revokeCollectionPluginAuthority'; export * from './updateCollection'; export * from './updateCollectionPlugin'; +export * from './writeCollectionData'; diff --git a/clients/js/src/instructions/collection/writeCollectionData.ts b/clients/js/src/instructions/collection/writeCollectionData.ts new file mode 100644 index 00000000..2a5a1184 --- /dev/null +++ b/clients/js/src/instructions/collection/writeCollectionData.ts @@ -0,0 +1,29 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + writeCollectionExternalPluginAdapterDataV1, + WriteCollectionExternalPluginAdapterDataV1InstructionAccounts, + WriteCollectionExternalPluginAdapterDataV1InstructionArgs, +} from '../../generated'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../../plugins'; + +export type WriteCollectionDataArgs = Omit< + WriteCollectionExternalPluginAdapterDataV1InstructionArgs, + 'key' +> & { + key: ExternalPluginAdapterKey; +}; + +export const writeCollectionData = ( + context: Pick, + args: WriteCollectionDataArgs & + WriteCollectionExternalPluginAdapterDataV1InstructionAccounts +) => { + const { key, ...rest } = args; + return writeCollectionExternalPluginAdapterDataV1(context, { + ...rest, + key: externalPluginAdapterKeyToBase(key), + }); +}; diff --git a/clients/js/src/instructions/group/addAssetsToGroup.ts b/clients/js/src/instructions/group/addAssetsToGroup.ts new file mode 100644 index 00000000..6c65c426 --- /dev/null +++ b/clients/js/src/instructions/group/addAssetsToGroup.ts @@ -0,0 +1,9 @@ +import { Context } from '@metaplex-foundation/umi'; +import { addAssetsToGroupV1 } from '../../generated'; + +export type AddAssetsToGroupArgs = Parameters[1]; + +export const addAssetsToGroup = ( + context: Pick, + args: AddAssetsToGroupArgs +) => addAssetsToGroupV1(context, args); diff --git a/clients/js/src/instructions/group/addCollectionsToGroup.ts b/clients/js/src/instructions/group/addCollectionsToGroup.ts new file mode 100644 index 00000000..d7b7d639 --- /dev/null +++ b/clients/js/src/instructions/group/addCollectionsToGroup.ts @@ -0,0 +1,11 @@ +import { Context } from '@metaplex-foundation/umi'; +import { addCollectionsToGroupV1 } from '../../generated'; + +export type AddCollectionsToGroupArgs = Parameters< + typeof addCollectionsToGroupV1 +>[1]; + +export const addCollectionsToGroup = ( + context: Pick, + args: AddCollectionsToGroupArgs +) => addCollectionsToGroupV1(context, args); diff --git a/clients/js/src/instructions/group/addGroupPlugin.ts b/clients/js/src/instructions/group/addGroupPlugin.ts new file mode 100644 index 00000000..96ae4bbd --- /dev/null +++ b/clients/js/src/instructions/group/addGroupPlugin.ts @@ -0,0 +1,48 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + addGroupExternalPluginAdapterV1, + addGroupPluginV1, +} from '../../generated'; +import { + GroupAddablePluginAuthorityPairArgsV2, + groupPluginAuthorityPairV2, +} from '../../plugins'; +import { + createExternalPluginAdapterInitInfo, + ExternalPluginAdapterInitInfoArgs, + isExternalPluginAdapterType, +} from '../../plugins/externalPluginAdapters'; + +export type AddGroupPluginArgsPlugin = + | GroupAddablePluginAuthorityPairArgsV2 + | ExternalPluginAdapterInitInfoArgs; + +export type AddGroupPluginArgs = Omit< + Parameters[1], + 'plugin' | 'initAuthority' +> & { + plugin: AddGroupPluginArgsPlugin; +}; + +export const addGroupPlugin = ( + context: Pick, + { plugin, ...args }: AddGroupPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return addGroupExternalPluginAdapterV1(context, { + ...args, + initInfo: createExternalPluginAdapterInitInfo( + plugin as ExternalPluginAdapterInitInfoArgs + ), + }); + } + + const pair = groupPluginAuthorityPairV2( + plugin as GroupAddablePluginAuthorityPairArgsV2 + ); + return addGroupPluginV1(context, { + ...args, + plugin: pair.plugin, + initAuthority: pair.authority, + }); +}; diff --git a/clients/js/src/instructions/group/addGroupsToGroup.ts b/clients/js/src/instructions/group/addGroupsToGroup.ts new file mode 100644 index 00000000..5952dccc --- /dev/null +++ b/clients/js/src/instructions/group/addGroupsToGroup.ts @@ -0,0 +1,9 @@ +import { Context } from '@metaplex-foundation/umi'; +import { addGroupsToGroupV1 } from '../../generated'; + +export type AddGroupsToGroupArgs = Parameters[1]; + +export const addGroupsToGroup = ( + context: Pick, + args: AddGroupsToGroupArgs +) => addGroupsToGroupV1(context, args); diff --git a/clients/js/src/instructions/group/approveGroupPluginAuthority.ts b/clients/js/src/instructions/group/approveGroupPluginAuthority.ts new file mode 100644 index 00000000..528e91c5 --- /dev/null +++ b/clients/js/src/instructions/group/approveGroupPluginAuthority.ts @@ -0,0 +1,25 @@ +import { Context } from '@metaplex-foundation/umi'; +import { approveGroupPluginAuthorityV1, PluginType } from '../../generated'; +import { PluginAuthority, pluginAuthorityToBase } from '../../plugins'; + +export type ApproveGroupPluginAuthorityArgsPlugin = { + type: keyof typeof PluginType; +}; + +export type ApproveGroupPluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' | 'newAuthority' +> & { + plugin: ApproveGroupPluginAuthorityArgsPlugin; + newAuthority: PluginAuthority; +}; + +export const approveGroupPluginAuthority = ( + context: Pick, + { plugin, newAuthority, ...args }: ApproveGroupPluginAuthorityArgs +) => + approveGroupPluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + newAuthority: pluginAuthorityToBase(newAuthority), + }); diff --git a/clients/js/src/instructions/group/closeGroup.ts b/clients/js/src/instructions/group/closeGroup.ts new file mode 100644 index 00000000..8b226234 --- /dev/null +++ b/clients/js/src/instructions/group/closeGroup.ts @@ -0,0 +1,9 @@ +import { Context } from '@metaplex-foundation/umi'; +import { closeGroupV1 } from '../../generated'; + +export type CloseGroupArgs = Parameters[1]; + +export const closeGroup = ( + context: Pick, + args: CloseGroupArgs +) => closeGroupV1(context, args); diff --git a/clients/js/src/instructions/group/createGroup.ts b/clients/js/src/instructions/group/createGroup.ts new file mode 100644 index 00000000..2202d188 --- /dev/null +++ b/clients/js/src/instructions/group/createGroup.ts @@ -0,0 +1,9 @@ +import { Context } from '@metaplex-foundation/umi'; +import { createGroupV1 } from '../../generated'; + +export type CreateGroupArgs = Parameters[1]; + +export const createGroup = ( + context: Pick, + args: CreateGroupArgs +) => createGroupV1(context, args); diff --git a/clients/js/src/instructions/group/index.ts b/clients/js/src/instructions/group/index.ts new file mode 100644 index 00000000..faad3270 --- /dev/null +++ b/clients/js/src/instructions/group/index.ts @@ -0,0 +1,15 @@ +export * from './addAssetsToGroup'; +export * from './addCollectionsToGroup'; +export * from './addGroupPlugin'; +export * from './addGroupsToGroup'; +export * from './approveGroupPluginAuthority'; +export * from './closeGroup'; +export * from './createGroup'; +export * from './removeAssetsFromGroup'; +export * from './removeCollectionsFromGroup'; +export * from './removeGroupPlugin'; +export * from './removeGroupsFromGroup'; +export * from './revokeGroupPluginAuthority'; +export * from './updateGroup'; +export * from './updateGroupPlugin'; +export * from './writeGroupData'; diff --git a/clients/js/src/instructions/group/removeAssetsFromGroup.ts b/clients/js/src/instructions/group/removeAssetsFromGroup.ts new file mode 100644 index 00000000..6a88efdc --- /dev/null +++ b/clients/js/src/instructions/group/removeAssetsFromGroup.ts @@ -0,0 +1,11 @@ +import { Context } from '@metaplex-foundation/umi'; +import { removeAssetsFromGroupV1 } from '../../generated'; + +export type RemoveAssetsFromGroupArgs = Parameters< + typeof removeAssetsFromGroupV1 +>[1]; + +export const removeAssetsFromGroup = ( + context: Pick, + args: RemoveAssetsFromGroupArgs +) => removeAssetsFromGroupV1(context, args); diff --git a/clients/js/src/instructions/group/removeCollectionsFromGroup.ts b/clients/js/src/instructions/group/removeCollectionsFromGroup.ts new file mode 100644 index 00000000..26d9c5b4 --- /dev/null +++ b/clients/js/src/instructions/group/removeCollectionsFromGroup.ts @@ -0,0 +1,11 @@ +import { Context } from '@metaplex-foundation/umi'; +import { removeCollectionsFromGroupV1 } from '../../generated'; + +export type RemoveCollectionsFromGroupArgs = Parameters< + typeof removeCollectionsFromGroupV1 +>[1]; + +export const removeCollectionsFromGroup = ( + context: Pick, + args: RemoveCollectionsFromGroupArgs +) => removeCollectionsFromGroupV1(context, args); diff --git a/clients/js/src/instructions/group/removeGroupPlugin.ts b/clients/js/src/instructions/group/removeGroupPlugin.ts new file mode 100644 index 00000000..327ba8be --- /dev/null +++ b/clients/js/src/instructions/group/removeGroupPlugin.ts @@ -0,0 +1,41 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + PluginType, + removeGroupExternalPluginAdapterV1, + removeGroupPluginV1, +} from '../../generated'; +import { isExternalPluginAdapterType } from '../../plugins'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../../plugins/externalPluginAdapterKey'; + +export type RemoveGroupPluginArgsPlugin = + | { + type: Exclude; + } + | ExternalPluginAdapterKey; + +export type RemoveGroupPluginArgs = Omit< + Parameters[1], + 'pluginType' +> & { + plugin: RemoveGroupPluginArgsPlugin; +}; + +export const removeGroupPlugin = ( + context: Pick, + { plugin, ...args }: RemoveGroupPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return removeGroupExternalPluginAdapterV1(context, { + ...args, + key: externalPluginAdapterKeyToBase(plugin as ExternalPluginAdapterKey), + }); + } + + return removeGroupPluginV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); +}; diff --git a/clients/js/src/instructions/group/removeGroupsFromGroup.ts b/clients/js/src/instructions/group/removeGroupsFromGroup.ts new file mode 100644 index 00000000..a0362131 --- /dev/null +++ b/clients/js/src/instructions/group/removeGroupsFromGroup.ts @@ -0,0 +1,11 @@ +import { Context } from '@metaplex-foundation/umi'; +import { removeGroupsFromGroupV1 } from '../../generated'; + +export type RemoveGroupsFromGroupArgs = Parameters< + typeof removeGroupsFromGroupV1 +>[1]; + +export const removeGroupsFromGroup = ( + context: Pick, + args: RemoveGroupsFromGroupArgs +) => removeGroupsFromGroupV1(context, args); diff --git a/clients/js/src/instructions/group/revokeGroupPluginAuthority.ts b/clients/js/src/instructions/group/revokeGroupPluginAuthority.ts new file mode 100644 index 00000000..78b7438b --- /dev/null +++ b/clients/js/src/instructions/group/revokeGroupPluginAuthority.ts @@ -0,0 +1,22 @@ +import { Context } from '@metaplex-foundation/umi'; +import { PluginType, revokeGroupPluginAuthorityV1 } from '../../generated'; + +export type RevokeGroupPluginAuthorityArgsPlugin = { + type: keyof typeof PluginType; +}; + +export type RevokeGroupPluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' +> & { + plugin: RevokeGroupPluginAuthorityArgsPlugin; +}; + +export const revokeGroupPluginAuthority = ( + context: Pick, + { plugin, ...args }: RevokeGroupPluginAuthorityArgs +) => + revokeGroupPluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); diff --git a/clients/js/src/instructions/group/updateGroup.ts b/clients/js/src/instructions/group/updateGroup.ts new file mode 100644 index 00000000..aac7b635 --- /dev/null +++ b/clients/js/src/instructions/group/updateGroup.ts @@ -0,0 +1,9 @@ +import { Context } from '@metaplex-foundation/umi'; +import { updateGroupV1 } from '../../generated'; + +export type UpdateGroupArgs = Parameters[1]; + +export const updateGroup = ( + context: Pick, + args: UpdateGroupArgs +) => updateGroupV1(context, args); diff --git a/clients/js/src/instructions/group/updateGroupPlugin.ts b/clients/js/src/instructions/group/updateGroupPlugin.ts new file mode 100644 index 00000000..0cffa4a2 --- /dev/null +++ b/clients/js/src/instructions/group/updateGroupPlugin.ts @@ -0,0 +1,21 @@ +import { Context } from '@metaplex-foundation/umi'; +import { updateGroupPluginV1 } from '../../generated'; +import { createGroupPluginV2, GroupAllPluginArgsV2 } from '../../plugins'; + +export type UpdateGroupPluginArgsPlugin = GroupAllPluginArgsV2; + +export type UpdateGroupPluginArgs = Omit< + Parameters[1], + 'plugin' +> & { + plugin: UpdateGroupPluginArgsPlugin; +}; + +export const updateGroupPlugin = ( + context: Pick, + { plugin, ...args }: UpdateGroupPluginArgs +) => + updateGroupPluginV1(context, { + ...args, + plugin: createGroupPluginV2(plugin as GroupAllPluginArgsV2), + }); diff --git a/clients/js/src/instructions/group/writeGroupData.ts b/clients/js/src/instructions/group/writeGroupData.ts new file mode 100644 index 00000000..bb49d383 --- /dev/null +++ b/clients/js/src/instructions/group/writeGroupData.ts @@ -0,0 +1,29 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + writeGroupExternalPluginAdapterDataV1, + WriteGroupExternalPluginAdapterDataV1InstructionAccounts, + WriteGroupExternalPluginAdapterDataV1InstructionArgs, +} from '../../generated'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../../plugins'; + +export type WriteGroupDataArgs = Omit< + WriteGroupExternalPluginAdapterDataV1InstructionArgs, + 'key' +> & { + key: ExternalPluginAdapterKey; +}; + +export const writeGroupData = ( + context: Pick, + args: WriteGroupDataArgs & + WriteGroupExternalPluginAdapterDataV1InstructionAccounts +) => { + const { key, ...rest } = args; + return writeGroupExternalPluginAdapterDataV1(context, { + ...rest, + key: externalPluginAdapterKeyToBase(key), + }); +}; diff --git a/clients/js/src/instructions/index.ts b/clients/js/src/instructions/index.ts index 51c2e928..9d7a5896 100644 --- a/clients/js/src/instructions/index.ts +++ b/clients/js/src/instructions/index.ts @@ -1,15 +1,16 @@ +export * from './addPlugin'; +export * from './approvePluginAuthority'; +export * from './burn'; +export * from './collection'; +export * from './create'; +export * from './execute'; +export * from './freeze'; +export * from './group'; export * from './legacyDelegate'; export * from './legacyRevoke'; -export * from './freeze'; -export * from './create'; -export * from './update'; -export * from './transfer'; -export * from './burn'; -export * from './addPlugin'; export * from './removePlugin'; -export * from './updatePlugin'; -export * from './approvePluginAuthority'; export * from './revokePluginAuthority'; -export * from './collection'; +export * from './transfer'; +export * from './update'; +export * from './updatePlugin'; export * from './writeData'; -export * from './execute'; diff --git a/clients/js/src/plugins/lib.ts b/clients/js/src/plugins/lib.ts index a10102af..4276e0e6 100644 --- a/clients/js/src/plugins/lib.ts +++ b/clients/js/src/plugins/lib.ts @@ -24,6 +24,8 @@ import { AssetAllPluginArgsV2, AssetPluginAuthorityPairArgsV2, CreatePluginArgs, + GroupAddablePluginAuthorityPairArgsV2, + GroupAllPluginArgsV2, PluginAuthorityPairHelperArgs, PluginsList, } from './types'; @@ -129,6 +131,23 @@ export function pluginAuthorityPairV2({ }; } +export function createGroupPluginV2(args: GroupAllPluginArgsV2): BasePlugin { + // Group plugins are a strict subset of Asset plugins, so we can safely + // delegate to the generic Asset helper while preserving type safety. + return createPluginV2(args as unknown as AssetAllPluginArgsV2); +} + +export function groupPluginAuthorityPairV2({ + authority, + ...rest +}: GroupAddablePluginAuthorityPairArgsV2): PluginAuthorityPair { + // Delegate to the generic Asset helper – the types are compatible. + return pluginAuthorityPairV2({ + authority, + ...rest, + } as unknown as AssetPluginAuthorityPairArgsV2); +} + export function mapPluginFields(fields: Array>) { return fields.reduce((acc2, field) => ({ ...acc2, ...field }), {}); } diff --git a/clients/js/src/plugins/types.ts b/clients/js/src/plugins/types.ts index ec9941a3..051407e4 100644 --- a/clients/js/src/plugins/types.ts +++ b/clients/js/src/plugins/types.ts @@ -238,3 +238,27 @@ export type CollectionPluginsList = { } & CommonPluginsList; export type PluginsList = AssetPluginsList & CollectionPluginsList; + +export type GroupAddablePluginArgsV2 = + | ({ + type: 'Attributes'; + } & AttributesArgs) + | ({ + type: 'Autograph'; + } & AutographArgs) + | ({ + type: 'VerifiedCreators'; + } & VerifiedCreatorsArgs); + +export type GroupAllPluginArgsV2 = GroupAddablePluginArgsV2; +export type GroupPluginAuthorityPairArgsV2 = GroupAllPluginArgsV2 & + AuthorityArgsV2; + +export type GroupAddablePluginAuthorityPairArgsV2 = GroupAddablePluginArgsV2 & + AuthorityArgsV2; + +export type GroupPluginsList = { + attributes?: AttributesPlugin; + autograph?: AutographPlugin; + verifiedCreators?: VerifiedCreatorsPlugin; +}; diff --git a/clients/js/test/_setupRaw.ts b/clients/js/test/_setupRaw.ts index bc2f696e..f9faab4f 100644 --- a/clients/js/test/_setupRaw.ts +++ b/clients/js/test/_setupRaw.ts @@ -1,30 +1,34 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { createUmi as basecreateUmi } from '@metaplex-foundation/umi-bundle-tests'; -import { Assertions } from 'ava'; import { - PublicKey, - Signer, - Umi, assertAccountExists, generateSigner, + PublicKey, publicKey, + Signer, + Umi, } from '@metaplex-foundation/umi'; +import { createUmi as basecreateUmi } from '@metaplex-foundation/umi-bundle-tests'; +import { Assertions } from 'ava'; import { - DataState, - Key, + AssetPluginsList, + AssetV1, + createCollectionV1 as baseCreateCollection, + CollectionPluginsList, + CollectionV1, + createGroupV1, createV1, + DataState, + ExternalPluginAdaptersList, + fetchAsset, fetchAssetV1, fetchCollectionV1, + fetchGroupV1, + GroupPluginsList, + GroupV1, + Key, mplCore, - createCollectionV1 as baseCreateCollection, - CollectionV1, - AssetV1, PluginAuthorityPairArgs, UpdateAuthority, - ExternalPluginAdaptersList, - AssetPluginsList, - CollectionPluginsList, - fetchAsset, } from '../src'; export const createUmi = async () => (await basecreateUmi()).use(mplCore()); @@ -52,6 +56,11 @@ export const DEFAULT_COLLECTION = { uri: 'https://example.com/collection', }; +export const DEFAULT_GROUP = { + name: 'Test Group', + uri: 'https://example.com/group', +}; + export const createAsset = async ( umi: Umi, input: CreateAssetHelperArgs = {} @@ -107,6 +116,88 @@ export const createCollection = async ( return fetchCollectionV1(umi, publicKey(collection)); }; +export const createGroup = async ( + umi: Umi, + input: { + name?: string; + uri?: string; + payer?: Signer; + group?: Signer; + updateAuthority?: PublicKey | Signer; + } = {} +) => { + const payer = input.payer || umi.identity; + const group = input.group || generateSigner(umi); + + // Determine if the provided updateAuthority is a signer or just a public key. + const providedUpdateAuth = input.updateAuthority; + + let updateAuthoritySigner: Signer | undefined; + let updateAuthorityPubkey: PublicKey | undefined; + + if (providedUpdateAuth) { + // Heuristic: If the value has a `secretKey` property, we treat it as a Signer. + if ( + typeof providedUpdateAuth === 'object' && + 'secretKey' in providedUpdateAuth + ) { + updateAuthoritySigner = providedUpdateAuth as Signer; + updateAuthorityPubkey = publicKey(updateAuthoritySigner); + } else { + updateAuthorityPubkey = publicKey(providedUpdateAuth); + } + } + + // Step 1: create the group. If we have a signer for the update authority, pass it now. + const createGroupArgs: Parameters[1] = { + name: input.name || DEFAULT_GROUP.name, + uri: input.uri || DEFAULT_GROUP.uri, + group, + payer, + relationships: [], + }; + if (updateAuthoritySigner) { + // Account field type now allows Signer. + (createGroupArgs as any).updateAuthority = updateAuthoritySigner; + } + + await createGroupV1(umi, createGroupArgs).sendAndConfirm(umi); + + // Step 2: If the desired update authority was provided as a public key (non-signer), + // update the group to set that new update authority. + if (updateAuthorityPubkey && !updateAuthoritySigner) { + const { updateGroup } = await import('../src'); + await updateGroup(umi, { + group: group.publicKey, + payer, + authority: payer, + newUpdateAuthority: updateAuthorityPubkey, + newName: null, + newUri: null, + }).sendAndConfirm(umi); + } + + // If we have a signer for the update authority, delegate permissions to the payer + if (updateAuthoritySigner) { + const { plugin } = await import('../src/generated/types/plugin'); + const { addGroupPluginV1 } = await import('../src/generated'); + + await addGroupPluginV1(umi, { + group: group.publicKey, + payer, + authority: updateAuthoritySigner, + plugin: plugin('UpdateDelegate', [ + { + additionalDelegates: [publicKey(payer)], + }, + ] as any), + initAuthority: null, + }).sendAndConfirm(umi); + } + + return fetchGroupV1(umi, publicKey(group)); +}; + export const createAssetWithCollection: ( umi: Umi, assetInput: CreateAssetHelperArgs & { collection?: PublicKey | Signer }, @@ -212,6 +303,47 @@ export const assertCollection = async ( t.like(collectionWithPlugins, testObj); }; +export const assertGroup = async ( + t: Assertions, + umi: Umi, + input: { + group: PublicKey | Signer; + updateAuthority?: PublicKey | Signer; + name?: string | RegExp; + uri?: string | RegExp; + assets?: PublicKey[]; + collections?: PublicKey[]; + groups?: PublicKey[]; + parentGroups?: PublicKey[]; + } & GroupPluginsList & + ExternalPluginAdaptersList +) => { + const { group, name, uri, updateAuthority, ...rest } = input; + + const groupAddress = publicKey(group); + const groupWithPlugins = await fetchGroupV1(umi, groupAddress); + + // Name. + if (typeof name === 'string') t.is(groupWithPlugins.name, name); + else if (name !== undefined) t.regex(groupWithPlugins.name, name); + + // Uri. + if (typeof uri === 'string') t.is(groupWithPlugins.uri, uri); + else if (uri !== undefined) t.regex(groupWithPlugins.uri, uri); + + const testObj = { + key: Key.GroupV1, + publicKey: groupAddress, + ...rest, + }; + + if (updateAuthority) { + testObj.updateAuthority = publicKey(updateAuthority); + } + + t.like(groupWithPlugins, testObj); +}; + export const assertBurned = async ( t: Assertions, umi: Umi, diff --git a/clients/js/test/approveGroupPluginAuthority.test.ts b/clients/js/test/approveGroupPluginAuthority.test.ts new file mode 100644 index 00000000..f335db1b --- /dev/null +++ b/clients/js/test/approveGroupPluginAuthority.test.ts @@ -0,0 +1,101 @@ +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + addGroupPlugin, + approveGroupPluginAuthority, + revokeGroupPluginAuthority, + updateGroupPlugin, +} from '../src'; +import { + assertGroup, + createGroup, + createUmi, + DEFAULT_GROUP, +} from './_setupRaw'; + +// Same log-wrapper constant used in existing tests. +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Approve & Revoke Plugin Authority +// ----------------------------------------------------------------------------- + +test('it can approve and subsequently revoke a plugin authority on a group', async (t) => { + // --------------------------------------------------------------------------- + // Setup: create a group with an Attributes plugin whose authority is None. + // --------------------------------------------------------------------------- + const umi = await createUmi(); + const group = await createGroup(umi); + + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'init', value: 'value' }], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // --------------------------------------------------------------------------- + // Approve: delegate plugin authority to a new signer. + // --------------------------------------------------------------------------- + const newAuthority = generateSigner(umi); + + await approveGroupPluginAuthority(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + plugin: { type: 'Attributes' }, + newAuthority: { type: 'Address', address: newAuthority.publicKey }, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // The new authority should now be able to update the plugin. + await updateGroupPlugin(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'k1', value: 'v1' }], + }, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); + + // --------------------------------------------------------------------------- + // Revoke: remove the dedicated authority and ensure it can no longer act. + // --------------------------------------------------------------------------- + await revokeGroupPluginAuthority(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + plugin: { type: 'Attributes' }, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); + + // Attempting an update with the revoked authority should now fail. + await t.throwsAsync( + updateGroupPlugin(umi, { + group: group.publicKey, + payer: umi.identity, + authority: newAuthority, + logWrapper: LOG_WRAPPER, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'fail', value: 'fail' }], + }, + }).sendAndConfirm(umi) + ); +}); diff --git a/clients/js/test/closeGroup.test.ts b/clients/js/test/closeGroup.test.ts new file mode 100644 index 00000000..1dafe56b --- /dev/null +++ b/clients/js/test/closeGroup.test.ts @@ -0,0 +1,14 @@ +import test from 'ava'; +import { closeGroup } from '../src'; +import { assertBurned, createGroup, createUmi } from './_setupRaw'; + +test('it can close a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + await closeGroup(umi, { + group: group.publicKey, + }).sendAndConfirm(umi); + + await assertBurned(t, umi, group.publicKey); +}); diff --git a/clients/js/test/compute.test.ts b/clients/js/test/compute.test.ts new file mode 100644 index 00000000..83e28618 --- /dev/null +++ b/clients/js/test/compute.test.ts @@ -0,0 +1,67 @@ +import test from 'ava'; +import { addAssetsToGroup, removeAssetsFromGroup } from '../src'; +import { createAsset, createGroup, createUmi } from './_setupRaw'; + +const MAX_COMPUTE_UNITS = 1_400_000; // 1.4M Compute Units – Solana TX limit. + +/** + * Utility to fetch compute units consumed by a confirmed transaction signature. + */ +async function getComputeUnits( + umi: Awaited>, + signature: Uint8Array +) { + const txInfo = await umi.rpc.getTransaction(signature); + return Number(txInfo?.meta.computeUnitsConsumed ?? 0); +} + +// Ensure we stay within Solana's 1.4M CU per-TX hard limit. +// NOTE: This benchmark has been moved to the dedicated `/bench` folder to +// keep the deterministic unit‐test suite lightweight. See +// `clients/js/bench/compute.ts` for the performance check. + +test('compute units: removing 1 asset from a group stays below the 1.4M limit', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // Create and add 1 asset to ensure it is a member of the group first. + const assets = await Promise.all( + Array.from({ length: 1 }).map(() => createAsset(umi)) + ); + + // Initial add so that we can remove afterwards. + await addAssetsToGroup(umi, { + group: group.publicKey, + authority: umi.identity, + }) + .addRemainingAccounts( + assets.map((asset) => ({ + isSigner: false, + isWritable: true, + pubkey: asset.publicKey, + })) + ) + .sendAndConfirm(umi); + + // Now remove those same assets in a single instruction. + const removeTx = await removeAssetsFromGroup(umi, { + group: group.publicKey, + assets: assets.map((a) => a.publicKey), + authority: umi.identity, + }) + .addRemainingAccounts( + assets.map((asset) => ({ + isSigner: false, + isWritable: true, + pubkey: asset.publicKey, + })) + ) + .sendAndConfirm(umi); + + const computeUnits = await getComputeUnits(umi, removeTx.signature); + + t.true( + computeUnits <= MAX_COMPUTE_UNITS, + `Removing 1 asset used ${computeUnits} CUs which exceeds the 1.4M limit.` + ); +}); diff --git a/clients/js/test/createGroup.test.ts b/clients/js/test/createGroup.test.ts new file mode 100644 index 00000000..0e0fb4c0 --- /dev/null +++ b/clients/js/test/createGroup.test.ts @@ -0,0 +1,23 @@ +import test from 'ava'; +import { + assertGroup, + createGroup, + createUmi, + DEFAULT_GROUP, +} from './_setupRaw'; + +// Verify creation of a group + +test('it can create a new group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi, { + name: 'My Group', + }); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + name: 'My Group', + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); diff --git a/clients/js/test/group.test.ts b/clients/js/test/group.test.ts new file mode 100644 index 00000000..a627c7e9 --- /dev/null +++ b/clients/js/test/group.test.ts @@ -0,0 +1,32 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { getGroupV1GpaBuilder, Key } from '../src'; +import { createGroup, createUmi } from './_setupRaw'; + +test('it can gpa fetch groups by updateAuthority', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + + await createGroup(umi, { + name: 'group1', + updateAuthority: updateAuthority.publicKey, + }); + await createGroup(umi, { + name: 'group2', + updateAuthority: updateAuthority.publicKey, + }); + await createGroup(umi, { name: 'group3' }); + + const groups = await getGroupV1GpaBuilder(umi) + .whereField('updateAuthority', updateAuthority.publicKey) + .whereField('key', Key.GroupV1) + .getDeserialized(); + const names = ['group1', 'group2']; + + t.is(groups.length, 2); + t.assert(groups.every((g) => names.includes(g.name))); + t.assert( + groups.every((g) => g.updateAuthority === updateAuthority.publicKey) + ); +}); diff --git a/clients/js/test/groupAdditionalPlugins.test.ts b/clients/js/test/groupAdditionalPlugins.test.ts new file mode 100644 index 00000000..7e551ef7 --- /dev/null +++ b/clients/js/test/groupAdditionalPlugins.test.ts @@ -0,0 +1,131 @@ +import { publicKey } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { addGroupPlugin, removeGroupPlugin, updateGroupPlugin } from '../src'; +import { + DEFAULT_GROUP, + assertGroup, + createGroup, + createUmi, +} from './_setupRaw'; + +// Re-use the same Noop log wrapper program used across existing plugin tests. +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Group Plugins – Autograph +// ----------------------------------------------------------------------------- + +test('it can add, update, and remove an Autograph plugin on a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // --------------------------------------------------------------------------- + // 1. Add plugin with a single signature. + // --------------------------------------------------------------------------- + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'Autograph', + signatures: [ + { + address: umi.identity.publicKey, + message: 'Initial signature', + }, + ], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // Validate the group still matches our expectations (plugin data is checked in + // dedicated plugin unit tests – here we merely ensure the instruction + // executed successfully and the group account remains consistent). + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); + + // --------------------------------------------------------------------------- + // 2. Update plugin to include a second signature. + // --------------------------------------------------------------------------- + await updateGroupPlugin(umi, { + group: group.publicKey, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + plugin: { + type: 'Autograph', + signatures: [ + { address: umi.identity.publicKey, message: 'sig-0' }, + { address: umi.identity.publicKey, message: 'sig-1' }, + ], + }, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); + + // --------------------------------------------------------------------------- + // 3. Remove the plugin entirely. + // --------------------------------------------------------------------------- + await removeGroupPlugin(umi, { + group: group.publicKey, + plugin: { type: 'Autograph' }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); + +// ----------------------------------------------------------------------------- +// Group Plugins – VerifiedCreators +// ----------------------------------------------------------------------------- + +test('it can add and remove a VerifiedCreators plugin on a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // Add VerifiedCreators plugin with a single verified creator. + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'VerifiedCreators', + signatures: [ + { + address: umi.identity.publicKey, + verified: true, + }, + ], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); + + // Remove the plugin. + await removeGroupPlugin(umi, { + group: group.publicKey, + plugin: { type: 'VerifiedCreators' }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); diff --git a/clients/js/test/groupComplexRelations.test.ts b/clients/js/test/groupComplexRelations.test.ts new file mode 100644 index 00000000..72cf5526 --- /dev/null +++ b/clients/js/test/groupComplexRelations.test.ts @@ -0,0 +1,68 @@ +import test from 'ava'; +import { addGroupsToGroup, removeGroupsFromGroup } from '../src'; +import { assertGroup, createGroup, createUmi } from './_setupRaw'; + +// ----------------------------------------------------------------------------- +// Complex Group Relations – Parent ↔ Child Synchronisation +// ----------------------------------------------------------------------------- + +test('it keeps parentGroups in sync with groups when adding and removing child groups', async (t) => { + // --------------------------------------------------------------------------- + // 1. Setup – create a parent and a child group. + // --------------------------------------------------------------------------- + const umi = await createUmi(); + const parent = await createGroup(umi, { name: 'parent' }); + const child = await createGroup(umi, { name: 'child' }); + + // --------------------------------------------------------------------------- + // 2. Add the child to the parent. + // --------------------------------------------------------------------------- + await addGroupsToGroup(umi, { + parentGroup: parent.publicKey, + groups: [child.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts([ + { isSigner: false, isWritable: true, pubkey: child.publicKey }, + ]) + .sendAndConfirm(umi); + + // Parent should list child, and child should list parent. + await assertGroup(t, umi, { + group: parent.publicKey, + updateAuthority: umi.identity.publicKey, + groups: [child.publicKey], + }); + + await assertGroup(t, umi, { + group: child.publicKey, + updateAuthority: umi.identity.publicKey, + parentGroups: [parent.publicKey], + }); + + // --------------------------------------------------------------------------- + // 3. Remove the child again. + // --------------------------------------------------------------------------- + await removeGroupsFromGroup(umi, { + parentGroup: parent.publicKey, + groups: [child.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts([ + { isSigner: false, isWritable: true, pubkey: child.publicKey }, + ]) + .sendAndConfirm(umi); + + // Relations should now be cleared on both sides. + await assertGroup(t, umi, { + group: parent.publicKey, + updateAuthority: umi.identity.publicKey, + groups: [], + }); + + await assertGroup(t, umi, { + group: child.publicKey, + updateAuthority: umi.identity.publicKey, + parentGroups: [], + }); +}); diff --git a/clients/js/test/groupErrors.test.ts b/clients/js/test/groupErrors.test.ts new file mode 100644 index 00000000..ed4603ca --- /dev/null +++ b/clients/js/test/groupErrors.test.ts @@ -0,0 +1,101 @@ +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + createExternalPluginAdapterInitInfo, + ExternalPluginAdapterSchema, + writeGroupData, +} from '../src'; +import { addGroupExternalPluginAdapterV1 } from '../src/generated'; +import { createGroup, createUmi } from './_setupRaw'; + +// Re-use the SPL Noop log wrapper program used elsewhere in the suite. +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Error Handling – writeGroupData +// ----------------------------------------------------------------------------- + +test('writeGroupData fails for unsupported adapter types', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + await t.throwsAsync( + writeGroupData(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + key: { type: 'Oracle', baseAddress: umi.identity.publicKey }, + data: new Uint8Array([1, 2, 3]), + }).sendAndConfirm(umi) + ); +}); + +test('writeGroupData fails when no data sources are provided', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // Add a valid AppData adapter first so that the key exists on-chain. + const initInfo = createExternalPluginAdapterInitInfo({ + type: 'AppData', + dataAuthority: { type: 'UpdateAuthority' }, + schema: ExternalPluginAdapterSchema.Json, + }); + + await addGroupExternalPluginAdapterV1(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + initInfo, + }).sendAndConfirm(umi); + + // Attempt to write with neither `data` nor `buffer` provided. + await t.throwsAsync( + writeGroupData(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + key: { type: 'AppData', dataAuthority: { type: 'UpdateAuthority' } }, + data: null, + }).sendAndConfirm(umi) + ); +}); + +test('writeGroupData fails when signer is not the plugin data authority', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + const externalSigner = generateSigner(umi); + + // Add adapter with an explicit external signer set as data authority. + const initInfo = createExternalPluginAdapterInitInfo({ + type: 'AppData', + dataAuthority: { type: 'Address', address: externalSigner.publicKey }, + schema: ExternalPluginAdapterSchema.Json, + }); + + await addGroupExternalPluginAdapterV1(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + initInfo, + }).sendAndConfirm(umi); + + const bytes = new Uint8Array([1, 2, 3]); + + // Attempt to write using the update authority (umi.identity) instead of the + // designated data authority. + await t.throwsAsync( + writeGroupData(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, // Not authorised + key: { + type: 'AppData', + dataAuthority: { type: 'Address', address: externalSigner.publicKey }, + }, + data: bytes, + }).sendAndConfirm(umi) + ); +}); diff --git a/clients/js/test/groupExternalPluginAdapters.test.ts b/clients/js/test/groupExternalPluginAdapters.test.ts new file mode 100644 index 00000000..2f22f743 --- /dev/null +++ b/clients/js/test/groupExternalPluginAdapters.test.ts @@ -0,0 +1,69 @@ +import { publicKey } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + createExternalPluginAdapterInitInfo, + externalPluginAdapterKeyToBase, + ExternalPluginAdapterSchema, +} from '../src'; +import { + addGroupExternalPluginAdapterV1, + removeGroupExternalPluginAdapterV1, +} from '../src/generated'; +import { + assertGroup, + createGroup, + createUmi, + DEFAULT_GROUP, +} from './_setupRaw'; + +// Re-use the same SPL Noop log wrapper program used across existing plugin tests. +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Group External Plugin Adapters – AppData +// ----------------------------------------------------------------------------- + +test('it can add, write, and remove an AppData external plugin adapter on a group', async (t) => { + // --------------------------------------------------------------------------- + // 1. Setup – Create a group. + // --------------------------------------------------------------------------- + const umi = await createUmi(); + const group = await createGroup(umi); + + // --------------------------------------------------------------------------- + // 2. Add an AppData external plugin adapter (schema: JSON). + // --------------------------------------------------------------------------- + const initInfo = createExternalPluginAdapterInitInfo({ + type: 'AppData', + dataAuthority: { type: 'UpdateAuthority' }, + schema: ExternalPluginAdapterSchema.Json, + }); + + await addGroupExternalPluginAdapterV1(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + initInfo, + }).sendAndConfirm(umi); + + // --------------------------------------------------------------------------- + // 4. Remove the adapter and ensure it no longer exists. + // --------------------------------------------------------------------------- + await removeGroupExternalPluginAdapterV1(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + key: externalPluginAdapterKeyToBase({ + type: 'AppData', + dataAuthority: { type: 'UpdateAuthority' }, + }), + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); diff --git a/clients/js/test/groupPlugins.test.ts b/clients/js/test/groupPlugins.test.ts new file mode 100644 index 00000000..030ce832 --- /dev/null +++ b/clients/js/test/groupPlugins.test.ts @@ -0,0 +1,138 @@ +import { publicKey } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + addGroupPlugin, + removeGroupPlugin, + updateGroup, + updateGroupPlugin, +} from '../src'; +import { + DEFAULT_GROUP, + assertGroup, + createGroup, + createUmi, +} from './_setupRaw'; + +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Group Plugins – Attributes +// ----------------------------------------------------------------------------- + +test('it can add attributes plugin to a group', async (t) => { + // Given a Umi instance and a freshly created group. + const umi = await createUmi(); + const group = await createGroup(umi); + + // When we add an Attributes plugin with an empty attribute list. + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'init', value: 'value' }], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // Then the group account should reflect the new plugin. + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); + +test('it can update an attributes plugin on a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // Add initial plugin with empty list. + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'init', value: 'value' }], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // Update the plugin with two attributes. + await updateGroupPlugin(umi, { + group: group.publicKey, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + plugin: { + type: 'Attributes', + attributeList: [ + { key: 'key0', value: 'value0' }, + { key: 'key1', value: 'value1' }, + ], + }, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); + +test('it can remove an attributes plugin from a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + // Add plugin first. + await addGroupPlugin(umi, { + group: group.publicKey, + plugin: { + type: 'Attributes', + attributeList: [{ key: 'init', value: 'value' }], + }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // Remove the plugin. + await removeGroupPlugin(umi, { + group: group.publicKey, + plugin: { type: 'Attributes' }, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + }).sendAndConfirm(umi); + + // The plugin should no longer be present. + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + }); +}); + +// ----------------------------------------------------------------------------- +// Group Update – Name & URI +// ----------------------------------------------------------------------------- + +test("it can update a group's name and URI", async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + const NEW_NAME = 'Updated Group'; + const NEW_URI = 'https://example.com/updated-group'; + + await updateGroup(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + newName: NEW_NAME, + newUri: NEW_URI, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + name: NEW_NAME, + uri: NEW_URI, + updateAuthority: umi.identity.publicKey, + }); +}); diff --git a/clients/js/test/groupRelations.test.ts b/clients/js/test/groupRelations.test.ts new file mode 100644 index 00000000..7d071c41 --- /dev/null +++ b/clients/js/test/groupRelations.test.ts @@ -0,0 +1,162 @@ +import test from 'ava'; +import { + addAssetsToGroup, + addCollectionsToGroup, + addGroupsToGroup, + removeAssetsFromGroup, + removeCollectionsFromGroup, + removeGroupsFromGroup, +} from '../src'; +import { + assertGroup, + createAsset, + createCollection, + createGroup, + createUmi, +} from './_setupRaw'; + +test('it can add and remove assets from a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + const asset1 = await createAsset(umi, {}); + const asset2 = await createAsset(umi, {}); + + // Add assets + await addAssetsToGroup(umi, { + group: group.publicKey, + authority: umi.identity, + }) + .addRemainingAccounts([ + { + isSigner: false, + isWritable: true, + pubkey: asset1.publicKey, + }, + { + isSigner: false, + isWritable: true, + pubkey: asset2.publicKey, + }, + ]) + .sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + assets: [asset1.publicKey, asset2.publicKey], + }); + + // Remove asset1 + await removeAssetsFromGroup(umi, { + group: group.publicKey, + assets: [asset1.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts({ + isSigner: false, + isWritable: true, + pubkey: asset1.publicKey, + }) + .sendAndConfirm(umi); + + // Assert only asset2 remains + await assertGroup(t, umi, { + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + assets: [asset2.publicKey], + }); + + t.pass(); +}); + +test('it can add and remove collections from a group', async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + const collection1 = await createCollection(umi, {}); + const collection2 = await createCollection(umi, {}); + + await addCollectionsToGroup(umi, { + group: group.publicKey, + authority: umi.identity, + }) + .addRemainingAccounts([ + { + isSigner: false, + isWritable: true, + pubkey: collection1.publicKey, + }, + { + isSigner: false, + isWritable: true, + pubkey: collection2.publicKey, + }, + ]) + .sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + collections: [collection1.publicKey, collection2.publicKey], + }); + + // Remove collection1 + await removeCollectionsFromGroup(umi, { + group: group.publicKey, + collections: [collection1.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts({ + isSigner: false, + isWritable: true, + pubkey: collection1.publicKey, + }) + .sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + updateAuthority: umi.identity.publicKey, + collections: [collection2.publicKey], + }); +}); + +test('it can add and remove child groups to a parent group', async (t) => { + const umi = await createUmi(); + const parent = await createGroup(umi, { name: 'parent' }); + const child1 = await createGroup(umi, { name: 'child1' }); + const child2 = await createGroup(umi, { name: 'child2' }); + + await addGroupsToGroup(umi, { + parentGroup: parent.publicKey, + groups: [child1.publicKey, child2.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts([ + { isSigner: false, isWritable: true, pubkey: child1.publicKey }, + { isSigner: false, isWritable: true, pubkey: child2.publicKey }, + ]) + .sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: parent.publicKey, + updateAuthority: umi.identity.publicKey, + groups: [child1.publicKey, child2.publicKey], + }); + + await removeGroupsFromGroup(umi, { + parentGroup: parent.publicKey, + groups: [child1.publicKey], + authority: umi.identity, + }) + .addRemainingAccounts([ + { isSigner: false, isWritable: true, pubkey: child1.publicKey }, + ]) + .sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: parent.publicKey, + updateAuthority: umi.identity.publicKey, + groups: [child2.publicKey], + }); + + t.pass(); +}); diff --git a/clients/js/test/groupWriteData.test.ts b/clients/js/test/groupWriteData.test.ts new file mode 100644 index 00000000..87b5f9ca --- /dev/null +++ b/clients/js/test/groupWriteData.test.ts @@ -0,0 +1,83 @@ +import { createAccount } from '@metaplex-foundation/mpl-toolbox'; +import { + assertAccountExists, + generateSigner, + publicKey, + sol, +} from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + createExternalPluginAdapterInitInfo, + ExternalPluginAdapterSchema, + writeGroupData, +} from '../src'; +import { addGroupExternalPluginAdapterV1 } from '../src/generated'; +import { getGroupV1AccountDataSerializer } from '../src/hooked'; +import { createGroup, createUmi } from './_setupRaw'; + +// Re-use the SPL Noop log wrapper program used elsewhere in the suite. +const LOG_WRAPPER = publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'); + +// ----------------------------------------------------------------------------- +// Write Data – AppData External Plugin Adapter +// ----------------------------------------------------------------------------- + +test('it can write JSON data to an AppData external plugin adapter on a group', async (t) => { + // --------------------------------------------------------------------------- + // 1. Setup – create a group and add an AppData external plugin adapter. + // --------------------------------------------------------------------------- + const umi = await createUmi(); + const group = await createGroup(umi); + + const initInfo = createExternalPluginAdapterInitInfo({ + type: 'AppData', + dataAuthority: { type: 'UpdateAuthority' }, + schema: ExternalPluginAdapterSchema.Json, + }); + + await addGroupExternalPluginAdapterV1(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + initInfo, + }).sendAndConfirm(umi); + + // --------------------------------------------------------------------------- + // 2. Write some JSON data to the adapter via a buffer account. + // --------------------------------------------------------------------------- + const json = JSON.stringify({ hello: 'world' }); + const dataBytes = new TextEncoder().encode(json); + + // Create a temporary buffer account containing the data. + const buffer = generateSigner(umi); + await createAccount(umi, { + newAccount: buffer, + lamports: sol(0.1), + space: dataBytes.length, + programId: umi.programs.get('mplCore').publicKey, + }).sendAndConfirm(umi); + + // Write data using the buffer account (no inline bytes). + await writeGroupData(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + logWrapper: LOG_WRAPPER, + key: { type: 'AppData', dataAuthority: { type: 'UpdateAuthority' } }, + buffer: buffer.publicKey, + data: null, + }).sendAndConfirm(umi); + + // --------------------------------------------------------------------------- + // 3. Verify the data has been written (dataLen should match length). + // --------------------------------------------------------------------------- + const account = await umi.rpc.getAccount(group.publicKey); + assertAccountExists(account, 'Group'); + const [decoded] = getGroupV1AccountDataSerializer().deserialize(account.data); + + t.true(Array.isArray(decoded.appDatas) && decoded.appDatas.length === 1); + if (decoded.appDatas) { + t.is(decoded.appDatas[0].dataLen, BigInt(dataBytes.length)); + } +}); diff --git a/clients/js/test/updateGroupAuthority.test.ts b/clients/js/test/updateGroupAuthority.test.ts new file mode 100644 index 00000000..ef2befc7 --- /dev/null +++ b/clients/js/test/updateGroupAuthority.test.ts @@ -0,0 +1,64 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { updateGroup } from '../src'; +import { + assertGroup, + createGroup, + createUmi, + DEFAULT_GROUP, +} from './_setupRaw'; + +// ----------------------------------------------------------------------------- +// Update Authority Transfer +// ----------------------------------------------------------------------------- + +test("it can transfer a group's update authority", async (t) => { + const umi = await createUmi(); + const group = await createGroup(umi); + + const newAuthority = generateSigner(umi); + + // 1. Transfer the update authority to the new signer. + await updateGroup(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + newUpdateAuthority: newAuthority.publicKey, + newName: null, + newUri: null, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + ...DEFAULT_GROUP, + group: group.publicKey, + updateAuthority: newAuthority.publicKey, + }); + + // 2. The new authority updates the group's name. + const UPDATED_NAME = 'Updated Group Name'; + + await updateGroup(umi, { + group: group.publicKey, + payer: umi.identity, + authority: newAuthority, + newName: UPDATED_NAME, + newUri: null, + }).sendAndConfirm(umi); + + await assertGroup(t, umi, { + group: group.publicKey, + name: UPDATED_NAME, + updateAuthority: newAuthority.publicKey, + }); + + // 3. Old authority attempting further updates should fail. + await t.throwsAsync( + updateGroup(umi, { + group: group.publicKey, + payer: umi.identity, + authority: umi.identity, + newName: 'Should Fail', + newUri: null, + }).sendAndConfirm(umi) + ); +}); diff --git a/clients/rust/Cargo.toml b/clients/rust/Cargo.toml index c5879a85..1e6efd0e 100644 --- a/clients/rust/Cargo.toml +++ b/clients/rust/Cargo.toml @@ -5,7 +5,7 @@ license-file = "../../LICENSE" name = "mpl-core" readme = "README.md" repository = "https://github.com/metaplex-foundation/mpl-core" -version = "0.11.0" +version = "0.10.1-alpha.2" [lib] crate-type = ["cdylib", "lib"] @@ -31,6 +31,7 @@ thiserror = "^1.0" kaigan = { version = "0.3.0",features = ["serde"], optional = false } + [dev-dependencies] assert_matches = "1.5.0" solana-program-test = "2.2.1" diff --git a/clients/rust/src/generated/accounts/group_v1.rs b/clients/rust/src/generated/accounts/group_v1.rs new file mode 100644 index 00000000..62c42828 --- /dev/null +++ b/clients/rust/src/generated/accounts/group_v1.rs @@ -0,0 +1,67 @@ +//! 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 crate::generated::types::Key; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GroupV1 { + pub key: Key, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub update_authority: Pubkey, + pub name: String, + pub uri: String, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::>") + )] + pub collections: Vec, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::>") + )] + pub groups: Vec, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::>") + )] + pub parent_groups: Vec, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::>") + )] + pub assets: Vec, +} + +impl GroupV1 { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for GroupV1 { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} diff --git a/clients/rust/src/generated/accounts/mod.rs b/clients/rust/src/generated/accounts/mod.rs index 27923350..9c543a2e 100644 --- a/clients/rust/src/generated/accounts/mod.rs +++ b/clients/rust/src/generated/accounts/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod r#asset_signer; pub(crate) mod r#base_asset_v1; pub(crate) mod r#base_collection_v1; +pub(crate) mod r#group_v1; pub(crate) mod r#hashed_asset_v1; pub(crate) mod r#plugin_header_v1; pub(crate) mod r#plugin_registry_v1; @@ -15,6 +16,7 @@ pub(crate) mod r#plugin_registry_v1; pub use self::r#asset_signer::*; pub use self::r#base_asset_v1::*; pub use self::r#base_collection_v1::*; +pub use self::r#group_v1::*; pub use self::r#hashed_asset_v1::*; pub use self::r#plugin_header_v1::*; pub use self::r#plugin_registry_v1::*; diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index 8d9af88c..c21f840b 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -163,6 +163,12 @@ pub enum MplCoreError { /// 50 (0x32) - Bubblegum V2 Plugin limits other plugins #[error("Bubblegum V2 Plugin limits other plugins")] BlockedByBubblegumV2, + /// 51 (0x33) - Group must be empty to be closed + #[error("Group must be empty to be closed")] + GroupMustBeEmpty, + /// 52 (0x34) - Duplicate entry provided when adding relationships to a group + #[error("Duplicate entry provided when adding relationships to a group")] + DuplicateEntry, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/clients/rust/src/generated/instructions/add_assets_to_group_v1.rs b/clients/rust/src/generated/instructions/add_assets_to_group_v1.rs new file mode 100644 index 00000000..935e2341 --- /dev/null +++ b/clients/rust/src/generated/instructions/add_assets_to_group_v1.rs @@ -0,0 +1,421 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddAssetsToGroupV1 { + /// The address of the group to modify + pub group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group/assets + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl AddAssetsToGroupV1 { + 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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = AddAssetsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddAssetsToGroupV1InstructionData { + discriminator: u8, +} + +impl AddAssetsToGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 35 } + } +} + +/// Instruction builder for `AddAssetsToGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct AddAssetsToGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + __remaining_accounts: Vec, +} + +impl AddAssetsToGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/assets + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 = AddAssetsToGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `add_assets_to_group_v1` CPI accounts. +pub struct AddAssetsToGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/assets + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `add_assets_to_group_v1` CPI instruction. +pub struct AddAssetsToGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/assets + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> AddAssetsToGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddAssetsToGroupV1CpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + 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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 = AddAssetsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `AddAssetsToGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct AddAssetsToGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddAssetsToGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddAssetsToGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/assets + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 = AddAssetsToGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + 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 AddAssetsToGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: 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/clients/rust/src/generated/instructions/add_collections_to_group_v1.rs b/clients/rust/src/generated/instructions/add_collections_to_group_v1.rs new file mode 100644 index 00000000..5efc0e5f --- /dev/null +++ b/clients/rust/src/generated/instructions/add_collections_to_group_v1.rs @@ -0,0 +1,421 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddCollectionsToGroupV1 { + /// The address of the group to modify + pub group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group/collections + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl AddCollectionsToGroupV1 { + 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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = AddCollectionsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddCollectionsToGroupV1InstructionData { + discriminator: u8, +} + +impl AddCollectionsToGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 33 } + } +} + +/// Instruction builder for `AddCollectionsToGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct AddCollectionsToGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + __remaining_accounts: Vec, +} + +impl AddCollectionsToGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/collections + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 = AddCollectionsToGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `add_collections_to_group_v1` CPI accounts. +pub struct AddCollectionsToGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/collections + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `add_collections_to_group_v1` CPI instruction. +pub struct AddCollectionsToGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/collections + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> AddCollectionsToGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddCollectionsToGroupV1CpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + 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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 = AddCollectionsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `AddCollectionsToGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct AddCollectionsToGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddCollectionsToGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddCollectionsToGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/collections + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 = AddCollectionsToGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + 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 AddCollectionsToGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: 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/clients/rust/src/generated/instructions/add_group_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/add_group_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..dd7df6e2 --- /dev/null +++ b/clients/rust/src/generated/instructions/add_group_external_plugin_adapter_v1.rs @@ -0,0 +1,526 @@ +//! 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 crate::generated::types::ExternalPluginAdapterInitInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddGroupExternalPluginAdapterV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl AddGroupExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: AddGroupExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: AddGroupExternalPluginAdapterV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = AddGroupExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddGroupExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl AddGroupExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 44 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddGroupExternalPluginAdapterV1InstructionArgs { + pub init_info: ExternalPluginAdapterInitInfo, +} + +/// Instruction builder for `AddGroupExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct AddGroupExternalPluginAdapterV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + init_info: Option, + __remaining_accounts: Vec, +} + +impl AddGroupExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.init_info = Some(init_info); + 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 = AddGroupExternalPluginAdapterV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = AddGroupExternalPluginAdapterV1InstructionArgs { + init_info: self.init_info.clone().expect("init_info is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `add_group_external_plugin_adapter_v1` CPI accounts. +pub struct AddGroupExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `add_group_external_plugin_adapter_v1` CPI instruction. +pub struct AddGroupExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: AddGroupExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> AddGroupExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddGroupExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: AddGroupExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = AddGroupExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `AddGroupExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct AddGroupExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddGroupExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddGroupExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + init_info: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.instruction.init_info = Some(init_info); + 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 args = AddGroupExternalPluginAdapterV1InstructionArgs { + init_info: self + .instruction + .init_info + .clone() + .expect("init_info is not set"), + }; + let instruction = AddGroupExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct AddGroupExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + init_info: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/add_group_plugin_v1.rs b/clients/rust/src/generated/instructions/add_group_plugin_v1.rs new file mode 100644 index 00000000..95dafcd3 --- /dev/null +++ b/clients/rust/src/generated/instructions/add_group_plugin_v1.rs @@ -0,0 +1,537 @@ +//! 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 crate::generated::types::Plugin; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddGroupPluginV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl AddGroupPluginV1 { + pub fn instruction( + &self, + args: AddGroupPluginV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: AddGroupPluginV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = AddGroupPluginV1InstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddGroupPluginV1InstructionData { + discriminator: u8, +} + +impl AddGroupPluginV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 39 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddGroupPluginV1InstructionArgs { + pub plugin: Plugin, + pub init_authority: Option, +} + +/// Instruction builder for `AddGroupPluginV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct AddGroupPluginV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + plugin: Option, + init_authority: Option, + __remaining_accounts: Vec, +} + +impl AddGroupPluginV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin(&mut self, plugin: Plugin) -> &mut Self { + self.plugin = Some(plugin); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: PluginAuthority) -> &mut Self { + self.init_authority = Some(init_authority); + 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 = AddGroupPluginV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = AddGroupPluginV1InstructionArgs { + plugin: self.plugin.clone().expect("plugin is not set"), + init_authority: self.init_authority.clone(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `add_group_plugin_v1` CPI accounts. +pub struct AddGroupPluginV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `add_group_plugin_v1` CPI instruction. +pub struct AddGroupPluginV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: AddGroupPluginV1InstructionArgs, +} + +impl<'a, 'b> AddGroupPluginV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddGroupPluginV1CpiAccounts<'a, 'b>, + args: AddGroupPluginV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = AddGroupPluginV1InstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `AddGroupPluginV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct AddGroupPluginV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddGroupPluginV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddGroupPluginV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + plugin: None, + init_authority: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin(&mut self, plugin: Plugin) -> &mut Self { + self.instruction.plugin = Some(plugin); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn init_authority(&mut self, init_authority: PluginAuthority) -> &mut Self { + self.instruction.init_authority = Some(init_authority); + 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 args = AddGroupPluginV1InstructionArgs { + plugin: self.instruction.plugin.clone().expect("plugin is not set"), + init_authority: self.instruction.init_authority.clone(), + }; + let instruction = AddGroupPluginV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct AddGroupPluginV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + plugin: Option, + init_authority: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/add_groups_to_group_v1.rs b/clients/rust/src/generated/instructions/add_groups_to_group_v1.rs new file mode 100644 index 00000000..9f89cdf1 --- /dev/null +++ b/clients/rust/src/generated/instructions/add_groups_to_group_v1.rs @@ -0,0 +1,469 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +/// Accounts. +pub struct AddGroupsToGroupV1 { + /// The address of the parent group to modify + pub parent_group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the groups + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl AddGroupsToGroupV1 { + pub fn instruction( + &self, + args: AddGroupsToGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: AddGroupsToGroupV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.parent_group, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = AddGroupsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddGroupsToGroupV1InstructionData { + discriminator: u8, +} + +impl AddGroupsToGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 37 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddGroupsToGroupV1InstructionArgs { + pub groups: Vec, +} + +/// Instruction builder for `AddGroupsToGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` parent_group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct AddGroupsToGroupV1Builder { + parent_group: Option, + payer: Option, + authority: Option, + system_program: Option, + groups: Option>, + __remaining_accounts: Vec, +} + +impl AddGroupsToGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the parent group to modify + #[inline(always)] + pub fn parent_group(&mut self, parent_group: solana_program::pubkey::Pubkey) -> &mut Self { + self.parent_group = Some(parent_group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the groups + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + #[inline(always)] + pub fn groups(&mut self, groups: Vec) -> &mut Self { + self.groups = Some(groups); + 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 = AddGroupsToGroupV1 { + parent_group: self.parent_group.expect("parent_group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = AddGroupsToGroupV1InstructionArgs { + groups: self.groups.clone().expect("groups is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `add_groups_to_group_v1` CPI accounts. +pub struct AddGroupsToGroupV1CpiAccounts<'a, 'b> { + /// The address of the parent group to modify + pub parent_group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the groups + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `add_groups_to_group_v1` CPI instruction. +pub struct AddGroupsToGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the parent group to modify + pub parent_group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the groups + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: AddGroupsToGroupV1InstructionArgs, +} + +impl<'a, 'b> AddGroupsToGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddGroupsToGroupV1CpiAccounts<'a, 'b>, + args: AddGroupsToGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + parent_group: accounts.parent_group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + __args: args, + } + } + #[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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.parent_group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 mut data = AddGroupsToGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.parent_group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `AddGroupsToGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` parent_group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct AddGroupsToGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddGroupsToGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddGroupsToGroupV1CpiBuilderInstruction { + __program: program, + parent_group: None, + payer: None, + authority: None, + system_program: None, + groups: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the parent group to modify + #[inline(always)] + pub fn parent_group( + &mut self, + parent_group: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.parent_group = Some(parent_group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the groups + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + #[inline(always)] + pub fn groups(&mut self, groups: Vec) -> &mut Self { + self.instruction.groups = Some(groups); + 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 args = AddGroupsToGroupV1InstructionArgs { + groups: self.instruction.groups.clone().expect("groups is not set"), + }; + let instruction = AddGroupsToGroupV1Cpi { + __program: self.instruction.__program, + + parent_group: self + .instruction + .parent_group + .expect("parent_group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct AddGroupsToGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + parent_group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + groups: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/approve_group_plugin_authority_v1.rs b/clients/rust/src/generated/instructions/approve_group_plugin_authority_v1.rs new file mode 100644 index 00000000..df335492 --- /dev/null +++ b/clients/rust/src/generated/instructions/approve_group_plugin_authority_v1.rs @@ -0,0 +1,550 @@ +//! 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 crate::generated::types::PluginAuthority; +use crate::generated::types::PluginType; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct ApproveGroupPluginAuthorityV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl ApproveGroupPluginAuthorityV1 { + pub fn instruction( + &self, + args: ApproveGroupPluginAuthorityV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: ApproveGroupPluginAuthorityV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = ApproveGroupPluginAuthorityV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct ApproveGroupPluginAuthorityV1InstructionData { + discriminator: u8, +} + +impl ApproveGroupPluginAuthorityV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 42 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ApproveGroupPluginAuthorityV1InstructionArgs { + pub plugin_type: PluginType, + pub new_authority: PluginAuthority, +} + +/// Instruction builder for `ApproveGroupPluginAuthorityV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct ApproveGroupPluginAuthorityV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + plugin_type: Option, + new_authority: Option, + __remaining_accounts: Vec, +} + +impl ApproveGroupPluginAuthorityV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.plugin_type = Some(plugin_type); + self + } + #[inline(always)] + pub fn new_authority(&mut self, new_authority: PluginAuthority) -> &mut Self { + self.new_authority = Some(new_authority); + 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 = ApproveGroupPluginAuthorityV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = ApproveGroupPluginAuthorityV1InstructionArgs { + plugin_type: self.plugin_type.clone().expect("plugin_type is not set"), + new_authority: self + .new_authority + .clone() + .expect("new_authority is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `approve_group_plugin_authority_v1` CPI accounts. +pub struct ApproveGroupPluginAuthorityV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `approve_group_plugin_authority_v1` CPI instruction. +pub struct ApproveGroupPluginAuthorityV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: ApproveGroupPluginAuthorityV1InstructionArgs, +} + +impl<'a, 'b> ApproveGroupPluginAuthorityV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: ApproveGroupPluginAuthorityV1CpiAccounts<'a, 'b>, + args: ApproveGroupPluginAuthorityV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = ApproveGroupPluginAuthorityV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `ApproveGroupPluginAuthorityV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct ApproveGroupPluginAuthorityV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> ApproveGroupPluginAuthorityV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(ApproveGroupPluginAuthorityV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + plugin_type: None, + new_authority: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.instruction.plugin_type = Some(plugin_type); + self + } + #[inline(always)] + pub fn new_authority(&mut self, new_authority: PluginAuthority) -> &mut Self { + self.instruction.new_authority = Some(new_authority); + 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 args = ApproveGroupPluginAuthorityV1InstructionArgs { + plugin_type: self + .instruction + .plugin_type + .clone() + .expect("plugin_type is not set"), + new_authority: self + .instruction + .new_authority + .clone() + .expect("new_authority is not set"), + }; + let instruction = ApproveGroupPluginAuthorityV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct ApproveGroupPluginAuthorityV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + plugin_type: Option, + new_authority: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/close_group_v1.rs b/clients/rust/src/generated/instructions/close_group_v1.rs new file mode 100644 index 00000000..05a2bc09 --- /dev/null +++ b/clients/rust/src/generated/instructions/close_group_v1.rs @@ -0,0 +1,372 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct CloseGroupV1 { + /// The address of the group to close + pub group: solana_program::pubkey::Pubkey, + /// The account receiving reclaimed lamports + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or update delegate of the group + pub authority: Option, +} + +impl CloseGroupV1 { + 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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = CloseGroupV1InstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct CloseGroupV1InstructionData { + discriminator: u8, +} + +impl CloseGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 48 } + } +} + +/// Instruction builder for `CloseGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +#[derive(Default)] +pub struct CloseGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + __remaining_accounts: Vec, +} + +impl CloseGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to close + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account receiving reclaimed lamports + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or update delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + 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 = CloseGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `close_group_v1` CPI accounts. +pub struct CloseGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to close + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account receiving reclaimed lamports + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `close_group_v1` CPI instruction. +pub struct CloseGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to close + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account receiving reclaimed lamports + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> CloseGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: CloseGroupV1CpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + } + } + #[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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 = CloseGroupV1InstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `CloseGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +pub struct CloseGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> CloseGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(CloseGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to close + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account receiving reclaimed lamports + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or update delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + 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 = CloseGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct CloseGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: 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/clients/rust/src/generated/instructions/create_group_v1.rs b/clients/rust/src/generated/instructions/create_group_v1.rs new file mode 100644 index 00000000..6866be03 --- /dev/null +++ b/clients/rust/src/generated/instructions/create_group_v1.rs @@ -0,0 +1,501 @@ +//! 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 crate::generated::types::RelationshipEntry; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct CreateGroupV1 { + /// The address of the new group + pub group: solana_program::pubkey::Pubkey, + /// The authority of the new group + pub update_authority: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl CreateGroupV1 { + pub fn instruction( + &self, + args: CreateGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: CreateGroupV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, true, + )); + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + update_authority, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = CreateGroupV1InstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct CreateGroupV1InstructionData { + discriminator: u8, +} + +impl CreateGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 47 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CreateGroupV1InstructionArgs { + pub name: String, + pub uri: String, + pub relationships: Vec, +} + +/// Instruction builder for `CreateGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` group +/// 1. `[optional]` update_authority +/// 2. `[writable, signer]` payer +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct CreateGroupV1Builder { + group: Option, + update_authority: Option, + payer: Option, + system_program: Option, + name: Option, + uri: Option, + relationships: Option>, + __remaining_accounts: Vec, +} + +impl CreateGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the new group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// `[optional account]` + /// The authority of the new group + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option, + ) -> &mut Self { + self.update_authority = update_authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.uri = Some(uri); + self + } + #[inline(always)] + pub fn relationships(&mut self, relationships: Vec) -> &mut Self { + self.relationships = Some(relationships); + 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 = CreateGroupV1 { + group: self.group.expect("group is not set"), + update_authority: self.update_authority, + payer: self.payer.expect("payer is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = CreateGroupV1InstructionArgs { + name: self.name.clone().expect("name is not set"), + uri: self.uri.clone().expect("uri is not set"), + relationships: self + .relationships + .clone() + .expect("relationships is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `create_group_v1` CPI accounts. +pub struct CreateGroupV1CpiAccounts<'a, 'b> { + /// The address of the new group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new group + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `create_group_v1` CPI instruction. +pub struct CreateGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the new group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new group + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: CreateGroupV1InstructionArgs, +} + +impl<'a, 'b> CreateGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: CreateGroupV1CpiAccounts<'a, 'b>, + args: CreateGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + update_authority: accounts.update_authority, + payer: accounts.payer, + system_program: accounts.system_program, + __args: args, + } + } + #[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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + true, + )); + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *update_authority.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = CreateGroupV1InstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + if let Some(update_authority) = self.update_authority { + account_infos.push(update_authority.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) + } + } +} + +/// Instruction builder for `CreateGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` group +/// 1. `[optional]` update_authority +/// 2. `[writable, signer]` payer +/// 3. `[]` system_program +pub struct CreateGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> CreateGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(CreateGroupV1CpiBuilderInstruction { + __program: program, + group: None, + update_authority: None, + payer: None, + system_program: None, + name: None, + uri: None, + relationships: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the new group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// `[optional account]` + /// The authority of the new group + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.update_authority = update_authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// The 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 + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.instruction.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.instruction.uri = Some(uri); + self + } + #[inline(always)] + pub fn relationships(&mut self, relationships: Vec) -> &mut Self { + self.instruction.relationships = Some(relationships); + 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 args = CreateGroupV1InstructionArgs { + name: self.instruction.name.clone().expect("name is not set"), + uri: self.instruction.uri.clone().expect("uri is not set"), + relationships: self + .instruction + .relationships + .clone() + .expect("relationships is not set"), + }; + let instruction = CreateGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + update_authority: self.instruction.update_authority, + + payer: self.instruction.payer.expect("payer is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct CreateGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + update_authority: 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>>, + name: Option, + uri: Option, + relationships: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index 5ceddc77..b5c8e9a5 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -5,27 +5,41 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod r#add_assets_to_group_v1; pub(crate) mod r#add_collection_external_plugin_adapter_v1; pub(crate) mod r#add_collection_plugin_v1; +pub(crate) mod r#add_collections_to_group_v1; pub(crate) mod r#add_external_plugin_adapter_v1; +pub(crate) mod r#add_group_external_plugin_adapter_v1; +pub(crate) mod r#add_group_plugin_v1; +pub(crate) mod r#add_groups_to_group_v1; pub(crate) mod r#add_plugin_v1; pub(crate) mod r#approve_collection_plugin_authority_v1; +pub(crate) mod r#approve_group_plugin_authority_v1; pub(crate) mod r#approve_plugin_authority_v1; pub(crate) mod r#burn_collection_v1; pub(crate) mod r#burn_v1; +pub(crate) mod r#close_group_v1; pub(crate) mod r#collect; pub(crate) mod r#compress_v1; pub(crate) mod r#create_collection_v1; pub(crate) mod r#create_collection_v2; +pub(crate) mod r#create_group_v1; pub(crate) mod r#create_v1; pub(crate) mod r#create_v2; pub(crate) mod r#decompress_v1; pub(crate) mod r#execute_v1; +pub(crate) mod r#remove_assets_from_group_v1; pub(crate) mod r#remove_collection_external_plugin_adapter_v1; pub(crate) mod r#remove_collection_plugin_v1; +pub(crate) mod r#remove_collections_from_group_v1; pub(crate) mod r#remove_external_plugin_adapter_v1; +pub(crate) mod r#remove_group_external_plugin_adapter_v1; +pub(crate) mod r#remove_group_plugin_v1; +pub(crate) mod r#remove_groups_from_group_v1; pub(crate) mod r#remove_plugin_v1; pub(crate) mod r#revoke_collection_plugin_authority_v1; +pub(crate) mod r#revoke_group_plugin_authority_v1; pub(crate) mod r#revoke_plugin_authority_v1; pub(crate) mod r#transfer_v1; pub(crate) mod r#update_collection_external_plugin_adapter_v1; @@ -33,33 +47,50 @@ pub(crate) mod r#update_collection_info_v1; pub(crate) mod r#update_collection_plugin_v1; pub(crate) mod r#update_collection_v1; pub(crate) mod r#update_external_plugin_adapter_v1; +pub(crate) mod r#update_group_plugin_v1; +pub(crate) mod r#update_group_v1; pub(crate) mod r#update_plugin_v1; pub(crate) mod r#update_v1; pub(crate) mod r#update_v2; pub(crate) mod r#write_collection_external_plugin_adapter_data_v1; pub(crate) mod r#write_external_plugin_adapter_data_v1; +pub(crate) mod r#write_group_external_plugin_adapter_data_v1; +pub use self::r#add_assets_to_group_v1::*; pub use self::r#add_collection_external_plugin_adapter_v1::*; pub use self::r#add_collection_plugin_v1::*; +pub use self::r#add_collections_to_group_v1::*; pub use self::r#add_external_plugin_adapter_v1::*; +pub use self::r#add_group_external_plugin_adapter_v1::*; +pub use self::r#add_group_plugin_v1::*; +pub use self::r#add_groups_to_group_v1::*; pub use self::r#add_plugin_v1::*; pub use self::r#approve_collection_plugin_authority_v1::*; +pub use self::r#approve_group_plugin_authority_v1::*; pub use self::r#approve_plugin_authority_v1::*; pub use self::r#burn_collection_v1::*; pub use self::r#burn_v1::*; +pub use self::r#close_group_v1::*; pub use self::r#collect::*; pub use self::r#compress_v1::*; pub use self::r#create_collection_v1::*; pub use self::r#create_collection_v2::*; +pub use self::r#create_group_v1::*; pub use self::r#create_v1::*; pub use self::r#create_v2::*; pub use self::r#decompress_v1::*; pub use self::r#execute_v1::*; +pub use self::r#remove_assets_from_group_v1::*; pub use self::r#remove_collection_external_plugin_adapter_v1::*; pub use self::r#remove_collection_plugin_v1::*; +pub use self::r#remove_collections_from_group_v1::*; pub use self::r#remove_external_plugin_adapter_v1::*; +pub use self::r#remove_group_external_plugin_adapter_v1::*; +pub use self::r#remove_group_plugin_v1::*; +pub use self::r#remove_groups_from_group_v1::*; pub use self::r#remove_plugin_v1::*; pub use self::r#revoke_collection_plugin_authority_v1::*; +pub use self::r#revoke_group_plugin_authority_v1::*; pub use self::r#revoke_plugin_authority_v1::*; pub use self::r#transfer_v1::*; pub use self::r#update_collection_external_plugin_adapter_v1::*; @@ -67,8 +98,11 @@ pub use self::r#update_collection_info_v1::*; pub use self::r#update_collection_plugin_v1::*; pub use self::r#update_collection_v1::*; pub use self::r#update_external_plugin_adapter_v1::*; +pub use self::r#update_group_plugin_v1::*; +pub use self::r#update_group_v1::*; pub use self::r#update_plugin_v1::*; pub use self::r#update_v1::*; pub use self::r#update_v2::*; pub use self::r#write_collection_external_plugin_adapter_data_v1::*; pub use self::r#write_external_plugin_adapter_data_v1::*; +pub use self::r#write_group_external_plugin_adapter_data_v1::*; diff --git a/clients/rust/src/generated/instructions/remove_assets_from_group_v1.rs b/clients/rust/src/generated/instructions/remove_assets_from_group_v1.rs new file mode 100644 index 00000000..cc5dbe9c --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_assets_from_group_v1.rs @@ -0,0 +1,462 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +/// Accounts. +pub struct RemoveAssetsFromGroupV1 { + /// The address of the group to modify + pub group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group/assets + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl RemoveAssetsFromGroupV1 { + pub fn instruction( + &self, + args: RemoveAssetsFromGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveAssetsFromGroupV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveAssetsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveAssetsFromGroupV1InstructionData { + discriminator: u8, +} + +impl RemoveAssetsFromGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 36 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveAssetsFromGroupV1InstructionArgs { + pub assets: Vec, +} + +/// Instruction builder for `RemoveAssetsFromGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct RemoveAssetsFromGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + assets: Option>, + __remaining_accounts: Vec, +} + +impl RemoveAssetsFromGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/assets + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + #[inline(always)] + pub fn assets(&mut self, assets: Vec) -> &mut Self { + self.assets = Some(assets); + 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 = RemoveAssetsFromGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = RemoveAssetsFromGroupV1InstructionArgs { + assets: self.assets.clone().expect("assets is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_assets_from_group_v1` CPI accounts. +pub struct RemoveAssetsFromGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/assets + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `remove_assets_from_group_v1` CPI instruction. +pub struct RemoveAssetsFromGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/assets + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: RemoveAssetsFromGroupV1InstructionArgs, +} + +impl<'a, 'b> RemoveAssetsFromGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveAssetsFromGroupV1CpiAccounts<'a, 'b>, + args: RemoveAssetsFromGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + __args: args, + } + } + #[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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 mut data = RemoveAssetsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `RemoveAssetsFromGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct RemoveAssetsFromGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveAssetsFromGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveAssetsFromGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + assets: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/assets + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + #[inline(always)] + pub fn assets(&mut self, assets: Vec) -> &mut Self { + self.instruction.assets = Some(assets); + 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 args = RemoveAssetsFromGroupV1InstructionArgs { + assets: self.instruction.assets.clone().expect("assets is not set"), + }; + let instruction = RemoveAssetsFromGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveAssetsFromGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + assets: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/remove_collections_from_group_v1.rs b/clients/rust/src/generated/instructions/remove_collections_from_group_v1.rs new file mode 100644 index 00000000..824b196c --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_collections_from_group_v1.rs @@ -0,0 +1,466 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +/// Accounts. +pub struct RemoveCollectionsFromGroupV1 { + /// The address of the group to modify + pub group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group/collections + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl RemoveCollectionsFromGroupV1 { + pub fn instruction( + &self, + args: RemoveCollectionsFromGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveCollectionsFromGroupV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveCollectionsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveCollectionsFromGroupV1InstructionData { + discriminator: u8, +} + +impl RemoveCollectionsFromGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 34 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveCollectionsFromGroupV1InstructionArgs { + pub collections: Vec, +} + +/// Instruction builder for `RemoveCollectionsFromGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct RemoveCollectionsFromGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + collections: Option>, + __remaining_accounts: Vec, +} + +impl RemoveCollectionsFromGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/collections + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + #[inline(always)] + pub fn collections(&mut self, collections: Vec) -> &mut Self { + self.collections = Some(collections); + 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 = RemoveCollectionsFromGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = RemoveCollectionsFromGroupV1InstructionArgs { + collections: self.collections.clone().expect("collections is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_collections_from_group_v1` CPI accounts. +pub struct RemoveCollectionsFromGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/collections + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `remove_collections_from_group_v1` CPI instruction. +pub struct RemoveCollectionsFromGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to modify + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group/collections + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: RemoveCollectionsFromGroupV1InstructionArgs, +} + +impl<'a, 'b> RemoveCollectionsFromGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveCollectionsFromGroupV1CpiAccounts<'a, 'b>, + args: RemoveCollectionsFromGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + __args: args, + } + } + #[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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 mut data = RemoveCollectionsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `RemoveCollectionsFromGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct RemoveCollectionsFromGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveCollectionsFromGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveCollectionsFromGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + collections: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to modify + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group/collections + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + #[inline(always)] + pub fn collections(&mut self, collections: Vec) -> &mut Self { + self.instruction.collections = Some(collections); + 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 args = RemoveCollectionsFromGroupV1InstructionArgs { + collections: self + .instruction + .collections + .clone() + .expect("collections is not set"), + }; + let instruction = RemoveCollectionsFromGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveCollectionsFromGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collections: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/remove_group_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/remove_group_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..d702b087 --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_group_external_plugin_adapter_v1.rs @@ -0,0 +1,522 @@ +//! 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 crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct RemoveGroupExternalPluginAdapterV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl RemoveGroupExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: RemoveGroupExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveGroupExternalPluginAdapterV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveGroupExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveGroupExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl RemoveGroupExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 45 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveGroupExternalPluginAdapterV1InstructionArgs { + pub key: ExternalPluginAdapterKey, +} + +/// Instruction builder for `RemoveGroupExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct RemoveGroupExternalPluginAdapterV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + __remaining_accounts: Vec, +} + +impl RemoveGroupExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + 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 = RemoveGroupExternalPluginAdapterV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = RemoveGroupExternalPluginAdapterV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_group_external_plugin_adapter_v1` CPI accounts. +pub struct RemoveGroupExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `remove_group_external_plugin_adapter_v1` CPI instruction. +pub struct RemoveGroupExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: RemoveGroupExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> RemoveGroupExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveGroupExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: RemoveGroupExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = RemoveGroupExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `RemoveGroupExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct RemoveGroupExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveGroupExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveGroupExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + 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 args = RemoveGroupExternalPluginAdapterV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + }; + let instruction = RemoveGroupExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveGroupExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/remove_group_plugin_v1.rs b/clients/rust/src/generated/instructions/remove_group_plugin_v1.rs new file mode 100644 index 00000000..fd8b3193 --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_group_plugin_v1.rs @@ -0,0 +1,526 @@ +//! 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 crate::generated::types::PluginType; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct RemoveGroupPluginV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl RemoveGroupPluginV1 { + pub fn instruction( + &self, + args: RemoveGroupPluginV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveGroupPluginV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveGroupPluginV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveGroupPluginV1InstructionData { + discriminator: u8, +} + +impl RemoveGroupPluginV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 40 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveGroupPluginV1InstructionArgs { + pub plugin_type: PluginType, +} + +/// Instruction builder for `RemoveGroupPluginV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct RemoveGroupPluginV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + plugin_type: Option, + __remaining_accounts: Vec, +} + +impl RemoveGroupPluginV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.plugin_type = Some(plugin_type); + 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 = RemoveGroupPluginV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = RemoveGroupPluginV1InstructionArgs { + plugin_type: self.plugin_type.clone().expect("plugin_type is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_group_plugin_v1` CPI accounts. +pub struct RemoveGroupPluginV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `remove_group_plugin_v1` CPI instruction. +pub struct RemoveGroupPluginV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: RemoveGroupPluginV1InstructionArgs, +} + +impl<'a, 'b> RemoveGroupPluginV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveGroupPluginV1CpiAccounts<'a, 'b>, + args: RemoveGroupPluginV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = RemoveGroupPluginV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `RemoveGroupPluginV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct RemoveGroupPluginV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveGroupPluginV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveGroupPluginV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + plugin_type: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.instruction.plugin_type = Some(plugin_type); + 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 args = RemoveGroupPluginV1InstructionArgs { + plugin_type: self + .instruction + .plugin_type + .clone() + .expect("plugin_type is not set"), + }; + let instruction = RemoveGroupPluginV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveGroupPluginV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + plugin_type: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/remove_groups_from_group_v1.rs b/clients/rust/src/generated/instructions/remove_groups_from_group_v1.rs new file mode 100644 index 00000000..83d1e476 --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_groups_from_group_v1.rs @@ -0,0 +1,469 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +/// Accounts. +pub struct RemoveGroupsFromGroupV1 { + /// The address of the parent group to modify + pub parent_group: solana_program::pubkey::Pubkey, + /// The account paying for storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the groups + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl RemoveGroupsFromGroupV1 { + pub fn instruction( + &self, + args: RemoveGroupsFromGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveGroupsFromGroupV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.parent_group, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveGroupsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveGroupsFromGroupV1InstructionData { + discriminator: u8, +} + +impl RemoveGroupsFromGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 38 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveGroupsFromGroupV1InstructionArgs { + pub groups: Vec, +} + +/// Instruction builder for `RemoveGroupsFromGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` parent_group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct RemoveGroupsFromGroupV1Builder { + parent_group: Option, + payer: Option, + authority: Option, + system_program: Option, + groups: Option>, + __remaining_accounts: Vec, +} + +impl RemoveGroupsFromGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the parent group to modify + #[inline(always)] + pub fn parent_group(&mut self, parent_group: solana_program::pubkey::Pubkey) -> &mut Self { + self.parent_group = Some(parent_group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the groups + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + #[inline(always)] + pub fn groups(&mut self, groups: Vec) -> &mut Self { + self.groups = Some(groups); + 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 = RemoveGroupsFromGroupV1 { + parent_group: self.parent_group.expect("parent_group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = RemoveGroupsFromGroupV1InstructionArgs { + groups: self.groups.clone().expect("groups is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_groups_from_group_v1` CPI accounts. +pub struct RemoveGroupsFromGroupV1CpiAccounts<'a, 'b> { + /// The address of the parent group to modify + pub parent_group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the groups + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `remove_groups_from_group_v1` CPI instruction. +pub struct RemoveGroupsFromGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the parent group to modify + pub parent_group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the groups + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: RemoveGroupsFromGroupV1InstructionArgs, +} + +impl<'a, 'b> RemoveGroupsFromGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveGroupsFromGroupV1CpiAccounts<'a, 'b>, + args: RemoveGroupsFromGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + parent_group: accounts.parent_group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + __args: args, + } + } + #[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(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.parent_group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 mut data = RemoveGroupsFromGroupV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.parent_group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.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) + } + } +} + +/// Instruction builder for `RemoveGroupsFromGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` parent_group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +pub struct RemoveGroupsFromGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveGroupsFromGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveGroupsFromGroupV1CpiBuilderInstruction { + __program: program, + parent_group: None, + payer: None, + authority: None, + system_program: None, + groups: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the parent group to modify + #[inline(always)] + pub fn parent_group( + &mut self, + parent_group: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.parent_group = Some(parent_group); + self + } + /// The account paying for storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the groups + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + #[inline(always)] + pub fn groups(&mut self, groups: Vec) -> &mut Self { + self.instruction.groups = Some(groups); + 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 args = RemoveGroupsFromGroupV1InstructionArgs { + groups: self.instruction.groups.clone().expect("groups is not set"), + }; + let instruction = RemoveGroupsFromGroupV1Cpi { + __program: self.instruction.__program, + + parent_group: self + .instruction + .parent_group + .expect("parent_group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveGroupsFromGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + parent_group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + groups: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/revoke_group_plugin_authority_v1.rs b/clients/rust/src/generated/instructions/revoke_group_plugin_authority_v1.rs new file mode 100644 index 00000000..02b35070 --- /dev/null +++ b/clients/rust/src/generated/instructions/revoke_group_plugin_authority_v1.rs @@ -0,0 +1,526 @@ +//! 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 crate::generated::types::PluginType; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct RevokeGroupPluginAuthorityV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl RevokeGroupPluginAuthorityV1 { + pub fn instruction( + &self, + args: RevokeGroupPluginAuthorityV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RevokeGroupPluginAuthorityV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = RevokeGroupPluginAuthorityV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RevokeGroupPluginAuthorityV1InstructionData { + discriminator: u8, +} + +impl RevokeGroupPluginAuthorityV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 43 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RevokeGroupPluginAuthorityV1InstructionArgs { + pub plugin_type: PluginType, +} + +/// Instruction builder for `RevokeGroupPluginAuthorityV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct RevokeGroupPluginAuthorityV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + plugin_type: Option, + __remaining_accounts: Vec, +} + +impl RevokeGroupPluginAuthorityV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.plugin_type = Some(plugin_type); + 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 = RevokeGroupPluginAuthorityV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = RevokeGroupPluginAuthorityV1InstructionArgs { + plugin_type: self.plugin_type.clone().expect("plugin_type is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `revoke_group_plugin_authority_v1` CPI accounts. +pub struct RevokeGroupPluginAuthorityV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `revoke_group_plugin_authority_v1` CPI instruction. +pub struct RevokeGroupPluginAuthorityV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: RevokeGroupPluginAuthorityV1InstructionArgs, +} + +impl<'a, 'b> RevokeGroupPluginAuthorityV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RevokeGroupPluginAuthorityV1CpiAccounts<'a, 'b>, + args: RevokeGroupPluginAuthorityV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = RevokeGroupPluginAuthorityV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `RevokeGroupPluginAuthorityV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct RevokeGroupPluginAuthorityV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RevokeGroupPluginAuthorityV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RevokeGroupPluginAuthorityV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + plugin_type: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin_type(&mut self, plugin_type: PluginType) -> &mut Self { + self.instruction.plugin_type = Some(plugin_type); + 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 args = RevokeGroupPluginAuthorityV1InstructionArgs { + plugin_type: self + .instruction + .plugin_type + .clone() + .expect("plugin_type is not set"), + }; + let instruction = RevokeGroupPluginAuthorityV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RevokeGroupPluginAuthorityV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + plugin_type: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/update_group_plugin_v1.rs b/clients/rust/src/generated/instructions/update_group_plugin_v1.rs new file mode 100644 index 00000000..8d76648f --- /dev/null +++ b/clients/rust/src/generated/instructions/update_group_plugin_v1.rs @@ -0,0 +1,522 @@ +//! 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 crate::generated::types::Plugin; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct UpdateGroupPluginV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or delegate of the group + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl UpdateGroupPluginV1 { + pub fn instruction( + &self, + args: UpdateGroupPluginV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: UpdateGroupPluginV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = UpdateGroupPluginV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct UpdateGroupPluginV1InstructionData { + discriminator: u8, +} + +impl UpdateGroupPluginV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 41 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UpdateGroupPluginV1InstructionArgs { + pub plugin: Plugin, +} + +/// Instruction builder for `UpdateGroupPluginV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct UpdateGroupPluginV1Builder { + group: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + plugin: Option, + __remaining_accounts: Vec, +} + +impl UpdateGroupPluginV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin(&mut self, plugin: Plugin) -> &mut Self { + self.plugin = Some(plugin); + 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 = UpdateGroupPluginV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = UpdateGroupPluginV1InstructionArgs { + plugin: self.plugin.clone().expect("plugin is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `update_group_plugin_v1` CPI accounts. +pub struct UpdateGroupPluginV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `update_group_plugin_v1` CPI instruction. +pub struct UpdateGroupPluginV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: UpdateGroupPluginV1InstructionArgs, +} + +impl<'a, 'b> UpdateGroupPluginV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: UpdateGroupPluginV1CpiAccounts<'a, 'b>, + args: UpdateGroupPluginV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = UpdateGroupPluginV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `UpdateGroupPluginV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct UpdateGroupPluginV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> UpdateGroupPluginV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(UpdateGroupPluginV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + plugin: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn plugin(&mut self, plugin: Plugin) -> &mut Self { + self.instruction.plugin = Some(plugin); + 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 args = UpdateGroupPluginV1InstructionArgs { + plugin: self.instruction.plugin.clone().expect("plugin is not set"), + }; + let instruction = UpdateGroupPluginV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct UpdateGroupPluginV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + plugin: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/update_group_v1.rs b/clients/rust/src/generated/instructions/update_group_v1.rs new file mode 100644 index 00000000..ca52430b --- /dev/null +++ b/clients/rust/src/generated/instructions/update_group_v1.rs @@ -0,0 +1,537 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct UpdateGroupV1 { + /// The address of the group to update + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The update authority or update delegate of the group + pub authority: Option, + /// The new update authority of the group + pub new_update_authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl UpdateGroupV1 { + pub fn instruction( + &self, + args: UpdateGroupV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: UpdateGroupV1InstructionArgs, + 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( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(new_update_authority) = self.new_update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + new_update_authority, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = UpdateGroupV1InstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct UpdateGroupV1InstructionData { + discriminator: u8, +} + +impl UpdateGroupV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 49 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UpdateGroupV1InstructionArgs { + pub new_name: Option, + pub new_uri: Option, +} + +/// Instruction builder for `UpdateGroupV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` new_update_authority +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct UpdateGroupV1Builder { + group: Option, + payer: Option, + authority: Option, + new_update_authority: Option, + system_program: Option, + new_name: Option, + new_uri: Option, + __remaining_accounts: Vec, +} + +impl UpdateGroupV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group to update + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or update delegate of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account]` + /// The new update authority of the group + #[inline(always)] + pub fn new_update_authority( + &mut self, + new_update_authority: Option, + ) -> &mut Self { + self.new_update_authority = new_update_authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional argument]` + #[inline(always)] + pub fn new_name(&mut self, new_name: String) -> &mut Self { + self.new_name = Some(new_name); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn new_uri(&mut self, new_uri: String) -> &mut Self { + self.new_uri = Some(new_uri); + 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 = UpdateGroupV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + new_update_authority: self.new_update_authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = UpdateGroupV1InstructionArgs { + new_name: self.new_name.clone(), + new_uri: self.new_uri.clone(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `update_group_v1` CPI accounts. +pub struct UpdateGroupV1CpiAccounts<'a, 'b> { + /// The address of the group to update + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new update authority of the group + pub new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `update_group_v1` CPI instruction. +pub struct UpdateGroupV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group to update + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update delegate of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new update authority of the group + pub new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: UpdateGroupV1InstructionArgs, +} + +impl<'a, 'b> UpdateGroupV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: UpdateGroupV1CpiAccounts<'a, 'b>, + args: UpdateGroupV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + new_update_authority: accounts.new_update_authority, + system_program: accounts.system_program, + __args: args, + } + } + #[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( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(new_update_authority) = self.new_update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *new_update_authority.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + 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 mut data = UpdateGroupV1InstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_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.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + if let Some(new_update_authority) = self.new_update_authority { + account_infos.push(new_update_authority.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) + } + } +} + +/// Instruction builder for `UpdateGroupV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` new_update_authority +/// 4. `[]` system_program +pub struct UpdateGroupV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> UpdateGroupV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(UpdateGroupV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + new_update_authority: None, + system_program: None, + new_name: None, + new_uri: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group to update + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The update authority or update delegate of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// `[optional account]` + /// The new update authority of the group + #[inline(always)] + pub fn new_update_authority( + &mut self, + new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.new_update_authority = new_update_authority; + self + } + /// The 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 + } + /// `[optional argument]` + #[inline(always)] + pub fn new_name(&mut self, new_name: String) -> &mut Self { + self.instruction.new_name = Some(new_name); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn new_uri(&mut self, new_uri: String) -> &mut Self { + self.instruction.new_uri = Some(new_uri); + 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 args = UpdateGroupV1InstructionArgs { + new_name: self.instruction.new_name.clone(), + new_uri: self.instruction.new_uri.clone(), + }; + let instruction = UpdateGroupV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + new_update_authority: self.instruction.new_update_authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct UpdateGroupV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_name: Option, + new_uri: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/write_group_external_plugin_adapter_data_v1.rs b/clients/rust/src/generated/instructions/write_group_external_plugin_adapter_data_v1.rs new file mode 100644 index 00000000..4282522c --- /dev/null +++ b/clients/rust/src/generated/instructions/write_group_external_plugin_adapter_data_v1.rs @@ -0,0 +1,596 @@ +//! 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 crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct WriteGroupExternalPluginAdapterDataV1 { + /// The address of the group + pub group: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The data authority or update authority of the group + pub authority: Option, + /// Buffer account containing data + pub buffer: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl WriteGroupExternalPluginAdapterDataV1 { + pub fn instruction( + &self, + args: WriteGroupExternalPluginAdapterDataV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: WriteGroupExternalPluginAdapterDataV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.group, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + buffer, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = WriteGroupExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct WriteGroupExternalPluginAdapterDataV1InstructionData { + discriminator: u8, +} + +impl WriteGroupExternalPluginAdapterDataV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 46 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WriteGroupExternalPluginAdapterDataV1InstructionArgs { + pub key: ExternalPluginAdapterKey, + pub data: Option>, +} + +/// Instruction builder for `WriteGroupExternalPluginAdapterDataV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` buffer +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper +#[derive(Default)] +pub struct WriteGroupExternalPluginAdapterDataV1Builder { + group: Option, + payer: Option, + authority: Option, + buffer: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + data: Option>, + __remaining_accounts: Vec, +} + +impl WriteGroupExternalPluginAdapterDataV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: solana_program::pubkey::Pubkey) -> &mut Self { + self.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The data authority or update authority of the group + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account]` + /// Buffer account containing data + #[inline(always)] + pub fn buffer(&mut self, buffer: Option) -> &mut Self { + self.buffer = buffer; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.data = Some(data); + 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 = WriteGroupExternalPluginAdapterDataV1 { + group: self.group.expect("group is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + buffer: self.buffer, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = WriteGroupExternalPluginAdapterDataV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + data: self.data.clone(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `write_group_external_plugin_adapter_data_v1` CPI accounts. +pub struct WriteGroupExternalPluginAdapterDataV1CpiAccounts<'a, 'b> { + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The data authority or update authority of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Buffer account containing data + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `write_group_external_plugin_adapter_data_v1` CPI instruction. +pub struct WriteGroupExternalPluginAdapterDataV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the group + pub group: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The data authority or update authority of the group + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Buffer account containing data + pub buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: WriteGroupExternalPluginAdapterDataV1InstructionArgs, +} + +impl<'a, 'b> WriteGroupExternalPluginAdapterDataV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: WriteGroupExternalPluginAdapterDataV1CpiAccounts<'a, 'b>, + args: WriteGroupExternalPluginAdapterDataV1InstructionArgs, + ) -> Self { + Self { + __program: program, + group: accounts.group, + payer: accounts.payer, + authority: accounts.authority, + buffer: accounts.buffer, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[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(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.group.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(buffer) = self.buffer { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *buffer.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + 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 mut data = WriteGroupExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.group.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + if let Some(buffer) = self.buffer { + account_infos.push(buffer.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.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) + } + } +} + +/// Instruction builder for `WriteGroupExternalPluginAdapterDataV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` group +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` buffer +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper +pub struct WriteGroupExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> WriteGroupExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(WriteGroupExternalPluginAdapterDataV1CpiBuilderInstruction { + __program: program, + group: None, + payer: None, + authority: None, + buffer: None, + system_program: None, + log_wrapper: None, + key: None, + data: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the group + #[inline(always)] + pub fn group(&mut self, group: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.group = Some(group); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The data authority or update authority of the group + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// `[optional account]` + /// Buffer account containing data + #[inline(always)] + pub fn buffer( + &mut self, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.buffer = buffer; + self + } + /// The 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 + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.instruction.data = Some(data); + 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 args = WriteGroupExternalPluginAdapterDataV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + data: self.instruction.data.clone(), + }; + let instruction = WriteGroupExternalPluginAdapterDataV1Cpi { + __program: self.instruction.__program, + + group: self.instruction.group.expect("group is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + buffer: self.instruction.buffer, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct WriteGroupExternalPluginAdapterDataV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + group: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + buffer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + data: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/types/groups.rs b/clients/rust/src/generated/types/groups.rs new file mode 100644 index 00000000..c952d17f --- /dev/null +++ b/clients/rust/src/generated/types/groups.rs @@ -0,0 +1,24 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Groups { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::>") + )] + pub groups: Vec, +} diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index b6e4313d..f529e0a5 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -22,4 +22,5 @@ pub enum Key { PluginHeaderV1, PluginRegistryV1, CollectionV1, + GroupV1, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index eda49fbf..eb948260 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -34,6 +34,7 @@ pub(crate) mod r#external_validation_result; pub(crate) mod r#extra_account; pub(crate) mod r#freeze_delegate; pub(crate) mod r#freeze_execute; +pub(crate) mod r#groups; pub(crate) mod r#hashable_plugin_schema; pub(crate) mod r#hashed_asset_schema; pub(crate) mod r#hookable_lifecycle_event; @@ -63,6 +64,8 @@ pub(crate) mod r#plugin_authority; pub(crate) mod r#plugin_authority_pair; pub(crate) mod r#plugin_type; pub(crate) mod r#registry_record; +pub(crate) mod r#relationship_entry; +pub(crate) mod r#relationship_kind; pub(crate) mod r#royalties; pub(crate) mod r#rule_set; pub(crate) mod r#seed; @@ -104,6 +107,7 @@ pub use self::r#external_validation_result::*; pub use self::r#extra_account::*; pub use self::r#freeze_delegate::*; pub use self::r#freeze_execute::*; +pub use self::r#groups::*; pub use self::r#hashable_plugin_schema::*; pub use self::r#hashed_asset_schema::*; pub use self::r#hookable_lifecycle_event::*; @@ -133,6 +137,8 @@ pub use self::r#plugin_authority::*; pub use self::r#plugin_authority_pair::*; pub use self::r#plugin_type::*; pub use self::r#registry_record::*; +pub use self::r#relationship_entry::*; +pub use self::r#relationship_kind::*; pub use self::r#royalties::*; pub use self::r#rule_set::*; pub use self::r#seed::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 7c8bd896..37002234 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -13,6 +13,7 @@ use crate::generated::types::BurnDelegate; use crate::generated::types::Edition; use crate::generated::types::FreezeDelegate; use crate::generated::types::FreezeExecute; +use crate::generated::types::Groups; use crate::generated::types::ImmutableMetadata; use crate::generated::types::MasterEdition; use crate::generated::types::PermanentBurnDelegate; @@ -51,4 +52,5 @@ pub enum Plugin { BubblegumV2(BubblegumV2), FreezeExecute(FreezeExecute), PermanentFreezeExecute(PermanentFreezeExecute), + Groups(Groups), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index f44c7c33..8950a2c5 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -34,4 +34,5 @@ pub enum PluginType { BubblegumV2, FreezeExecute, PermanentFreezeExecute, + Groups, } diff --git a/clients/rust/src/generated/types/relationship_entry.rs b/clients/rust/src/generated/types/relationship_entry.rs new file mode 100644 index 00000000..81948b35 --- /dev/null +++ b/clients/rust/src/generated/types/relationship_entry.rs @@ -0,0 +1,26 @@ +//! 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 crate::generated::types::RelationshipKind; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RelationshipEntry { + pub kind: RelationshipKind, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub key: Pubkey, +} diff --git a/clients/rust/src/generated/types/relationship_kind.rs b/clients/rust/src/generated/types/relationship_kind.rs new file mode 100644 index 00000000..fad38a80 --- /dev/null +++ b/clients/rust/src/generated/types/relationship_kind.rs @@ -0,0 +1,23 @@ +//! 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] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum RelationshipKind { + Collection, + ChildGroup, + ParentGroup, + Asset, +} diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 3fb95d56..7706ef4b 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -10,10 +10,10 @@ use crate::{ types::{ AddBlocker, AppData, Attributes, Autograph, BubblegumV2, BurnDelegate, DataSection, Edition, ExternalCheckResult, ExternalPluginAdapter, ExternalPluginAdapterKey, - FreezeDelegate, FreezeExecute, ImmutableMetadata, Key, LifecycleHook, LinkedAppData, - LinkedLifecycleHook, MasterEdition, Oracle, PermanentBurnDelegate, PermanentFreezeDelegate, - PermanentFreezeExecute, PermanentTransferDelegate, PluginAuthority, Royalties, - TransferDelegate, UpdateDelegate, VerifiedCreators, + FreezeDelegate, FreezeExecute, Groups, ImmutableMetadata, Key, LifecycleHook, + LinkedAppData, LinkedLifecycleHook, MasterEdition, Oracle, PermanentBurnDelegate, + PermanentFreezeDelegate, PermanentFreezeExecute, PermanentTransferDelegate, + PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, VerifiedCreators, }, }; @@ -179,6 +179,12 @@ pub struct PermanentFreezeExecutePlugin { pub permanent_freeze_execute: PermanentFreezeExecute, } +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct GroupsPlugin { + pub base: BasePlugin, + pub groups: Groups, +} + #[derive(Debug, Default)] pub struct PluginsList { pub royalties: Option, @@ -199,6 +205,7 @@ pub struct PluginsList { pub bubblegum_v2: Option, pub freeze_execute: Option, pub permanent_freeze_execute: Option, + pub groups: Option, } #[derive(Debug, Default)] diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index 75aa03a8..0b1d36c0 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -50,6 +50,7 @@ impl From<&Plugin> for PluginType { Plugin::BubblegumV2(_) => PluginType::BubblegumV2, Plugin::FreezeExecute(_) => PluginType::FreezeExecute, Plugin::PermanentFreezeExecute(_) => PluginType::PermanentFreezeExecute, + Plugin::Groups(_) => PluginType::Groups, } } } diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 69693185..2abb59db 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -15,11 +15,11 @@ use crate::{ AddBlockerPlugin, AppDataWithData, AttributesPlugin, AutographPlugin, BaseAuthority, BasePlugin, BubblegumV2Plugin, BurnDelegatePlugin, DataBlob, DataSectionWithData, EditionPlugin, ExternalPluginAdaptersList, ExternalRegistryRecordSafe, FreezeDelegatePlugin, - FreezeExecutePlugin, ImmutableMetadataPlugin, LifecycleHookWithData, MasterEditionPlugin, - PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, PermanentFreezeExecutePlugin, - PermanentTransferDelegatePlugin, PluginRegistryV1Safe, PluginsList, RegistryRecordSafe, - RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, UpdateDelegatePlugin, - VerifiedCreatorsPlugin, + FreezeExecutePlugin, GroupsPlugin, ImmutableMetadataPlugin, LifecycleHookWithData, + MasterEditionPlugin, PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, + PermanentFreezeExecutePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe, + PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, + UpdateDelegatePlugin, VerifiedCreatorsPlugin, }; /// Fetch the plugin from the registry. @@ -349,6 +349,7 @@ pub(crate) fn registry_records_to_plugin_list( Plugin::BubblegumV2(bubblegum_v2) => { acc.bubblegum_v2 = Some(BubblegumV2Plugin { base, bubblegum_v2 }) } + Plugin::Groups(groups) => acc.groups = Some(GroupsPlugin { base, groups }), Plugin::FreezeExecute(freeze_execute) => { acc.freeze_execute = Some(FreezeExecutePlugin { base, diff --git a/configs/kinobi.cjs b/configs/kinobi.cjs index 89571440..d036f7c1 100755 --- a/configs/kinobi.cjs +++ b/configs/kinobi.cjs @@ -10,336 +10,356 @@ const kinobi = k.createFromIdls([path.join(idlDir, "mpl_core.json")]); // Update programs. kinobi.update( - k.updateProgramsVisitor({ - mplCoreProgram: { name: "mplCore" }, - }) + k.updateProgramsVisitor({ + mplCoreProgram: { name: "mplCore" }, + }) ); // Append empty signer accounts. kinobi.update( - new k.bottomUpTransformerVisitor([{ - select: ['[programNode]', node => 'name' in node && node.name === "mplCore"], - transform: (node) => { - return k.programNode({ - ...node, - accounts: [ - ...node.accounts, - k.accountNode({ - name: "assetSigner", - size: 0, - data: k.structTypeNode([ - k.structFieldTypeNode({ - name: "data", - type: k.bytesTypeNode(k.remainderSizeNode()), - }) - ]), - }), - ], - }); - }, - }]) + new k.bottomUpTransformerVisitor([ + { + select: [ + "[programNode]", + (node) => "name" in node && node.name === "mplCore", + ], + transform: (node) => { + return k.programNode({ + ...node, + accounts: [ + ...node.accounts, + k.accountNode({ + name: "assetSigner", + size: 0, + data: k.structTypeNode([ + k.structFieldTypeNode({ + name: "data", + type: k.bytesTypeNode( + k.remainderSizeNode() + ), + }), + ]), + }), + ], + }); + }, + }, + ]) ); - kinobi.update( - new k.updateAccountsVisitor({ - assetV1: { - name: "baseAssetV1", - }, - collectionV1: { - name: "baseCollectionV1", - }, - assetSigner: { - size: 0, - seeds: [ - k.constantPdaSeedNodeFromString("mpl-core-execute"), - k.variablePdaSeedNode( - "asset", - k.publicKeyTypeNode(), - "The address of the asset account" - ), - ], - }, - }) + new k.updateAccountsVisitor({ + assetV1: { + name: "baseAssetV1", + }, + collectionV1: { + name: "baseCollectionV1", + }, + assetSigner: { + size: 0, + seeds: [ + k.constantPdaSeedNodeFromString("mpl-core-execute"), + k.variablePdaSeedNode( + "asset", + k.publicKeyTypeNode(), + "The address of the asset account" + ), + ], + }, + }) ); -kinobi.update(new k.updateDefinedTypesVisitor({ - authority: { - name: "pluginAuthority" - } -})) +kinobi.update( + new k.updateDefinedTypesVisitor({ + authority: { + name: "pluginAuthority", + }, + crate: { + name: "relationshipEntry", + }, + }) +); // Update instructions with default values kinobi.update( - k.updateInstructionsVisitor({ - // create: { - // bytesCreatedOnChain: k.bytesFromAccount("assetAccount"), - // }, - transferV1: { - arguments: { - compressionProof: { - defaultValue: k.noneValueNode() - } - } - }, - addPluginV1: { - arguments: { - initAuthority: { - defaultValue: k.noneValueNode() - } - } - }, - addCollectionPluginV1: { - arguments: { - initAuthority: { - defaultValue: k.noneValueNode() - } - } - }, - burnV1: { - arguments: { - compressionProof: { - defaultValue: k.noneValueNode() - } - } - }, - createV1: { - arguments: { - plugins: { - defaultValue: k.arrayValueNode([]) - }, - dataState: { - defaultValue: k.enumValueNode('DataState', 'AccountState') - } - } - }, - createV2: { - arguments: { - plugins: { - defaultValue: k.arrayValueNode([]) - }, - externalPluginAdapters: { - defaultValue: k.arrayValueNode([]) - }, - dataState: { - defaultValue: k.enumValueNode('DataState', 'AccountState') - } - } - }, - createCollectionV1: { - arguments: { - plugins: { - defaultValue: k.noneValueNode() - } - } - }, - createCollectionV2: { - arguments: { - plugins: { - defaultValue: k.noneValueNode() - }, - externalPluginAdapters: { - defaultValue: k.arrayValueNode([]) - }, - } - }, - collect: { - accounts: { - recipient1: { - defaultValue: k.publicKeyValueNode("8AT6o8Qk5T9QnZvPThMrF9bcCQLTGkyGvVZZzHgCw11v") - }, - recipient2: { - defaultValue: k.publicKeyValueNode("MmHsqX4LxTfifxoH8BVRLUKrwDn1LPCac6YcCZTHhwt") - } - } - }, - updateV1: { - arguments: { - newUpdateAuthority: { - defaultValue: k.noneValueNode() - }, - newName: { - defaultValue: k.noneValueNode() - }, - newUri: { - defaultValue: k.noneValueNode() - }, - } - }, - updateV2: { - arguments: { - newUpdateAuthority: { - defaultValue: k.noneValueNode() - }, - newName: { - defaultValue: k.noneValueNode() - }, - newUri: { - defaultValue: k.noneValueNode() - }, - } - }, - updateCollectionV1: { - arguments: { - newName: { - defaultValue: k.noneValueNode() - }, - newUri: { - defaultValue: k.noneValueNode() - }, - } - }, - executeV1: { - accounts: { - assetSigner: { - defaultValue: k.pdaValueNode("assetSigner") - } - } - }, - }) + k.updateInstructionsVisitor({ + // create: { + // bytesCreatedOnChain: k.bytesFromAccount("assetAccount"), + // }, + transferV1: { + arguments: { + compressionProof: { + defaultValue: k.noneValueNode(), + }, + }, + }, + addPluginV1: { + arguments: { + initAuthority: { + defaultValue: k.noneValueNode(), + }, + }, + }, + addCollectionPluginV1: { + arguments: { + initAuthority: { + defaultValue: k.noneValueNode(), + }, + }, + }, + burnV1: { + arguments: { + compressionProof: { + defaultValue: k.noneValueNode(), + }, + }, + }, + createV1: { + arguments: { + plugins: { + defaultValue: k.arrayValueNode([]), + }, + dataState: { + defaultValue: k.enumValueNode("DataState", "AccountState"), + }, + }, + }, + createV2: { + arguments: { + plugins: { + defaultValue: k.arrayValueNode([]), + }, + externalPluginAdapters: { + defaultValue: k.arrayValueNode([]), + }, + dataState: { + defaultValue: k.enumValueNode("DataState", "AccountState"), + }, + }, + }, + createCollectionV1: { + arguments: { + plugins: { + defaultValue: k.noneValueNode(), + }, + }, + }, + createCollectionV2: { + arguments: { + plugins: { + defaultValue: k.noneValueNode(), + }, + externalPluginAdapters: { + defaultValue: k.arrayValueNode([]), + }, + }, + }, + collect: { + accounts: { + recipient1: { + defaultValue: k.publicKeyValueNode( + "8AT6o8Qk5T9QnZvPThMrF9bcCQLTGkyGvVZZzHgCw11v" + ), + }, + recipient2: { + defaultValue: k.publicKeyValueNode( + "MmHsqX4LxTfifxoH8BVRLUKrwDn1LPCac6YcCZTHhwt" + ), + }, + }, + }, + updateV1: { + arguments: { + newUpdateAuthority: { + defaultValue: k.noneValueNode(), + }, + newName: { + defaultValue: k.noneValueNode(), + }, + newUri: { + defaultValue: k.noneValueNode(), + }, + }, + }, + updateV2: { + arguments: { + newUpdateAuthority: { + defaultValue: k.noneValueNode(), + }, + newName: { + defaultValue: k.noneValueNode(), + }, + newUri: { + defaultValue: k.noneValueNode(), + }, + }, + }, + updateCollectionV1: { + arguments: { + newName: { + defaultValue: k.noneValueNode(), + }, + newUri: { + defaultValue: k.noneValueNode(), + }, + }, + }, + executeV1: { + accounts: { + assetSigner: { + defaultValue: k.pdaValueNode("assetSigner"), + }, + }, + }, + }) ); // Set ShankAccount discriminator. const key = (name) => ({ field: "key", value: k.enumValueNode("Key", name) }); kinobi.update( - k.setAccountDiscriminatorFromFieldVisitor({ - assetV1: key("AssetV1"), - collectionV1: key("CollectionV1"), - }) + k.setAccountDiscriminatorFromFieldVisitor({ + assetV1: key("AssetV1"), + collectionV1: key("CollectionV1"), + }) ); // Render Rust. const crateDir = path.join(clientDir, "rust"); const rustDir = path.join(clientDir, "rust", "src", "generated"); kinobi.accept( - k.renderRustVisitor(rustDir, { - formatCode: true, - crateFolder: crateDir, - }) + k.renderRustVisitor(rustDir, { + formatCode: true, + crateFolder: crateDir, + }) ); - // rewrite the account names for custom account data kinobi.update( - new k.updateAccountsVisitor({ - baseAssetV1: { - name: "assetV1", - }, - baseCollectionV1: { - name: "collectionV1", - } - }) + new k.updateAccountsVisitor({ + baseAssetV1: { + name: "assetV1", + }, + baseCollectionV1: { + name: "collectionV1", + }, + }) ); kinobi.update( - new k.updateDefinedTypesVisitor({ - ruleSet: { - name: "baseRuleSet" - }, - royalties: { - name: "baseRoyalties" - }, - pluginAuthority: { - name: "basePluginAuthority" - }, - updateAuthority: { - name: "baseUpdateAuthority" - }, - seed: { - name: "baseSeed" - }, - extraAccount: { - name: "baseExtraAccount" - }, - externalPluginAdapterKey: { - name: "baseExternalPluginAdapterKey" - }, - linkedDataKey: { - name: 'baseLinkedDataKey' - }, - externalPluginAdapterInitInfo: { - name: "baseExternalPluginAdapterInitInfo" - }, - externalPluginAdapterUpdateInfo: { - name: "baseExternalPluginAdapterUpdateInfo" - }, - oracle: { - name: "baseOracle" - }, - oracleInitInfo: { - name: "baseOracleInitInfo" - }, - oracleUpdateInfo: { - name: "baseOracleUpdateInfo" - }, - lifecycleHook: { - name: "baseLifecycleHook" - }, - lifecycleHookInitInfo: { - name: "baseLifecycleHookInitInfo" - }, - lifecycleHookUpdateInfo: { - name: "baseLifecycleHookUpdateInfo" - }, - linkedLifecycleHook: { - name: "baseLinkedLifecycleHook" - }, - linkedLifecycleHookInitInfo: { - name: "baseLinkedLifecycleHookInitInfo" - }, - linkedLifecycleHookUpdateInfo: { - name: "baseLinkedLifecycleHookUpdateInfo" - }, - appData: { - name: "baseAppData" - }, - appDataInitInfo: { - name: "baseAppDataInitInfo" - }, - appDataUpdateInfo: { - name: "baseAppDataUpdateInfo" - }, - linkedAppData: { - name: "baseLinkedAppData" - }, - linkedAppDataInitInfo: { - name: "baseLinkedAppDataInitInfo" - }, - linkedAppDataUpdateInfo: { - name: "baseLinkedAppDataUpdateInfo" - }, - dataSection: { - name: "baseDataSection" - }, - dataSectionInitInfo: { - name: "baseDataSectionInitInfo" - }, - dataSectionUpdateInfo: { - name: "baseDataSectionUpdateInfo" - }, - validationResultsOffset: { - name: "baseValidationResultsOffset" - }, - masterEdition: { - name: "baseMasterEdition" - } - }) -) + new k.updateDefinedTypesVisitor({ + ruleSet: { + name: "baseRuleSet", + }, + royalties: { + name: "baseRoyalties", + }, + pluginAuthority: { + name: "basePluginAuthority", + }, + updateAuthority: { + name: "baseUpdateAuthority", + }, + seed: { + name: "baseSeed", + }, + extraAccount: { + name: "baseExtraAccount", + }, + externalPluginAdapterKey: { + name: "baseExternalPluginAdapterKey", + }, + linkedDataKey: { + name: "baseLinkedDataKey", + }, + externalPluginAdapterInitInfo: { + name: "baseExternalPluginAdapterInitInfo", + }, + externalPluginAdapterUpdateInfo: { + name: "baseExternalPluginAdapterUpdateInfo", + }, + oracle: { + name: "baseOracle", + }, + oracleInitInfo: { + name: "baseOracleInitInfo", + }, + oracleUpdateInfo: { + name: "baseOracleUpdateInfo", + }, + lifecycleHook: { + name: "baseLifecycleHook", + }, + lifecycleHookInitInfo: { + name: "baseLifecycleHookInitInfo", + }, + lifecycleHookUpdateInfo: { + name: "baseLifecycleHookUpdateInfo", + }, + linkedLifecycleHook: { + name: "baseLinkedLifecycleHook", + }, + linkedLifecycleHookInitInfo: { + name: "baseLinkedLifecycleHookInitInfo", + }, + linkedLifecycleHookUpdateInfo: { + name: "baseLinkedLifecycleHookUpdateInfo", + }, + appData: { + name: "baseAppData", + }, + appDataInitInfo: { + name: "baseAppDataInitInfo", + }, + appDataUpdateInfo: { + name: "baseAppDataUpdateInfo", + }, + linkedAppData: { + name: "baseLinkedAppData", + }, + linkedAppDataInitInfo: { + name: "baseLinkedAppDataInitInfo", + }, + linkedAppDataUpdateInfo: { + name: "baseLinkedAppDataUpdateInfo", + }, + dataSection: { + name: "baseDataSection", + }, + dataSectionInitInfo: { + name: "baseDataSectionInitInfo", + }, + dataSectionUpdateInfo: { + name: "baseDataSectionUpdateInfo", + }, + validationResultsOffset: { + name: "baseValidationResultsOffset", + }, + masterEdition: { + name: "baseMasterEdition", + }, + }) +); // Render JavaScript. const jsDir = path.join(clientDir, "js", "src", "generated"); const prettier = require(path.join(clientDir, "js", ".prettierrc.json")); -kinobi.accept(k.renderJavaScriptVisitor(jsDir, { - prettier, - internalNodes: [], - customAccountData: [{ - name: "assetV1", - extract: true, - }, { - name: "collectionV1", - extract: true, - }, { - name: "pluginRegistryV1", - extract: true, - }], -})); \ No newline at end of file +kinobi.accept( + k.renderJavaScriptVisitor(jsDir, { + prettier, + internalNodes: [], + customAccountData: [ + { + name: "assetV1", + extract: true, + }, + { + name: "collectionV1", + extract: true, + }, + { + name: "pluginRegistryV1", + extract: true, + }, + ], + }) +); diff --git a/idls/mpl_core.json b/idls/mpl_core.json index 7d3ff018..45d9d72b 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -2114,6 +2114,938 @@ "type": "u8", "value": 32 } + }, + { + "name": "AddCollectionsToGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group/collections" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "addCollectionsToGroupV1Args", + "type": { + "defined": "AddCollectionsToGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 33 + } + }, + { + "name": "RemoveCollectionsFromGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group/collections" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "removeCollectionsFromGroupV1Args", + "type": { + "defined": "RemoveCollectionsFromGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 34 + } + }, + { + "name": "AddAssetsToGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group/assets" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "addAssetsToGroupV1Args", + "type": { + "defined": "AddAssetsToGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 35 + } + }, + { + "name": "RemoveAssetsFromGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group/assets" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "removeAssetsFromGroupV1Args", + "type": { + "defined": "RemoveAssetsFromGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 36 + } + }, + { + "name": "AddGroupsToGroupV1", + "accounts": [ + { + "name": "parentGroup", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the parent group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the groups" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "addGroupsToGroupV1Args", + "type": { + "defined": "AddGroupsToGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 37 + } + }, + { + "name": "RemoveGroupsFromGroupV1", + "accounts": [ + { + "name": "parentGroup", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the parent group to modify" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the groups" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "removeGroupsFromGroupV1Args", + "type": { + "defined": "RemoveGroupsFromGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 38 + } + }, + { + "name": "AddGroupPluginV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "addGroupPluginV1Args", + "type": { + "defined": "AddGroupPluginV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 39 + } + }, + { + "name": "RemoveGroupPluginV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "removeGroupPluginV1Args", + "type": { + "defined": "RemoveGroupPluginV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 40 + } + }, + { + "name": "UpdateGroupPluginV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "updateGroupPluginV1Args", + "type": { + "defined": "UpdateGroupPluginV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 41 + } + }, + { + "name": "ApproveGroupPluginAuthorityV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "approveGroupPluginAuthorityV1Args", + "type": { + "defined": "ApproveGroupPluginAuthorityV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 42 + } + }, + { + "name": "RevokeGroupPluginAuthorityV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "revokeGroupPluginAuthorityV1Args", + "type": { + "defined": "RevokeGroupPluginAuthorityV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 43 + } + }, + { + "name": "AddGroupExternalPluginAdapterV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "addGroupExternalPluginAdapterV1Args", + "type": { + "defined": "AddGroupExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 44 + } + }, + { + "name": "RemoveGroupExternalPluginAdapterV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or delegate of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "removeGroupExternalPluginAdapterV1Args", + "type": { + "defined": "RemoveGroupExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 45 + } + }, + { + "name": "WriteGroupExternalPluginAdapterDataV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The data authority or update authority of the group" + ] + }, + { + "name": "buffer", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Buffer account containing data" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "writeGroupExternalPluginAdapterDataV1Args", + "type": { + "defined": "WriteGroupExternalPluginAdapterDataV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 46 + } + }, + { + "name": "CreateGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": true, + "docs": [ + "The address of the new group" + ] + }, + { + "name": "updateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The authority of the new group" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "createGroupV1Args", + "type": { + "defined": "CreateGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 47 + } + }, + { + "name": "CloseGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to close" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account receiving reclaimed lamports" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or update delegate of the group" + ] + } + ], + "args": [ + { + "name": "closeGroupV1Args", + "type": { + "defined": "CloseGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 48 + } + }, + { + "name": "UpdateGroupV1", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the group to update" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The update authority or update delegate of the group" + ] + }, + { + "name": "newUpdateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The new update authority of the group" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "updateGroupV1Args", + "type": { + "defined": "UpdateGroupV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 49 + } } ], "accounts": [ @@ -2237,6 +3169,56 @@ ] } }, + { + "name": "GroupV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "updateAuthority", + "type": "publicKey" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "collections", + "type": { + "vec": "publicKey" + } + }, + { + "name": "groups", + "type": { + "vec": "publicKey" + } + }, + { + "name": "parentGroups", + "type": { + "vec": "publicKey" + } + }, + { + "name": "assets", + "type": { + "vec": "publicKey" + } + } + ] + } + }, { "name": "HashedAssetV1", "type": { @@ -2900,6 +3882,20 @@ ] } }, + { + "name": "Groups", + "type": { + "kind": "struct", + "fields": [ + { + "name": "groups", + "type": { + "vec": "publicKey" + } + } + ] + } + }, { "name": "ImmutableMetadata", "type": { @@ -3231,14 +4227,42 @@ { "name": "dataLen", "type": { - "option": "u64" + "option": "u64" + } + } + ] + } + }, + { + "name": "AddAssetsToGroupV1Args", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "AddCollectionsToGroupV1Args", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "AddExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initInfo", + "type": { + "defined": "ExternalPluginAdapterInitInfo" } } ] } }, { - "name": "AddExternalPluginAdapterV1Args", + "name": "AddCollectionExternalPluginAdapterV1Args", "type": { "kind": "struct", "fields": [ @@ -3252,7 +4276,7 @@ } }, { - "name": "AddCollectionExternalPluginAdapterV1Args", + "name": "AddGroupExternalPluginAdapterV1Args", "type": { "kind": "struct", "fields": [ @@ -3265,6 +4289,42 @@ ] } }, + { + "name": "AddGroupPluginV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "plugin", + "type": { + "defined": "Plugin" + } + }, + { + "name": "initAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + } + ] + } + }, + { + "name": "AddGroupsToGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "groups", + "type": { + "vec": "publicKey" + } + } + ] + } + }, { "name": "AddPluginV1Args", "type": { @@ -3309,6 +4369,26 @@ ] } }, + { + "name": "ApproveGroupPluginAuthorityV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "pluginType", + "type": { + "defined": "PluginType" + } + }, + { + "name": "newAuthority", + "type": { + "defined": "Authority" + } + } + ] + } + }, { "name": "ApprovePluginAuthorityV1Args", "type": { @@ -3381,6 +4461,13 @@ ] } }, + { + "name": "CloseGroupV1Args", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "CompressV1Args", "type": { @@ -3524,6 +4611,30 @@ ] } }, + { + "name": "CreateGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "relationships", + "type": { + "vec": { + "defined": "crate" + } + } + } + ] + } + }, { "name": "DecompressV1Args", "type": { @@ -3550,6 +4661,34 @@ ] } }, + { + "name": "RemoveAssetsFromGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "assets", + "type": { + "vec": "publicKey" + } + } + ] + } + }, + { + "name": "RemoveCollectionsFromGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "collections", + "type": { + "vec": "publicKey" + } + } + ] + } + }, { "name": "RemoveExternalPluginAdapterV1Args", "type": { @@ -3578,6 +4717,48 @@ ] } }, + { + "name": "RemoveGroupExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + } + ] + } + }, + { + "name": "RemoveGroupPluginV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "pluginType", + "type": { + "defined": "PluginType" + } + } + ] + } + }, + { + "name": "RemoveGroupsFromGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "groups", + "type": { + "vec": "publicKey" + } + } + ] + } + }, { "name": "RemovePluginV1Args", "type": { @@ -3606,6 +4787,20 @@ ] } }, + { + "name": "RevokeGroupPluginAuthorityV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "pluginType", + "type": { + "defined": "PluginType" + } + } + ] + } + }, { "name": "RevokePluginAuthorityV1Args", "type": { @@ -3784,6 +4979,40 @@ ] } }, + { + "name": "UpdateGroupV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "newName", + "type": { + "option": "string" + } + }, + { + "name": "newUri", + "type": { + "option": "string" + } + } + ] + } + }, + { + "name": "UpdateGroupPluginV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "plugin", + "type": { + "defined": "Plugin" + } + } + ] + } + }, { "name": "UpdatePluginV1Args", "type": { @@ -3852,6 +5081,26 @@ ] } }, + { + "name": "WriteGroupExternalPluginAdapterDataV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + }, + { + "name": "data", + "type": { + "option": "bytes" + } + } + ] + } + }, { "name": "CompressionProof", "type": { @@ -3890,6 +5139,24 @@ ] } }, + { + "name": "RelationshipEntry", + "type": { + "kind": "struct", + "fields": [ + { + "name": "kind", + "type": { + "defined": "RelationshipKind" + } + }, + { + "name": "key", + "type": "publicKey" + } + ] + } + }, { "name": "HashablePluginSchema", "type": { @@ -4090,6 +5357,14 @@ "defined": "PermanentFreezeExecute" } ] + }, + { + "name": "Groups", + "fields": [ + { + "defined": "Groups" + } + ] } ] } @@ -4152,6 +5427,9 @@ }, { "name": "PermanentFreezeExecute" + }, + { + "name": "Groups" } ] } @@ -4803,6 +6081,29 @@ }, { "name": "CollectionV1" + }, + { + "name": "GroupV1" + } + ] + } + }, + { + "name": "RelationshipKind", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Collection" + }, + { + "name": "ChildGroup" + }, + { + "name": "ParentGroup" + }, + { + "name": "Asset" } ] } @@ -5086,6 +6387,16 @@ "code": 50, "name": "BlockedByBubblegumV2", "msg": "Bubblegum V2 Plugin limits other plugins" + }, + { + "code": 51, + "name": "GroupMustBeEmpty", + "msg": "Group must be empty to be closed" + }, + { + "code": 52, + "name": "DuplicateEntry", + "msg": "Duplicate entry provided when adding relationships to a group" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 78b89e2f..81a36e46 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -212,6 +212,14 @@ pub enum MplCoreError { /// 50 - Bubblegum V2 Plugin limits other plugins #[error("Bubblegum V2 Plugin limits other plugins")] BlockedByBubblegumV2, + + /// 51 - Group must be empty to be closed + #[error("Group must be empty to be closed")] + GroupMustBeEmpty, + + /// 52 - Duplicate entry provided when adding relationships to a group + #[error("Duplicate entry provided when adding relationships to a group")] + DuplicateEntry, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/instruction.rs b/programs/mpl-core/src/instruction.rs index 15e976d4..f589cc6e 100644 --- a/programs/mpl-core/src/instruction.rs +++ b/programs/mpl-core/src/instruction.rs @@ -3,16 +3,22 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::{ShankContext, ShankInstruction}; use crate::processor::{ - AddCollectionExternalPluginAdapterV1Args, AddCollectionPluginV1Args, - AddExternalPluginAdapterV1Args, AddPluginV1Args, ApproveCollectionPluginAuthorityV1Args, - ApprovePluginAuthorityV1Args, BurnCollectionV1Args, BurnV1Args, CompressV1Args, - CreateCollectionV1Args, CreateCollectionV2Args, CreateV1Args, CreateV2Args, DecompressV1Args, - ExecuteV1Args, RemoveCollectionExternalPluginAdapterV1Args, RemoveCollectionPluginV1Args, - RemoveExternalPluginAdapterV1Args, RemovePluginV1Args, RevokeCollectionPluginAuthorityV1Args, + AddAssetsToGroupV1Args, AddCollectionExternalPluginAdapterV1Args, AddCollectionPluginV1Args, + AddCollectionsToGroupV1Args, AddExternalPluginAdapterV1Args, + AddGroupExternalPluginAdapterV1Args, AddGroupPluginV1Args, AddGroupsToGroupV1Args, + AddPluginV1Args, ApproveCollectionPluginAuthorityV1Args, ApproveGroupPluginAuthorityV1Args, + ApprovePluginAuthorityV1Args, BurnCollectionV1Args, BurnV1Args, CloseGroupV1Args, + CompressV1Args, CreateCollectionV1Args, CreateCollectionV2Args, CreateGroupV1Args, + CreateV1Args, CreateV2Args, DecompressV1Args, ExecuteV1Args, RemoveAssetsFromGroupV1Args, + RemoveCollectionExternalPluginAdapterV1Args, RemoveCollectionPluginV1Args, + RemoveCollectionsFromGroupV1Args, RemoveExternalPluginAdapterV1Args, + RemoveGroupExternalPluginAdapterV1Args, RemoveGroupPluginV1Args, RemoveGroupsFromGroupV1Args, + RemovePluginV1Args, RevokeCollectionPluginAuthorityV1Args, RevokeGroupPluginAuthorityV1Args, RevokePluginAuthorityV1Args, TransferV1Args, UpdateCollectionExternalPluginAdapterV1Args, UpdateCollectionInfoV1Args, UpdateCollectionPluginV1Args, UpdateCollectionV1Args, - UpdateExternalPluginAdapterV1Args, UpdatePluginV1Args, UpdateV1Args, UpdateV2Args, - WriteCollectionExternalPluginAdapterDataV1Args, WriteExternalPluginAdapterDataV1Args, + UpdateExternalPluginAdapterV1Args, UpdateGroupPluginV1Args, UpdateGroupV1Args, + UpdatePluginV1Args, UpdateV1Args, UpdateV2Args, WriteCollectionExternalPluginAdapterDataV1Args, + WriteExternalPluginAdapterDataV1Args, WriteGroupExternalPluginAdapterDataV1Args, }; /// Instructions supported by the mpl-core program. @@ -307,4 +313,132 @@ pub(crate) enum MplAssetInstruction { #[account(0, writable, name="collection", desc = "The address of the asset")] #[account(1, signer, name="bubblegum_signer", desc = "Bubblegum PDA signer")] UpdateCollectionInfoV1(UpdateCollectionInfoV1Args), + + /// Add collections to a group. + #[account(0, writable, name="group", desc = "The address of the group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group/collections")] + #[account(3, name="system_program", desc = "The system program")] + AddCollectionsToGroupV1(AddCollectionsToGroupV1Args), + + /// Remove collections from a group. + #[account(0, writable, name="group", desc = "The address of the group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group/collections")] + #[account(3, name="system_program", desc = "The system program")] + RemoveCollectionsFromGroupV1(RemoveCollectionsFromGroupV1Args), + + /// Add assets to a group. + #[account(0, writable, name="group", desc = "The address of the group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group/assets")] + #[account(3, name="system_program", desc = "The system program")] + AddAssetsToGroupV1(AddAssetsToGroupV1Args), + + /// Remove assets from a group. + #[account(0, writable, name="group", desc = "The address of the group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group/assets")] + #[account(3, name="system_program", desc = "The system program")] + RemoveAssetsFromGroupV1(RemoveAssetsFromGroupV1Args), + + /// Add groups to a parent group. + #[account(0, writable, name="parent_group", desc = "The address of the parent group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the groups")] + #[account(3, name="system_program", desc = "The system program")] + AddGroupsToGroupV1(AddGroupsToGroupV1Args), + + /// Remove groups from a parent group. + #[account(0, writable, name="parent_group", desc = "The address of the parent group to modify")] + #[account(1, writable, signer, name="payer", desc = "The account paying for storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the groups")] + #[account(3, name="system_program", desc = "The system program")] + RemoveGroupsFromGroupV1(RemoveGroupsFromGroupV1Args), + + /// Add a plugin to a Group account. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + AddGroupPluginV1(AddGroupPluginV1Args), + + /// Remove a plugin from a Group account. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + RemoveGroupPluginV1(RemoveGroupPluginV1Args), + + /// Update a plugin of a Group account. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + UpdateGroupPluginV1(UpdateGroupPluginV1Args), + + /// Approve an authority to a Group plugin. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + ApproveGroupPluginAuthorityV1(ApproveGroupPluginAuthorityV1Args), + + /// Revoke an authority from a Group plugin. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + RevokeGroupPluginAuthorityV1(RevokeGroupPluginAuthorityV1Args), + + /// Add an external plugin adapter to a Group. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + AddGroupExternalPluginAdapterV1(AddGroupExternalPluginAdapterV1Args), + + /// Remove an external plugin adapter from a Group. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or delegate of the group")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + RemoveGroupExternalPluginAdapterV1(RemoveGroupExternalPluginAdapterV1Args), + + /// Write data to a Group external plugin adapter. + #[account(0, writable, name="group", desc = "The address of the group")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The data authority or update authority of the group")] + #[account(3, optional, name="buffer", desc = "Buffer account containing data")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + WriteGroupExternalPluginAdapterDataV1(WriteGroupExternalPluginAdapterDataV1Args), + + /// Create a new Group account. + #[account(0, writable, signer, name="group", desc = "The address of the new group")] + #[account(1, optional, name="update_authority", desc = "The authority of the new group")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, name="system_program", desc = "The system program")] + CreateGroupV1(CreateGroupV1Args), + + /// Close an existing Group account. The group must have no parent or child relationships. + #[account(0, writable, name="group", desc = "The address of the group to close")] + #[account(1, writable, signer, name="payer", desc = "The account receiving reclaimed lamports")] + #[account(2, optional, signer, name="authority", desc = "The update authority or update delegate of the group")] + CloseGroupV1(CloseGroupV1Args), + + /// Update an existing Group account. + #[account(0, writable, name="group", desc = "The address of the group to update")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The update authority or update delegate of the group")] + #[account(3, optional, name="new_update_authority", desc = "The new update authority of the group")] + #[account(4, name="system_program", desc = "The system program")] + UpdateGroupV1(UpdateGroupV1Args), } diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/groups.rs b/programs/mpl-core/src/plugins/internal/authority_managed/groups.rs new file mode 100644 index 00000000..c5f24a7f --- /dev/null +++ b/programs/mpl-core/src/plugins/internal/authority_managed/groups.rs @@ -0,0 +1,25 @@ +use crate::{plugins::PluginValidation, state::DataBlob}; +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +/// Groups plugin for collections. Stores the immediate parent group accounts this collection +/// belongs to. Validation is deferred to specialized group instructions, so the plugin itself has +/// no special lifecycle behaviour beyond the default `PluginValidation` implementation. +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)] +pub struct Groups { + /// The list of parent group accounts for this collection. + pub groups: Vec, // 4 + len * 32 +} + +impl Groups { + const BASE_LEN: usize = 4; // length of the groups vector +} + +impl DataBlob for Groups { + fn len(&self) -> usize { + Self::BASE_LEN + self.groups.len() * 32 + } +} + +impl PluginValidation for Groups {} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/mod.rs b/programs/mpl-core/src/plugins/internal/authority_managed/mod.rs index 55ecd552..0925ae3f 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/mod.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/mod.rs @@ -1,5 +1,6 @@ mod add_blocker; mod attributes; +mod groups; mod immutable_metadata; mod master_edition; mod royalties; @@ -8,6 +9,7 @@ mod verified_creators; pub use add_blocker::*; pub use attributes::*; +pub use groups::*; pub use immutable_metadata::*; pub use master_edition::*; pub use royalties::*; diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index 33e55acc..95cb9983 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -66,8 +66,9 @@ pub enum Plugin { FreezeExecute(FreezeExecute), /// Permanent Freeze Execute plugin allows the authority to freeze the execute lifecycle event. PermanentFreezeExecute(PermanentFreezeExecute), + /// Groups plugin stores parent group memberships of a collection for taxonomy purposes + Groups(Groups), } - impl Plugin { /// Get the default authority for a plugin which defines who must allow the plugin to be created. pub fn manager(&self) -> Authority { @@ -112,6 +113,7 @@ impl Plugin { Plugin::BubblegumV2(inner) => inner, Plugin::FreezeExecute(inner) => inner, Plugin::PermanentFreezeExecute(inner) => inner, + Plugin::Groups(inner) => inner, } } } @@ -147,7 +149,8 @@ impl DataBlob for Plugin { Plugin::FreezeExecute(freeze_execute) => freeze_execute.len(), Plugin::PermanentFreezeExecute(permanent_freeze_execute) => { permanent_freeze_execute.len() - } + }, + Plugin::Groups(groups) => groups.len(), } } } @@ -206,6 +209,8 @@ pub enum PluginType { FreezeExecute, /// Permanent Freeze Execute plugin. PermanentFreezeExecute, + /// Groups plugin. + Groups, } impl PluginType { @@ -248,6 +253,7 @@ impl From<&Plugin> for PluginType { Plugin::BubblegumV2(_) => PluginType::BubblegumV2, Plugin::FreezeExecute(_) => PluginType::FreezeExecute, Plugin::PermanentFreezeExecute(_) => PluginType::PermanentFreezeExecute, + Plugin::Groups(_) => PluginType::Groups, } } } @@ -276,6 +282,7 @@ impl PluginType { }, PluginType::FreezeExecute => Authority::Owner, PluginType::PermanentFreezeExecute => Authority::UpdateAuthority, + PluginType::Groups => Authority::UpdateAuthority, } } } @@ -329,6 +336,7 @@ mod test { Plugin::BubblegumV2(BubblegumV2 {}), Plugin::FreezeExecute(FreezeExecute { frozen: false }), Plugin::PermanentFreezeExecute(PermanentFreezeExecute { frozen: false }), + Plugin::Groups(Groups { groups: vec![] }), ]; assert_eq!( @@ -446,6 +454,9 @@ mod test { })], vec![Plugin::BubblegumV2(BubblegumV2 {})], vec![Plugin::FreezeExecute(FreezeExecute { frozen: true })], + vec![Plugin::Groups(Groups { + groups: vec![Pubkey::default()], + })], vec![Plugin::PermanentFreezeExecute(PermanentFreezeExecute { frozen: true, })], diff --git a/programs/mpl-core/src/processor/add_assets_to_group.rs b/programs/mpl-core/src/processor/add_assets_to_group.rs new file mode 100644 index 00000000..b390991d --- /dev/null +++ b/programs/mpl-core/src/processor/add_assets_to_group.rs @@ -0,0 +1,226 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + instruction::accounts::AddAssetsToGroupV1Accounts, + instruction::accounts::Context, + plugins::{ + create_meta_idempotent, initialize_plugin, Groups, Plugin, PluginHeaderV1, + PluginRegistryV1, PluginType, + }, + state::{AssetV1, DataBlob, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resize_or_reallocate_account, resolve_authority}, +}; + +/// Arguments for the `AddAssetsToGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddAssetsToGroupV1Args {} + +/// Returns true if the authority is allowed to mutate the asset (update authority or delegate). +fn is_valid_asset_authority( + asset_info: &AccountInfo, + authority_info: &AccountInfo, +) -> ProgramResult { + use crate::plugins::fetch_wrapped_plugin; + use crate::plugins::PluginType; + use crate::state::UpdateAuthority; + + let asset_core = AssetV1::load(asset_info, 0)?; + + // Direct update authority address. + if let UpdateAuthority::Address(addr) = asset_core.update_authority.clone() { + if addr == *authority_info.key { + return Ok(()); + } + } + + // Check UpdateDelegate plugin. + if let Ok((_pa, plugin)) = + fetch_wrapped_plugin::(asset_info, Some(&asset_core), PluginType::UpdateDelegate) + { + if let crate::plugins::Plugin::UpdateDelegate(update_delegate) = plugin { + if update_delegate + .additional_delegates + .contains(authority_info.key) + { + return Ok(()); + } + } + } + + Err(MplCoreError::InvalidAuthority.into()) +} + +/// Processor for the `AddAssetsToGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn add_assets_to_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + _args: AddAssetsToGroupV1Args, +) -> ProgramResult { + // Derive typed context generated by Shank. + let ctx: Context = AddAssetsToGroupV1Accounts::context(accounts)?; + + let group_info = ctx.accounts.group; + let payer_info = ctx.accounts.payer; + let authority_info_opt = ctx.accounts.authority; + let system_program_info = ctx.accounts.system_program; + + // Dynamic list of asset accounts passed after the fixed accounts. + let remaining_accounts = ctx.remaining_accounts; + + // The payer must ALWAYS be a signer because they may be charged rent + // if the group account or any of the asset plugin registries need to be + // resized or reallocated. Therefore, even when a distinct authority is + // provided (and signed) via the optional `authority` account, the payer + // is still required to sign this instruction. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Load group + let mut group = GroupV1::load(group_info, 0)?; + + // Ensure authority for group + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Iterate over each asset account in remaining_accounts. + for asset_info in remaining_accounts.iter() { + // authority must be valid for asset + is_valid_asset_authority(asset_info, authority_info)?; + + // Update group.assets vector + if !group.assets.contains(asset_info.key) { + group.assets.push(*asset_info.key); + } else { + // already present, but still update plugin to ensure membership + } + + // Ensure or update Groups plugin on asset + process_asset_groups_plugin_add( + asset_info, + *group_info.key, + payer_info, + system_program_info, + )?; + } + + // Save group changes + let serialized_group = group.try_to_vec()?; + if serialized_group.len() != group_info.data_len() { + resize_or_reallocate_account( + group_info, + payer_info, + system_program_info, + serialized_group.len(), + )?; + } + sol_memcpy( + &mut group_info.try_borrow_mut_data()?, + &serialized_group, + serialized_group.len(), + ); + + Ok(()) +} + +fn process_asset_groups_plugin_add<'a>( + asset_info: &AccountInfo<'a>, + parent_group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + let (_asset_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(asset_info, payer_info, system_program_info)?; + + let plugin_record_opt = plugin_registry + .registry + .iter() + .find(|r| r.plugin_type == PluginType::Groups) + .cloned(); + + match plugin_record_opt { + None => { + let plugin = Plugin::Groups(Groups { + groups: vec![parent_group], + }); + initialize_plugin::( + &plugin, + &plugin.manager(), + header_offset, + &mut plugin_header, + &mut plugin_registry, + asset_info, + payer_info, + system_program_info, + )?; + } + Some(record) => { + let mut plugin = Plugin::load(asset_info, record.offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if !inner.groups.contains(&parent_group) { + inner.groups.push(parent_group); + } else { + return Ok(()); + } + } else { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // serialize adjustments + let old_data = Plugin::deserialize(&mut &asset_info.data.borrow()[record.offset..])? + .try_to_vec()?; + let new_data = plugin.try_to_vec()?; + let size_diff = new_data.len() as isize - old_data.len() as isize; + if size_diff != 0 { + plugin_registry.bump_offsets(record.offset, size_diff)?; + plugin_header.plugin_registry_offset = + (plugin_header.plugin_registry_offset as isize + size_diff) as usize; + + let new_size = (asset_info.data_len() as isize + size_diff) as usize; + resize_or_reallocate_account( + asset_info, + payer_info, + system_program_info, + new_size, + )?; + + let next_offset = (record.offset + old_data.len()) as isize; + let new_next_offset = next_offset + size_diff; + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = asset_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_offset as usize), + base_ptr.add(next_offset as usize), + copy_len, + ); + } + } + } + plugin_header.save(asset_info, header_offset)?; + plugin_registry.save(asset_info, plugin_header.plugin_registry_offset)?; + plugin.save(asset_info, record.offset)?; + } + } + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_collections_to_group.rs b/programs/mpl-core/src/processor/add_collections_to_group.rs new file mode 100644 index 00000000..240272bd --- /dev/null +++ b/programs/mpl-core/src/processor/add_collections_to_group.rs @@ -0,0 +1,228 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + instruction::accounts::{AddCollectionsToGroupV1Accounts, Context}, + plugins::{ + create_meta_idempotent, initialize_plugin, Groups, Plugin, PluginHeaderV1, + PluginRegistryV1, PluginType, + }, + state::{CollectionV1, DataBlob, GroupV1, SolanaAccount}, + utils::{ + is_valid_collection_authority, is_valid_group_authority, resize_or_reallocate_account, + resolve_authority, + }, +}; + +/// Arguments for the `AddCollectionsToGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddCollectionsToGroupV1Args {} + +/// Processor for the `AddCollectionsToGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn add_collections_to_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + _args: AddCollectionsToGroupV1Args, +) -> ProgramResult { + // Use generated context to access fixed and remaining accounts. + let ctx: Context = + AddCollectionsToGroupV1Accounts::context(accounts)?; + + let group_info = ctx.accounts.group; + let payer_info = ctx.accounts.payer; + let authority_info_opt = ctx.accounts.authority; + let system_program_info = ctx.accounts.system_program; + + // Dynamic list of collection accounts passed after the fixed accounts. + let remaining_accounts = ctx.remaining_accounts; + + // Basic guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Deserialize group. + let mut group = GroupV1::load(group_info, 0)?; + + // Authority check: must be group's update authority or delegate. + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Process each collection. + for collection_info in remaining_accounts.iter() { + // Verify collection is writable. + if !collection_info.is_writable { + msg!("Error: Collection account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + // Deserialize collection. + let _collection_core = CollectionV1::load(collection_info, 0)?; + + // Authority must be update authority of the collection as well. + if !is_valid_collection_authority(collection_info, authority_info)? { + msg!("Error: Signer is not collection update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // 1. Update Group account collections vector. + if group.collections.contains(collection_info.key) { + // Already present, skip to next. + continue; + } + group.collections.push(*collection_info.key); + + // 2. Update or create Groups plugin on the collection. + process_collection_groups_plugin_add( + collection_info, + *group_info.key, + payer_info, + system_program_info, + )?; + + // The collection core itself does not change; no reserialization needed. + } + + // Persist group changes. + let serialized_group = group.try_to_vec()?; + if serialized_group.len() != group_info.data_len() { + resize_or_reallocate_account( + group_info, + payer_info, + system_program_info, + serialized_group.len(), + )?; + } + + sol_memcpy( + &mut group_info.try_borrow_mut_data()?, + &serialized_group, + serialized_group.len(), + ); + + Ok(()) +} + +/// Add the parent group pubkey to the collection's Groups plugin, creating the plugin if necessary. +fn process_collection_groups_plugin_add<'a>( + collection_info: &AccountInfo<'a>, + parent_group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + // Ensure plugin metadata exists or create. + let (_collection_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(collection_info, payer_info, system_program_info)?; + + // Attempt to fetch existing Groups plugin. + let plugin_record_opt = plugin_registry + .registry + .iter() + .find(|r| r.plugin_type == PluginType::Groups) + .cloned(); + + match plugin_record_opt { + None => { + // Plugin does not exist; create it. + let groups_plugin = Groups { + groups: vec![parent_group], + }; + let plugin = Plugin::Groups(groups_plugin); + initialize_plugin::( + &plugin, + &plugin.manager(), + header_offset, + &mut plugin_header, + &mut plugin_registry, + collection_info, + payer_info, + system_program_info, + )? + } + Some(record) => { + // Plugin exists, load, modify, and update. + let mut plugin = Plugin::load(collection_info, record.offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if inner.groups.contains(&parent_group) { + // Already present, nothing to do. + return Ok(()); + } + inner.groups.push(parent_group); + } else { + // This should never happen. + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Serialize old and new plugin to compute size diff. + let old_plugin_data = + Plugin::deserialize(&mut &collection_info.data.borrow()[record.offset..])? + .try_to_vec()?; + let new_plugin_data = plugin.try_to_vec()?; + let size_diff = (new_plugin_data.len() as isize) + .checked_sub(old_plugin_data.len() as isize) + .ok_or(MplCoreError::NumericalOverflow)?; + + if size_diff != 0 { + // Bump offsets for subsequent registry entries and header. + plugin_registry.bump_offsets(record.offset, size_diff)?; + let new_registry_offset = (plugin_header.plugin_registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + // Resize account. + let new_size = (collection_info.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize; + resize_or_reallocate_account( + collection_info, + payer_info, + system_program_info, + new_size, + )?; + + // Move trailing data to accommodate new plugin size. + let next_plugin_offset = (record.offset + old_plugin_data.len()) as isize; + let new_next_plugin_offset = next_plugin_offset + size_diff; + + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = collection_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_plugin_offset as usize), + base_ptr.add(next_plugin_offset as usize), + copy_len, + ); + } + } + } + + // Save header, registry and new plugin. + plugin_header.save(collection_info, header_offset)?; + plugin_registry.save(collection_info, plugin_header.plugin_registry_offset)?; + plugin.save(collection_info, record.offset)?; + } + } + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_group_external_plugin_adapter.rs b/programs/mpl-core/src/processor/add_group_external_plugin_adapter.rs new file mode 100644 index 00000000..f058f744 --- /dev/null +++ b/programs/mpl-core/src/processor/add_group_external_plugin_adapter.rs @@ -0,0 +1,125 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{ + create_meta_idempotent, initialize_external_plugin_adapter, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, PluginValidationContext, ValidationResult, + }, + state::{Authority, DataBlob, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for the `AddGroupExternalPluginAdapterV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddGroupExternalPluginAdapterV1Args { + /// Initialization info for the external plugin adapter. Only `AppData` is permitted. + pub init_info: ExternalPluginAdapterInitInfo, +} + +/// Processor for `AddGroupExternalPluginAdapterV1`. +#[allow(clippy::too_many_arguments)] +pub(crate) fn add_group_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: AddGroupExternalPluginAdapterV1Args, +) -> ProgramResult { + // Expected accounts: + // 0. [writable] Group account + // 1. [writable, signer] Payer account (also default authority) + // 2. [signer] Optional authority (update authority or update delegate of group) + // 3. [] System program + // 4. [] Optional SPL Noop (log wrapper) + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + let log_wrapper_info_opt = accounts.get(4); + + // Basic guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + // Validate system & log wrapper programs. + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + if let Some(wrapper_info) = log_wrapper_info_opt { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + // Only AppData external plugin adapters are permitted on Group accounts. + match args.init_info { + ExternalPluginAdapterInitInfo::AppData(_) => {} + _ => return Err(MplCoreError::InvalidPluginAdapterTarget.into()), + } + + // Authority check against the group's update authority or update delegate. + let group = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Build the adapter and perform adapter-level validation. + let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); + // The plugin authority defaults to UpdateAuthority if none supplied. + let external_plugin_adapter_authority = match &args.init_info { + ExternalPluginAdapterInitInfo::AppData(app_data) => app_data + .init_plugin_authority + .unwrap_or(Authority::UpdateAuthority), + _ => unreachable!(), + }; + + // Best-effort plugin validation (mirrors collection implementation). + let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: None, + self_authority: &Authority::UpdateAuthority, + authority_info, + resolved_authorities: None, + new_owner: None, + new_asset_authority: None, + new_collection_authority: None, + target_plugin: None, + target_plugin_authority: None, + target_external_plugin: Some(&external_plugin_adapter), + target_external_plugin_authority: Some(&external_plugin_adapter_authority), + }; + if crate::plugins::ExternalPluginAdapter::validate_add_external_plugin_adapter( + &external_plugin_adapter, + &validation_ctx, + )? == ValidationResult::Rejected + { + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Persist the adapter. + let (_group_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(group_info, payer_info, system_program_info)?; + + initialize_external_plugin_adapter::( + &args.init_info, + header_offset, + &mut plugin_header, + &mut plugin_registry, + group_info, + payer_info, + system_program_info, + None, + )?; + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_group_plugin.rs b/programs/mpl-core/src/processor/add_group_plugin.rs new file mode 100644 index 00000000..60787c45 --- /dev/null +++ b/programs/mpl-core/src/processor/add_group_plugin.rs @@ -0,0 +1,131 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{ + create_meta_idempotent, initialize_plugin, Plugin, PluginType, PluginValidationContext, + ValidationResult, + }, + state::{Authority, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for the `AddGroupPluginV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddGroupPluginV1Args { + pub(crate) plugin: Plugin, + /// Optional authority to initialize for the plugin. Defaults to the plugin's manager. + pub(crate) init_authority: Option, +} + +/// Processor for `AddGroupPluginV1`. +#[allow(clippy::too_many_arguments)] +pub(crate) fn add_group_plugin<'a>( + accounts: &'a [AccountInfo<'a>], + args: AddGroupPluginV1Args, +) -> ProgramResult { + // Expected accounts: + // 0. [writable] Group account + // 1. [writable, signer] Payer – also acts as default authority if none provided + // 2. [signer] Optional authority (update authority or its delegate) + // 3. [] System program + // 4. [] Optional SPL Noop (log wrapper) + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + // Optional log wrapper validation + if let Some(wrapper_info) = accounts.get(4) { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + // Basic guards + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Deserialize group and authority check + let group = GroupV1::load(group_info, 0)?; + + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // ------------------------------------------------------------------ + // ALLOW-LIST ENFORCEMENT + // ------------------------------------------------------------------ + // Only the following plugin types are permitted on Group accounts. + // • Attributes + // • VerifiedCreators + // • Autograph + + let plugin_type = PluginType::from(&args.plugin); + + let is_allowed_plugin = matches!( + plugin_type, + PluginType::Attributes | PluginType::VerifiedCreators | PluginType::Autograph + ); + + // Reject any plugin that is not in the allow-list. + if !is_allowed_plugin { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Plugin-specific validation (best-effort, mirrors AddCollectionPlugin) + let default_authority = args.plugin.manager(); + let target_plugin_authority = args.init_authority.as_ref().unwrap_or(&default_authority); + + let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: None, + self_authority: target_plugin_authority, + authority_info, + resolved_authorities: None, + new_owner: None, + new_asset_authority: None, + new_collection_authority: None, + target_plugin: Some(&args.plugin), + target_plugin_authority: Some(target_plugin_authority), + target_external_plugin: None, + target_external_plugin_authority: None, + }; + if Plugin::validate_add_plugin(&args.plugin, &validation_ctx)? == ValidationResult::Rejected { + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Persist plugin – create meta if needed then initialize. + let (_group, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(group_info, payer_info, system_program_info)?; + + initialize_plugin::( + &args.plugin, + target_plugin_authority, + header_offset, + &mut plugin_header, + &mut plugin_registry, + group_info, + payer_info, + system_program_info, + )?; + + // No other state changes required on the Group struct. + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_groups_to_group.rs b/programs/mpl-core/src/processor/add_groups_to_group.rs new file mode 100644 index 00000000..867ffd61 --- /dev/null +++ b/programs/mpl-core/src/processor/add_groups_to_group.rs @@ -0,0 +1,144 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + program_memory::sol_memcpy, pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + state::{DataBlob, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resize_or_reallocate_account, resolve_authority}, +}; + +/// Arguments for the `AddGroupsToGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddGroupsToGroupV1Args { + /// The list of child groups to add to the parent group. + pub(crate) groups: Vec, +} + +/// Processor for the `AddGroupsToGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn add_groups_to_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: AddGroupsToGroupV1Args, +) -> ProgramResult { + // Expected account layout: + // 0. [writable] Parent group account + // 1. [writable, signer] Payer account (also default authority) + // 2. [signer] Optional authority (update auth/delegate) + // 3. [] System program + // 4..N [writable] Child group accounts, one for each pubkey in args.groups + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let parent_group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + // Remaining accounts are the child group accounts. + let child_group_accounts = &accounts[4..]; + + // Basic guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Validate arg count matches remaining accounts. + if child_group_accounts.len() != args.groups.len() { + msg!( + "Error: Number of group accounts ({}) does not match number of pubkeys in args ({}).", + child_group_accounts.len(), + args.groups.len() + ); + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Deserialize parent group. + let mut parent_group = GroupV1::load(parent_group_info, 0)?; + + // Authority check: must be update authority or delegate of the parent group. + if !is_valid_group_authority(parent_group_info, authority_info)? { + msg!("Error: Invalid authority for parent group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Process each child group account. + for (i, child_info) in child_group_accounts.iter().enumerate() { + // Ensure account key matches expected pubkey. + if child_info.key != &args.groups[i] { + msg!( + "Error: Child group account at position {} does not match provided pubkey list", + i + ); + return Err(MplCoreError::IncorrectAccount.into()); + } + + // Ensure child group account is writable. + if !child_info.is_writable { + msg!("Error: Child group account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + // Deserialize child group. + let mut child_group = GroupV1::load(child_info, 0)?; + + // Authority must also be update authority or delegate for the child group. + if !is_valid_group_authority(child_info, authority_info)? { + msg!("Error: Signer is not child group update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // 1. Update parent group's child list if not already present. + if !parent_group.groups.contains(child_info.key) { + parent_group.groups.push(*child_info.key); + } + + // 2. Update child's parent_groups list if not already present. + if !child_group.parent_groups.contains(parent_group_info.key) { + child_group.parent_groups.push(*parent_group_info.key); + + // Persist child group changes. + let serialized_child = child_group.try_to_vec()?; + if serialized_child.len() != child_info.data_len() { + resize_or_reallocate_account( + child_info, + payer_info, + system_program_info, + serialized_child.len(), + )?; + } + sol_memcpy( + &mut child_info.try_borrow_mut_data()?, + &serialized_child, + serialized_child.len(), + ); + } + } + + // Persist parent group changes. + let serialized_parent = parent_group.try_to_vec()?; + if serialized_parent.len() != parent_group_info.data_len() { + resize_or_reallocate_account( + parent_group_info, + payer_info, + system_program_info, + serialized_parent.len(), + )?; + } + sol_memcpy( + &mut parent_group_info.try_borrow_mut_data()?, + &serialized_parent, + serialized_parent.len(), + ); + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index 8587f2fe..3aa7ab4f 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -138,6 +138,12 @@ pub(crate) fn add_collection_plugin<'a>( } let target_plugin_authority = args.init_authority.unwrap_or(args.plugin.manager()); + // Reject attempts to add a Groups plugin via the generic collection plugin pathway. + // Groups plugins must be managed exclusively by the dedicated Group instructions + // (Add/Remove Collections To/From Group, etc.). + if PluginType::from(&args.plugin) == PluginType::Groups { + return Err(MplCoreError::InvalidPlugin.into()); + } let validation_ctx = PluginValidationContext { accounts, asset_info: None, diff --git a/programs/mpl-core/src/processor/approve_group_plugin_authority.rs b/programs/mpl-core/src/processor/approve_group_plugin_authority.rs new file mode 100644 index 00000000..cdd89534 --- /dev/null +++ b/programs/mpl-core/src/processor/approve_group_plugin_authority.rs @@ -0,0 +1,80 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{approve_authority_on_plugin, fetch_wrapped_plugin, PluginType}, + state::{Authority, GroupV1, SolanaAccount}, + utils::{fetch_core_data, is_valid_group_authority, resolve_authority}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct ApproveGroupPluginAuthorityV1Args { + pub plugin_type: PluginType, + pub new_authority: Authority, +} + +pub(crate) fn approve_group_plugin_authority<'a>( + accounts: &'a [AccountInfo<'a>], + args: ApproveGroupPluginAuthorityV1Args, +) -> ProgramResult { + // Accounts: + // 0. [writable] Group + // 1. [writable, signer] Payer + // 2. [signer] Optional authority + // 3. [] System program + // 4. [] Optional log wrapper + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + if let Some(wrapper_info) = accounts.get(4) { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Authority check (update authority or delegate) + let group = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Ensure plugin exists + let (_current_authority, _plugin) = + fetch_wrapped_plugin::(group_info, None, args.plugin_type)?; + + // Fetch plugin meta + let (_core_stub, plugin_header_opt, plugin_registry_opt) = + fetch_core_data::(group_info)?; + let plugin_header = plugin_header_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + let mut plugin_registry = plugin_registry_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + + approve_authority_on_plugin::( + &args.plugin_type, + &args.new_authority, + group_info, + &plugin_header, + &mut plugin_registry, + payer_info, + system_program_info, + ) +} diff --git a/programs/mpl-core/src/processor/approve_plugin_authority.rs b/programs/mpl-core/src/processor/approve_plugin_authority.rs index 715c0de3..2790ac92 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -111,6 +111,11 @@ pub(crate) fn approve_collection_plugin_authority<'a>( } } + // Groups plugins must be managed only via Group-specific instructions; approve is not allowed. + if args.plugin_type == PluginType::Groups { + return Err(MplCoreError::InvalidPlugin.into()); + } + let (plugin_authority, plugin) = fetch_wrapped_plugin::(ctx.accounts.collection, None, args.plugin_type)?; diff --git a/programs/mpl-core/src/processor/close_group.rs b/programs/mpl-core/src/processor/close_group.rs new file mode 100644 index 00000000..f7e9ea80 --- /dev/null +++ b/programs/mpl-core/src/processor/close_group.rs @@ -0,0 +1,49 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; + +use crate::{ + error::MplCoreError, + instruction::accounts::CloseGroupV1Accounts, + state::{GroupV1, SolanaAccount}, + utils::{close_program_account, is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for the `CloseGroupV1` instruction. +/// +/// Currently, no arguments are required but the struct is kept for +/// forward-compatibility and to mirror the pattern used by other +/// processors in the codebase. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Default)] +pub(crate) struct CloseGroupV1Args {} + +/// Processor for the `CloseGroupV1` instruction. +pub(crate) fn close_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + _args: CloseGroupV1Args, +) -> ProgramResult { + // Derive the typed account context from the raw slice. + let ctx = CloseGroupV1Accounts::context(accounts)?; + + // Basic guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + // Deserialize the group account. + let group = GroupV1::load(ctx.accounts.group, 0)?; + + // Ensure the signer is the update authority or delegate of the group. + if !is_valid_group_authority(ctx.accounts.group, authority)? { + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Ensure the group has no children or parents. + if !(group.collections.is_empty() && group.groups.is_empty() && group.parent_groups.is_empty()) + { + return Err(MplCoreError::GroupMustBeEmpty.into()); + } + + // Close the group account, transferring rent-exempt lamports back to the payer. + close_program_account(ctx.accounts.group, ctx.accounts.payer) +} diff --git a/programs/mpl-core/src/processor/create_group.rs b/programs/mpl-core/src/processor/create_group.rs new file mode 100644 index 00000000..1d11aa06 --- /dev/null +++ b/programs/mpl-core/src/processor/create_group.rs @@ -0,0 +1,552 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + msg, + program::invoke, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, + rent::Rent, + system_instruction, system_program, + sysvar::Sysvar, +}; + +use crate::{ + error::MplCoreError, + instruction::accounts::CreateGroupV1Accounts, + plugins::{ + create_meta_idempotent, initialize_plugin, Groups, Plugin, PluginHeaderV1, + PluginRegistryV1, PluginType, + }, + state::{AssetV1, CollectionV1, DataBlob, SolanaAccount}, + state::{GroupV1, Key}, + utils::{ + is_valid_collection_authority, is_valid_group_authority, resize_or_reallocate_account, + resolve_authority, + }, +}; + +use crate::plugins::fetch_wrapped_plugin; +use crate::state::RelationshipKind; +use std::collections::HashSet; + +/// Arguments for the `CreateGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct CreateGroupV1Args { + /// Human-readable display name for the group. + pub(crate) name: String, + /// URI pointing to off-chain JSON describing the group. + pub(crate) uri: String, + /// Relationships (collections, child groups, parent groups, assets) to link at creation. + pub(crate) relationships: Vec, +} + +/// Returns true if the `authority_info` represents either the update authority of the asset +/// or a valid update delegate of the asset. +fn is_valid_asset_authority( + asset_info: &AccountInfo, + authority_info: &AccountInfo, +) -> Result { + use crate::plugins::PluginType; + use crate::state::UpdateAuthority; + + // Load asset core data + let asset_core = AssetV1::load(asset_info, 0)?; + + // Fast path: signer is the update authority address (when address type) + if let UpdateAuthority::Address(addr) = asset_core.update_authority.clone() { + if addr == *authority_info.key { + return Ok(true); + } + } + + // Attempt to locate an UpdateDelegate plugin on the asset. + if let Ok((_plugin_authority, plugin)) = + fetch_wrapped_plugin::(asset_info, Some(&asset_core), PluginType::UpdateDelegate) + { + if let crate::plugins::Plugin::UpdateDelegate(update_delegate) = plugin { + if update_delegate + .additional_delegates + .contains(authority_info.key) + { + return Ok(true); + } + } + } + + Ok(false) +} + +/// Processor for the `CreateGroupV1` instruction. +pub(crate) fn create_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: CreateGroupV1Args, +) -> ProgramResult { + // Derive the typed account context from the raw slice. + let ctx = CreateGroupV1Accounts::context(accounts)?; + let rent = Rent::get()?; + + // Basic guards. + assert_signer(ctx.accounts.group)?; + assert_signer(ctx.accounts.payer)?; + let authority_info = resolve_authority(ctx.accounts.payer, ctx.accounts.update_authority)?; + + // Ensure the canonical system program is provided. + if *ctx.accounts.system_program.key != system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // --- PREP INPUT VECTORS ------------------------------------------------- + let CreateGroupV1Args { + name, + uri, + relationships, + } = args; + + let mut collections_vec: Vec = Vec::new(); + let mut child_groups_vec: Vec = Vec::new(); + let mut parent_groups_vec: Vec = Vec::new(); + let mut assets_vec: Vec = Vec::new(); + + // Track all relationship keys we have already seen to prevent duplicates across + // categories (e.g., the same account referenced twice or in two different + // categories). If a duplicate is detected, abort early with an error. + let mut seen: HashSet = HashSet::new(); + + for rel in relationships.into_iter() { + // If insert returns false, the value was already present – duplicate detected. + if !seen.insert(rel.key) { + return Err(MplCoreError::DuplicateEntry.into()); + } + + match rel.kind { + RelationshipKind::Collection => collections_vec.push(rel.key), + RelationshipKind::ChildGroup => child_groups_vec.push(rel.key), + RelationshipKind::ParentGroup => parent_groups_vec.push(rel.key), + RelationshipKind::Asset => assets_vec.push(rel.key), + } + } + + let new_group = GroupV1::new( + *authority_info.key, + name.clone(), + uri.clone(), + collections_vec.clone(), + child_groups_vec.clone(), + parent_groups_vec.clone(), + assets_vec.clone(), + ); + + let serialized_data = new_group.try_to_vec()?; + let lamports = rent.minimum_balance(serialized_data.len()); + + // Create the on-chain account for the group via CPI to System Program. + invoke( + &system_instruction::create_account( + ctx.accounts.payer.key, + ctx.accounts.group.key, + lamports, + serialized_data.len() as u64, + &crate::ID, + ), + &[ + ctx.accounts.payer.clone(), + ctx.accounts.group.clone(), + ctx.accounts.system_program.clone(), + ], + )?; + + // Persist the serialized group data into the new account. + sol_memcpy( + &mut ctx.accounts.group.try_borrow_mut_data()?, + &serialized_data, + serialized_data.len(), + ); + + // ---------------------------------------------------------------------- + // POST-CREATION LINKING LOGIC + // ---------------------------------------------------------------------- + let remaining_accounts = ctx.remaining_accounts; + let expected_accounts = + collections_vec.len() + child_groups_vec.len() + parent_groups_vec.len() + assets_vec.len(); + + if remaining_accounts.len() != expected_accounts { + msg!( + "Error: Incorrect number of remaining accounts (expected {}, got {}).", + expected_accounts, + remaining_accounts.len() + ); + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Offsets into remaining_accounts slice + let mut cursor: usize = 0; + + // ----------------- LINK COLLECTIONS ----------------------------------- + for (i, collection_key) in collections_vec.iter().enumerate() { + let collection_info = &remaining_accounts[cursor + i]; + + // Account correctness checks + if collection_info.key != collection_key { + msg!("Error: Collection account mismatch at index {}", i); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !collection_info.is_writable { + msg!("Error: Collection account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + // Authority check (collection update authority or delegate) + if !is_valid_collection_authority(collection_info, authority_info)? { + msg!("Error: Signer is not collection update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Add Groups plugin entry. + process_collection_groups_plugin_add( + collection_info, + *ctx.accounts.group.key, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + } + + cursor += collections_vec.len(); + + // ----------------- LINK CHILD GROUPS ---------------------------------- + for (i, child_key) in child_groups_vec.iter().enumerate() { + let child_info = &remaining_accounts[cursor + i]; + + if child_info.key != child_key { + msg!("Error: Child group account mismatch at index {}", i); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !child_info.is_writable { + msg!("Error: Child group account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + let mut child_group = GroupV1::load(child_info, 0)?; + + if !is_valid_group_authority(child_info, authority_info)? { + msg!("Error: Signer is not child group update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + if !child_group.parent_groups.contains(ctx.accounts.group.key) { + child_group.parent_groups.push(*ctx.accounts.group.key); + + let serialized_child = child_group.try_to_vec()?; + if serialized_child.len() != child_info.data_len() { + resize_or_reallocate_account( + child_info, + ctx.accounts.payer, + ctx.accounts.system_program, + serialized_child.len(), + )?; + } + sol_memcpy( + &mut child_info.try_borrow_mut_data()?, + &serialized_child, + serialized_child.len(), + ); + } + } + + cursor += child_groups_vec.len(); + + // ----------------- LINK PARENT GROUPS --------------------------------- + for (i, parent_key) in parent_groups_vec.iter().enumerate() { + let parent_info = &remaining_accounts[cursor + i]; + + if parent_info.key != parent_key { + msg!("Error: Parent group account mismatch at index {}", i); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !parent_info.is_writable { + msg!("Error: Parent group account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + let mut parent_group = GroupV1::load(parent_info, 0)?; + + if !is_valid_group_authority(parent_info, authority_info)? { + msg!("Error: Signer is not parent group update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + if !parent_group.groups.contains(ctx.accounts.group.key) { + parent_group.groups.push(*ctx.accounts.group.key); + + let serialized_parent = parent_group.try_to_vec()?; + if serialized_parent.len() != parent_info.data_len() { + resize_or_reallocate_account( + parent_info, + ctx.accounts.payer, + ctx.accounts.system_program, + serialized_parent.len(), + )?; + } + sol_memcpy( + &mut parent_info.try_borrow_mut_data()?, + &serialized_parent, + serialized_parent.len(), + ); + } + } + + cursor += parent_groups_vec.len(); + + // ----------------- LINK ASSETS ----------------------------------------- + for (i, asset_key) in assets_vec.iter().enumerate() { + let asset_info = &remaining_accounts[cursor + i]; + + if asset_info.key != asset_key { + msg!("Error: Asset account mismatch at index {}", i); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !asset_info.is_writable { + msg!("Error: Asset account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + // Authority check (asset update authority or delegate) + if !is_valid_asset_authority(asset_info, authority_info)? { + msg!("Error: Signer is not asset update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Add Assets plugin entry. + process_asset_groups_plugin_add( + asset_info, + *ctx.accounts.group.key, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + } + + Ok(()) +} + +/// Add the parent group pubkey to the collection's Groups plugin, creating the plugin if necessary. +fn process_collection_groups_plugin_add<'a>( + collection_info: &AccountInfo<'a>, + parent_group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + // Ensure plugin metadata exists or create. + let (_collection_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(collection_info, payer_info, system_program_info)?; + + // Attempt to fetch existing Groups plugin. + let plugin_record_opt = plugin_registry + .registry + .iter() + .find(|r| r.plugin_type == PluginType::Groups) + .cloned(); + + match plugin_record_opt { + None => { + // Plugin does not exist; create it. + let groups_plugin = Groups { + groups: vec![parent_group], + }; + let plugin = Plugin::Groups(groups_plugin); + initialize_plugin::( + &plugin, + &plugin.manager(), + header_offset, + &mut plugin_header, + &mut plugin_registry, + collection_info, + payer_info, + system_program_info, + )? + } + Some(record) => { + // Plugin exists, load, modify, and update. + let mut plugin = Plugin::load(collection_info, record.offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if inner.groups.contains(&parent_group) { + // Already present, nothing to do. + return Ok(()); + } + inner.groups.push(parent_group); + } else { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Serialize old and new plugin to compute size diff. + let old_plugin_data = + Plugin::deserialize(&mut &collection_info.data.borrow()[record.offset..])? + .try_to_vec()?; + let new_plugin_data = plugin.try_to_vec()?; + let size_diff = (new_plugin_data.len() as isize) + .checked_sub(old_plugin_data.len() as isize) + .ok_or(MplCoreError::NumericalOverflow)?; + + if size_diff != 0 { + // Bump offsets for subsequent registry entries and header. + plugin_registry.bump_offsets(record.offset, size_diff)?; + let new_registry_offset = (plugin_header.plugin_registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + // Resize account. + let new_size = (collection_info.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize; + resize_or_reallocate_account( + collection_info, + payer_info, + system_program_info, + new_size, + )?; + + // Move trailing data to accommodate new plugin size. + let next_plugin_offset = (record.offset + old_plugin_data.len()) as isize; + let new_next_plugin_offset = next_plugin_offset + size_diff; + + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = collection_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_plugin_offset as usize), + base_ptr.add(next_plugin_offset as usize), + copy_len, + ); + } + } + } + + // Save header, registry and new plugin. + plugin_header.save(collection_info, header_offset)?; + plugin_registry.save(collection_info, plugin_header.plugin_registry_offset)?; + plugin.save(collection_info, record.offset)?; + } + } + + Ok(()) +} + +/// Add the asset pubkey to the group's Assets plugin, creating the plugin if necessary. +fn process_asset_groups_plugin_add<'a>( + asset_info: &AccountInfo<'a>, + group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + // Ensure plugin metadata exists or create. + let (_asset_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(asset_info, payer_info, system_program_info)?; + + // Attempt to fetch existing Assets plugin. + let plugin_record_opt = plugin_registry + .registry + .iter() + .find(|r| r.plugin_type == PluginType::Groups) + .cloned(); + + match plugin_record_opt { + None => { + // Plugin does not exist; create it. + let assets_plugin = Groups { + groups: vec![group], + }; + let plugin = Plugin::Groups(assets_plugin); + initialize_plugin::( + &plugin, + &plugin.manager(), + header_offset, + &mut plugin_header, + &mut plugin_registry, + asset_info, + payer_info, + system_program_info, + )? + } + Some(record) => { + // Plugin exists, load, modify, and update. + let mut plugin = Plugin::load(asset_info, record.offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if inner.groups.contains(&group) { + // Already present, nothing to do. + return Ok(()); + } + inner.groups.push(group); + } else { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Serialize old and new plugin to compute size diff. + let old_plugin_data = + Plugin::deserialize(&mut &asset_info.data.borrow()[record.offset..])? + .try_to_vec()?; + let new_plugin_data = plugin.try_to_vec()?; + let size_diff = (new_plugin_data.len() as isize) + .checked_sub(old_plugin_data.len() as isize) + .ok_or(MplCoreError::NumericalOverflow)?; + + if size_diff != 0 { + // Bump offsets for subsequent registry entries and header. + plugin_registry.bump_offsets(record.offset, size_diff)?; + let new_registry_offset = (plugin_header.plugin_registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + // Resize account. + let new_size = (asset_info.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? + as usize; + resize_or_reallocate_account( + asset_info, + payer_info, + system_program_info, + new_size, + )?; + + // Move trailing data to accommodate new plugin size. + let next_plugin_offset = (record.offset + old_plugin_data.len()) as isize; + let new_next_plugin_offset = next_plugin_offset + size_diff; + + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = asset_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_plugin_offset as usize), + base_ptr.add(next_plugin_offset as usize), + copy_len, + ); + } + } + } + + // Save header, registry and new plugin. + plugin_header.save(asset_info, header_offset)?; + plugin_registry.save(asset_info, plugin_header.plugin_registry_offset)?; + plugin.save(asset_info, record.offset)?; + } + } + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/mod.rs b/programs/mpl-core/src/processor/mod.rs index 78693ac2..e9a37ddd 100644 --- a/programs/mpl-core/src/processor/mod.rs +++ b/programs/mpl-core/src/processor/mod.rs @@ -1,42 +1,76 @@ +mod add_assets_to_group; +mod add_collections_to_group; mod add_external_plugin_adapter; +mod add_group_external_plugin_adapter; +mod add_group_plugin; +mod add_groups_to_group; mod add_plugin; +mod approve_group_plugin_authority; mod approve_plugin_authority; mod burn; +mod close_group; mod collect; mod compress; mod create; mod create_collection; +mod create_group; mod decompress; mod execute; +mod remove_assets_from_group; +mod remove_collections_from_group; mod remove_external_plugin_adapter; +mod remove_group_external_plugin_adapter; +mod remove_group_plugin; +mod remove_groups_from_group; mod remove_plugin; +mod revoke_group_plugin_authority; mod revoke_plugin_authority; mod transfer; mod update; mod update_collection_info; mod update_external_plugin_adapter; +mod update_group; +mod update_group_plugin; mod update_plugin; mod write_external_plugin_adapter_data; +mod write_group_external_plugin_adapter_data; +pub(crate) use add_assets_to_group::*; +pub(crate) use add_collections_to_group::*; pub(crate) use add_external_plugin_adapter::*; +pub(crate) use add_group_external_plugin_adapter::*; +pub(crate) use add_group_plugin::*; +pub(crate) use add_groups_to_group::*; pub(crate) use add_plugin::*; +pub(crate) use approve_group_plugin_authority::*; pub(crate) use approve_plugin_authority::*; pub(crate) use burn::*; +pub(crate) use close_group::*; pub(crate) use collect::*; pub(crate) use compress::*; pub(crate) use create::*; pub(crate) use create_collection::*; +pub(crate) use create_group::*; pub(crate) use decompress::*; pub(crate) use execute::*; +pub(crate) use remove_assets_from_group::*; +pub(crate) use remove_collections_from_group::*; pub(crate) use remove_external_plugin_adapter::*; +pub(crate) use remove_group_external_plugin_adapter::*; +pub(crate) use remove_group_plugin::*; +pub(crate) use remove_groups_from_group::*; pub(crate) use remove_plugin::*; +pub(crate) use revoke_group_plugin_authority::*; pub(crate) use revoke_plugin_authority::*; pub(crate) use transfer::*; pub(crate) use update::*; pub(crate) use update_collection_info::*; pub(crate) use update_external_plugin_adapter::*; +pub(crate) use update_group::*; +pub(crate) use update_group_plugin::*; pub(crate) use update_plugin::*; pub(crate) use write_external_plugin_adapter_data::*; +pub(crate) use write_group_external_plugin_adapter_data::*; use borsh::BorshDeserialize; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; @@ -152,14 +186,6 @@ pub fn process_instruction<'a>( msg!("Instruction: RemoveCollectionExternalPluginAdapter"); remove_collection_external_plugin_adapter(accounts, args) } - MplAssetInstruction::UpdateExternalPluginAdapterV1(args) => { - msg!("Instruction: UpdateExternalPluginAdapter"); - update_external_plugin_adapter(accounts, args) - } - MplAssetInstruction::UpdateCollectionExternalPluginAdapterV1(args) => { - msg!("Instruction: UpdateCollectionExternalPluginAdapter"); - update_collection_external_plugin_adapter(accounts, args) - } MplAssetInstruction::WriteExternalPluginAdapterDataV1(args) => { msg!("Instruction: WriteExternalPluginAdapterDataV1"); write_external_plugin_adapter_data(accounts, args) @@ -182,5 +208,82 @@ pub fn process_instruction<'a>( msg!("Instruction: UpdateCollectionInfoV1"); update_collection_info(accounts, args) } + + MplAssetInstruction::CreateGroupV1(args) => { + msg!("Instruction: CreateGroup"); + create_group_v1(accounts, args) + } + MplAssetInstruction::CloseGroupV1(args) => { + msg!("Instruction: CloseGroup"); + close_group_v1(accounts, args) + } + MplAssetInstruction::UpdateGroupV1(args) => { + msg!("Instruction: UpdateGroup"); + update_group_v1(accounts, args) + } + MplAssetInstruction::AddCollectionsToGroupV1(args) => { + msg!("Instruction: AddCollectionsToGroup"); + add_collections_to_group_v1(accounts, args) + } + MplAssetInstruction::RemoveCollectionsFromGroupV1(args) => { + msg!("Instruction: RemoveCollectionsFromGroup"); + remove_collections_from_group_v1(accounts, args) + } + MplAssetInstruction::AddGroupsToGroupV1(args) => { + msg!("Instruction: AddGroupsToGroup"); + add_groups_to_group_v1(accounts, args) + } + MplAssetInstruction::RemoveGroupsFromGroupV1(args) => { + msg!("Instruction: RemoveGroupsFromGroup"); + remove_groups_from_group_v1(accounts, args) + } + MplAssetInstruction::UpdateExternalPluginAdapterV1(args) => { + msg!("Instruction: UpdateExternalPluginAdapter"); + update_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::UpdateCollectionExternalPluginAdapterV1(args) => { + msg!("Instruction: UpdateCollectionExternalPluginAdapter"); + update_collection_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::AddGroupPluginV1(args) => { + msg!("Instruction: AddGroupPlugin"); + add_group_plugin(accounts, args) + } + MplAssetInstruction::RemoveGroupPluginV1(args) => { + msg!("Instruction: RemoveGroupPlugin"); + remove_group_plugin(accounts, args) + } + MplAssetInstruction::UpdateGroupPluginV1(args) => { + msg!("Instruction: UpdateGroupPlugin"); + update_group_plugin(accounts, args) + } + MplAssetInstruction::ApproveGroupPluginAuthorityV1(args) => { + msg!("Instruction: ApproveGroupPluginAuthority"); + approve_group_plugin_authority(accounts, args) + } + MplAssetInstruction::RevokeGroupPluginAuthorityV1(args) => { + msg!("Instruction: RevokeGroupPluginAuthority"); + revoke_group_plugin_authority(accounts, args) + } + MplAssetInstruction::AddGroupExternalPluginAdapterV1(args) => { + msg!("Instruction: AddGroupExternalPluginAdapter"); + add_group_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::RemoveGroupExternalPluginAdapterV1(args) => { + msg!("Instruction: RemoveGroupExternalPluginAdapter"); + remove_group_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::WriteGroupExternalPluginAdapterDataV1(args) => { + msg!("Instruction: WriteGroupExternalPluginAdapterDataV1"); + write_group_external_plugin_adapter_data(accounts, args) + } + MplAssetInstruction::AddAssetsToGroupV1(args) => { + msg!("Instruction: AddAssetsToGroup"); + add_assets_to_group_v1(accounts, args) + } + MplAssetInstruction::RemoveAssetsFromGroupV1(args) => { + msg!("Instruction: RemoveAssetsFromGroup"); + remove_assets_from_group_v1(accounts, args) + } } } diff --git a/programs/mpl-core/src/processor/remove_assets_from_group.rs b/programs/mpl-core/src/processor/remove_assets_from_group.rs new file mode 100644 index 00000000..8de4c881 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_assets_from_group.rs @@ -0,0 +1,199 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + plugins::{create_meta_idempotent, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType}, + state::{AssetV1, DataBlob, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resize_or_reallocate_account, resolve_authority}, +}; + +/// Args for RemoveAssetsFromGroupV1 +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveAssetsFromGroupV1Args { + pub(crate) assets: Vec, +} + +fn is_valid_asset_authority( + asset_info: &AccountInfo, + authority_info: &AccountInfo, +) -> ProgramResult { + use crate::plugins::{fetch_wrapped_plugin, PluginType}; + use crate::state::UpdateAuthority; + + let asset_core = AssetV1::load(asset_info, 0)?; + + if let UpdateAuthority::Address(addr) = asset_core.update_authority.clone() { + if addr == *authority_info.key { + return Ok(()); + } + } + + if let Ok((_pa, plugin)) = + fetch_wrapped_plugin::(asset_info, Some(&asset_core), PluginType::UpdateDelegate) + { + if let crate::plugins::Plugin::UpdateDelegate(update_delegate) = plugin { + if update_delegate + .additional_delegates + .contains(authority_info.key) + { + return Ok(()); + } + } + } + + Err(MplCoreError::InvalidAuthority.into()) +} + +pub(crate) fn remove_assets_from_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveAssetsFromGroupV1Args, +) -> ProgramResult { + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + let asset_accounts = &accounts[4..]; + + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + if asset_accounts.len() != args.assets.len() { + msg!("Error: account/pubkey mismatch"); + return Err(ProgramError::NotEnoughAccountKeys); + } + + let mut group = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + return Err(MplCoreError::InvalidAuthority.into()); + } + + for (i, asset_info) in asset_accounts.iter().enumerate() { + if asset_info.key != &args.assets[i] { + return Err(MplCoreError::IncorrectAccount.into()); + } + if !asset_info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + is_valid_asset_authority(asset_info, authority_info)?; + + // remove asset from group list + if let Some(pos) = group.assets.iter().position(|pk| pk == asset_info.key) { + group.assets.remove(pos); + } else { + continue; // asset not part of group: skip plugin update + } + + process_asset_groups_plugin_remove( + asset_info, + *group_info.key, + payer_info, + system_program_info, + )?; + } + + let serialized_group = group.try_to_vec()?; + if serialized_group.len() != group_info.data_len() { + resize_or_reallocate_account( + group_info, + payer_info, + system_program_info, + serialized_group.len(), + )?; + } + sol_memcpy( + &mut group_info.try_borrow_mut_data()?, + &serialized_group, + serialized_group.len(), + ); + Ok(()) +} + +fn process_asset_groups_plugin_remove<'a>( + asset_info: &AccountInfo<'a>, + parent_group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + let (_asset_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(asset_info, payer_info, system_program_info)?; + + let record_index_opt = plugin_registry + .registry + .iter() + .position(|r| r.plugin_type == PluginType::Groups); + + if let Some(index) = record_index_opt { + let record_offset = plugin_registry.registry[index].offset; + let mut plugin = Plugin::load(asset_info, record_offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if let Some(pos) = inner.groups.iter().position(|pk| pk == &parent_group) { + inner.groups.remove(pos); + } else { + return Ok(()); + } + } else { + return Err(MplCoreError::InvalidPlugin.into()); + } + + let old_data = + Plugin::deserialize(&mut &asset_info.data.borrow()[record_offset..])?.try_to_vec()?; + let new_data = plugin.try_to_vec()?; + let size_diff = new_data.len() as isize - old_data.len() as isize; + if size_diff != 0 { + plugin_registry.bump_offsets(record_offset, size_diff)?; + plugin_header.plugin_registry_offset = + (plugin_header.plugin_registry_offset as isize + size_diff) as usize; + let new_size = (asset_info.data_len() as isize + size_diff) as usize; + resize_or_reallocate_account(asset_info, payer_info, system_program_info, new_size)?; + let next_offset = (record_offset + old_data.len()) as isize; + let new_next_offset = next_offset + size_diff; + + // Copy the bytes that sit between the end of the plugin we just + // updated/removed and the start of the plugin registry. In some + // edge-cases (e.g. when the plugin we touched was the **last** + // plugin in the account), there is nothing left to move which + // would previously lead to an underflow when calculating the + // length of the range to copy. Switching to `saturating_sub` and + // gating the `sol_memmove` behind a size-check prevents the + // debug-panic `attempt to subtract with overflow` whilst keeping + // the behaviour identical for the common case. + + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = asset_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_offset as usize), + base_ptr.add(next_offset as usize), + copy_len, + ); + } + } + } + plugin_header.save(asset_info, header_offset)?; + plugin_registry.save(asset_info, plugin_header.plugin_registry_offset)?; + plugin.save(asset_info, record_offset)?; + } + Ok(()) +} diff --git a/programs/mpl-core/src/processor/remove_collections_from_group.rs b/programs/mpl-core/src/processor/remove_collections_from_group.rs new file mode 100644 index 00000000..16ee51b7 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_collections_from_group.rs @@ -0,0 +1,225 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_memory::{sol_memcpy, sol_memmove}, + pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + plugins::{ + create_meta_idempotent, initialize_plugin, Groups, Plugin, PluginHeaderV1, + PluginRegistryV1, PluginType, + }, + state::{CollectionV1, DataBlob, GroupV1, SolanaAccount}, + utils::{ + is_valid_collection_authority, is_valid_group_authority, resize_or_reallocate_account, + resolve_authority, + }, +}; + +/// Arguments for the `RemoveCollectionsFromGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveCollectionsFromGroupV1Args { + /// The list of collections to remove from the group. + pub(crate) collections: Vec, +} + +/// Processor for the `RemoveCollectionsFromGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn remove_collections_from_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveCollectionsFromGroupV1Args, +) -> ProgramResult { + // Expected account layout: + // 0. [writable] Group account + // 1. [writable, signer] Payer account (also default authority) + // 2. [signer] Optional authority (update auth/delegate) + // 3. [] System program + // 4..N [writable] Collection accounts, one for each pubkey in args.collections + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + let collection_accounts = &accounts[4..]; + + // Basic guards + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if collection_accounts.len() != args.collections.len() { + msg!( + "Error: Number of collection accounts ({}) does not match number of pubkeys in args ({}).", + collection_accounts.len(), + args.collections.len() + ); + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Deserialize group. + let mut group = GroupV1::load(group_info, 0)?; + + // Authority check: must be the group's update authority or delegate. + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + for (i, collection_info) in collection_accounts.iter().enumerate() { + if collection_info.key != &args.collections[i] { + msg!( + "Error: Collection account at position {} does not match provided pubkey list", + i + ); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !collection_info.is_writable { + msg!("Error: Collection account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + let _collection_core = CollectionV1::load(collection_info, 0)?; + + if !is_valid_collection_authority(collection_info, authority_info)? { + msg!("Error: Signer is not collection update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Remove from group.collections if present. + if let Some(pos) = group + .collections + .iter() + .position(|pk| pk == collection_info.key) + { + group.collections.remove(pos); + } else { + // If collection not present, skip plugin update. + continue; + } + + // Remove group from collection's Groups plugin. + process_collection_groups_plugin_remove( + collection_info, + *group_info.key, + payer_info, + system_program_info, + )?; + } + + // Persist group changes. + let serialized_group = group.try_to_vec()?; + if serialized_group.len() != group_info.data_len() { + resize_or_reallocate_account( + group_info, + payer_info, + system_program_info, + serialized_group.len(), + )?; + } + sol_memcpy( + &mut group_info.try_borrow_mut_data()?, + &serialized_group, + serialized_group.len(), + ); + + Ok(()) +} + +fn process_collection_groups_plugin_remove<'a>( + collection_info: &AccountInfo<'a>, + parent_group: Pubkey, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + let (_collection_core, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(collection_info, payer_info, system_program_info)?; + + let record_index_opt = plugin_registry + .registry + .iter() + .position(|r| r.plugin_type == PluginType::Groups); + + if let Some(index) = record_index_opt { + let record_offset = plugin_registry.registry[index].offset; + + let mut plugin = Plugin::load(collection_info, record_offset)?; + if let Plugin::Groups(inner) = &mut plugin { + if let Some(pos) = inner.groups.iter().position(|pk| pk == &parent_group) { + inner.groups.remove(pos); + } else { + // Group not present, nothing to remove. + return Ok(()); + } + } else { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Serialize sizes. + let old_plugin_data = + Plugin::deserialize(&mut &collection_info.data.borrow()[record_offset..])? + .try_to_vec()?; + let new_plugin_data = plugin.try_to_vec()?; + let size_diff = (new_plugin_data.len() as isize) + .checked_sub(old_plugin_data.len() as isize) + .ok_or(MplCoreError::NumericalOverflow)?; + + if size_diff != 0 { + plugin_registry.bump_offsets(record_offset, size_diff)?; + let new_registry_offset = (plugin_header.plugin_registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + let new_size = (collection_info.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)? as usize; + resize_or_reallocate_account( + collection_info, + payer_info, + system_program_info, + new_size, + )?; + + let next_plugin_offset = (record_offset + old_plugin_data.len()) as isize; + let new_next_plugin_offset = next_plugin_offset + size_diff; + + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base_ptr = collection_info.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base_ptr.add(new_next_plugin_offset as usize), + base_ptr.add(next_plugin_offset as usize), + copy_len, + ); + } + } + } + + // Save changes. + plugin_header.save(collection_info, header_offset)?; + plugin_registry.save(collection_info, plugin_header.plugin_registry_offset)?; + plugin.save(collection_info, record_offset)?; + } + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/remove_group_external_plugin_adapter.rs b/programs/mpl-core/src/processor/remove_group_external_plugin_adapter.rs new file mode 100644 index 00000000..48992941 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_group_external_plugin_adapter.rs @@ -0,0 +1,85 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{ + delete_external_plugin_adapter, fetch_wrapped_external_plugin_adapter, + ExternalPluginAdapterKey, + }, + state::{DataBlob, GroupV1, SolanaAccount}, + utils::{fetch_core_data, is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for `RemoveGroupExternalPluginAdapterV1`. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveGroupExternalPluginAdapterV1Args { + /// External plugin adapter key to remove. + pub key: ExternalPluginAdapterKey, +} + +pub(crate) fn remove_group_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveGroupExternalPluginAdapterV1Args, +) -> ProgramResult { + // Expected accounts: + // 0. [writable] Group account + // 1. [writable, signer] Payer account + // 2. [signer] Optional authority (update authority or delegate) + // 3. [] System program + // 4. [] Optional SPL Noop (log wrapper) + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + let log_wrapper_info_opt = accounts.get(4); + + // Guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + if let Some(wrapper_info) = log_wrapper_info_opt { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + // Fetch core + meta and check authority/delegate. + let (group_core, plugin_header_opt, plugin_registry_opt) = + fetch_core_data::(group_info)?; + + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Nothing to delete if no plugin meta exists. + if plugin_header_opt.is_none() || plugin_registry_opt.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + // Ensure the plugin exists. + let _ = + fetch_wrapped_external_plugin_adapter::(group_info, Some(&group_core), &args.key)?; + + // Delete plugin. + delete_external_plugin_adapter( + &args.key, + &group_core, + group_info, + payer_info, + system_program_info, + ) +} diff --git a/programs/mpl-core/src/processor/remove_group_plugin.rs b/programs/mpl-core/src/processor/remove_group_plugin.rs new file mode 100644 index 00000000..b97fb671 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_group_plugin.rs @@ -0,0 +1,80 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{delete_plugin, fetch_wrapped_plugin, PluginType}, + state::{DataBlob, GroupV1, SolanaAccount}, + utils::{fetch_core_data, is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for `RemoveGroupPluginV1`. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveGroupPluginV1Args { + pub(crate) plugin_type: PluginType, +} + +pub(crate) fn remove_group_plugin<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveGroupPluginV1Args, +) -> ProgramResult { + // Expected accounts: + // 0. [writable] Group account + // 1. [writable, signer] Payer + // 2. [signer] Optional authority (update authority or delegate) + // 3. [] System program + // 4. [] Optional log wrapper + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + if let Some(wrapper_info) = accounts.get(4) { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Load group and permission check + let group_core = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Ensure plugins initialized + let (group_core, plugin_header_opt, plugin_registry_opt) = + fetch_core_data::(group_info)?; + + if plugin_header_opt.is_none() || plugin_registry_opt.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + // Fetch the plugin to make sure it exists (also returns its authority, unused here) + let _ = fetch_wrapped_plugin::(group_info, Some(&group_core), args.plugin_type)?; + + // Remove plugin + delete_plugin::( + &args.plugin_type, + &group_core, + group_info, + payer_info, + system_program_info, + ) +} diff --git a/programs/mpl-core/src/processor/remove_groups_from_group.rs b/programs/mpl-core/src/processor/remove_groups_from_group.rs new file mode 100644 index 00000000..35fe50e8 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_groups_from_group.rs @@ -0,0 +1,152 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + program_memory::sol_memcpy, pubkey::Pubkey, +}; + +use crate::{ + error::MplCoreError, + state::{DataBlob, GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resize_or_reallocate_account, resolve_authority}, +}; + +/// Arguments for the `RemoveGroupsFromGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveGroupsFromGroupV1Args { + /// The list of child groups to remove from the parent group. + pub(crate) groups: Vec, +} + +/// Processor for the `RemoveGroupsFromGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn remove_groups_from_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveGroupsFromGroupV1Args, +) -> ProgramResult { + // Expected account layout: + // 0. [writable] Parent group account + // 1. [writable, signer] Payer account (also default authority) + // 2. [signer] Optional authority (update auth/delegate) + // 3. [] System program + // 4..N [writable] Child group accounts, one for each pubkey in args.groups + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let parent_group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + // Remaining accounts are child group accounts. + let child_group_accounts = &accounts[4..]; + + // Basic guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Validate arg count. + if child_group_accounts.len() != args.groups.len() { + msg!( + "Error: Number of group accounts ({}) does not match number of pubkeys in args ({}).", + child_group_accounts.len(), + args.groups.len() + ); + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Deserialize parent group. + let mut parent_group = GroupV1::load(parent_group_info, 0)?; + + // Authority check: must be parent group's update authority or delegate. + if !is_valid_group_authority(parent_group_info, authority_info)? { + msg!("Error: Invalid authority for parent group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Iterate child groups. + for (i, child_info) in child_group_accounts.iter().enumerate() { + if child_info.key != &args.groups[i] { + msg!( + "Error: Child group account at position {} does not match provided pubkey list", + i + ); + return Err(MplCoreError::IncorrectAccount.into()); + } + + if !child_info.is_writable { + msg!("Error: Child group account must be writable"); + return Err(ProgramError::InvalidAccountData); + } + + let mut child_group = GroupV1::load(child_info, 0)?; + + // Authority must also be update authority or delegate for the child group. + if !is_valid_group_authority(child_info, authority_info)? { + msg!("Error: Signer is not child group update authority/delegate"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Remove child from parent list if present. + if let Some(pos) = parent_group + .groups + .iter() + .position(|pk| pk == child_info.key) + { + parent_group.groups.remove(pos); + } else { + // Not present, skip modification of child. + continue; + } + + // Remove parent from child's parent_groups if present. + if let Some(pos) = child_group + .parent_groups + .iter() + .position(|pk| pk == parent_group_info.key) + { + child_group.parent_groups.remove(pos); + + // Persist child changes. + let serialized_child = child_group.try_to_vec()?; + if serialized_child.len() != child_info.data_len() { + resize_or_reallocate_account( + child_info, + payer_info, + system_program_info, + serialized_child.len(), + )?; + } + sol_memcpy( + &mut child_info.try_borrow_mut_data()?, + &serialized_child, + serialized_child.len(), + ); + } + } + + // Persist parent group changes. + let serialized_parent = parent_group.try_to_vec()?; + if serialized_parent.len() != parent_group_info.data_len() { + resize_or_reallocate_account( + parent_group_info, + payer_info, + system_program_info, + serialized_parent.len(), + )?; + } + sol_memcpy( + &mut parent_group_info.try_borrow_mut_data()?, + &serialized_parent, + serialized_parent.len(), + ); + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/remove_plugin.rs b/programs/mpl-core/src/processor/remove_plugin.rs index 9ad623c6..9a04a810 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -115,6 +115,11 @@ pub(crate) fn remove_collection_plugin<'a>( } } + // Groups plugins can only be removed via specialized Group instructions. + if args.plugin_type == PluginType::Groups { + return Err(MplCoreError::InvalidPlugin.into()); + } + let (collection, plugin_header, plugin_registry) = fetch_core_data::(ctx.accounts.collection)?; diff --git a/programs/mpl-core/src/processor/revoke_group_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_group_plugin_authority.rs new file mode 100644 index 00000000..7bab169c --- /dev/null +++ b/programs/mpl-core/src/processor/revoke_group_plugin_authority.rs @@ -0,0 +1,78 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{fetch_wrapped_plugin, revoke_authority_on_plugin, PluginType}, + state::{GroupV1, SolanaAccount}, + utils::{fetch_core_data, is_valid_group_authority, resolve_authority}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RevokeGroupPluginAuthorityV1Args { + pub plugin_type: PluginType, +} + +pub(crate) fn revoke_group_plugin_authority<'a>( + accounts: &'a [AccountInfo<'a>], + args: RevokeGroupPluginAuthorityV1Args, +) -> ProgramResult { + // Accounts: + // 0. [writable] Group + // 1. [writable, signer] Payer + // 2. [signer] Optional authority + // 3. [] System program + // 4. [] Optional log wrapper + + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + if let Some(wrapper_info) = accounts.get(4) { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Authority check (update authority or delegate) + let group = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Ensure plugin exists + let (_current_authority, _plugin) = + fetch_wrapped_plugin::(group_info, None, args.plugin_type)?; + + // Fetch plugin meta + let (_core_stub, plugin_header_opt, plugin_registry_opt) = + fetch_core_data::(group_info)?; + let plugin_header = plugin_header_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + let mut plugin_registry = plugin_registry_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + + revoke_authority_on_plugin( + &args.plugin_type, + group_info, + &plugin_header, + &mut plugin_registry, + payer_info, + system_program_info, + ) +} diff --git a/programs/mpl-core/src/processor/revoke_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_plugin_authority.rs index a5e8bfc3..5373a454 100644 --- a/programs/mpl-core/src/processor/revoke_plugin_authority.rs +++ b/programs/mpl-core/src/processor/revoke_plugin_authority.rs @@ -125,6 +125,11 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( } } + // Groups plugins must be managed only via Group-specific instructions; revoke is not allowed. + if args.plugin_type == PluginType::Groups { + return Err(MplCoreError::InvalidPlugin.into()); + } + let (collection, plugin_header, mut plugin_registry) = fetch_core_data::(ctx.accounts.collection)?; diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 99728da6..8e34eb84 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -385,17 +385,17 @@ fn process_update<'a, T: DataBlob + SolanaAccount>( resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; - // SAFETY: `borrow_mut` will always return a valid pointer. - // new_plugin_offset is derived from plugin_offset and size_diff using - // checked arithmetic, so it will always be less than or equal to account.data_len(). - // This will fail and revert state if there is a memory violation. - unsafe { - let base = account.data.borrow_mut().as_mut_ptr(); - sol_memmove( - base.add(new_plugin_offset as usize), - base.add(plugin_offset as usize), - registry_offset - plugin_offset as usize, - ); + let copy_len = (registry_offset as usize).saturating_sub(plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base = account.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base.add(new_plugin_offset as usize), + base.add(plugin_offset as usize), + copy_len, + ); + } } plugin_header.save(account, new_core_size as usize)?; diff --git a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs index 829e5a58..914aa0ce 100644 --- a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs @@ -247,17 +247,19 @@ fn process_update_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; - // SAFETY: `borrow_mut` will always return a valid pointer. - // new_next_plugin_offset is derived from next_plugin_offset and size_diff using - // checked arithmetic, so it will always be less than or equal to account.data_len(). - // This will fail and revert state if there is a memory violation. - unsafe { - let base = account.data.borrow_mut().as_mut_ptr(); - sol_memmove( - base.add(new_next_plugin_offset as usize), - base.add(next_plugin_offset as usize), - registry_offset - next_plugin_offset as usize, - ); + let copy_len = plugin_header + .plugin_registry_offset + .saturating_sub(next_plugin_offset as usize); + + if copy_len > 0 { + unsafe { + let base = account.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base.add(new_next_plugin_offset as usize), + base.add(next_plugin_offset as usize), + copy_len, + ); + } } plugin_header.save(account, core.len())?; diff --git a/programs/mpl-core/src/processor/update_group.rs b/programs/mpl-core/src/processor/update_group.rs new file mode 100644 index 00000000..06ad4924 --- /dev/null +++ b/programs/mpl-core/src/processor/update_group.rs @@ -0,0 +1,93 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy, +}; + +use crate::{ + error::MplCoreError, + instruction::accounts::UpdateGroupV1Accounts, + state::{GroupV1, SolanaAccount}, + utils::{is_valid_group_authority, resize_or_reallocate_account, resolve_authority}, +}; + +/// Arguments for the `UpdateGroupV1` instruction. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct UpdateGroupV1Args { + /// New display name for the group (optional). + pub(crate) new_name: Option, + /// New URI for the group (optional). + pub(crate) new_uri: Option, +} + +/// Processor for the `UpdateGroupV1` instruction. +#[allow(clippy::too_many_arguments)] +pub(crate) fn update_group_v1<'a>( + accounts: &'a [AccountInfo<'a>], + args: UpdateGroupV1Args, +) -> ProgramResult { + // Derive the typed account context from the raw slice. + let ctx = UpdateGroupV1Accounts::context(accounts)?; + + // Basic guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + // Ensure the canonical system program is provided. + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // Deserialize the group account. + let mut group = GroupV1::load(ctx.accounts.group, 0)?; + + // Ensure the signer is the update authority or update delegate of the group. + if !is_valid_group_authority(ctx.accounts.group, authority)? { + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Track if any field is modified. + let mut dirty = false; + + // Apply a new update authority if supplied as an account. + if let Some(new_update_authority) = ctx.accounts.new_update_authority { + group.update_authority = *new_update_authority.key; + dirty = true; + } + + // Apply inline argument changes. + if let Some(new_name) = &args.new_name { + group.name.clone_from(new_name); + dirty = true; + } + + if let Some(new_uri) = &args.new_uri { + group.uri.clone_from(new_uri); + dirty = true; + } + + // Persist state changes if anything was updated. + if dirty { + let serialized = group.try_to_vec()?; + + // Resize the account if the serialized length differs. + if serialized.len() != ctx.accounts.group.data_len() { + resize_or_reallocate_account( + ctx.accounts.group, + ctx.accounts.payer, + ctx.accounts.system_program, + serialized.len(), + )?; + } + + // Write the updated data into the account. + sol_memcpy( + &mut ctx.accounts.group.try_borrow_mut_data()?, + &serialized, + serialized.len(), + ); + } + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/update_group_plugin.rs b/programs/mpl-core/src/processor/update_group_plugin.rs new file mode 100644 index 00000000..c9615a88 --- /dev/null +++ b/programs/mpl-core/src/processor/update_group_plugin.rs @@ -0,0 +1,118 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{fetch_wrapped_plugin, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType}, + state::{DataBlob, GroupV1, SolanaAccount}, + utils::{ + fetch_core_data, is_valid_group_authority, resize_or_reallocate_account, resolve_authority, + }, +}; + +/// Args for `UpdateGroupPluginV1`. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct UpdateGroupPluginV1Args { + pub plugin: Plugin, +} + +pub(crate) fn update_group_plugin<'a>( + accounts: &'a [AccountInfo<'a>], + args: UpdateGroupPluginV1Args, +) -> ProgramResult { + if accounts.len() < 4 { + return Err(ProgramError::NotEnoughAccountKeys); + } + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let system_program_info = &accounts[3]; + + if let Some(wrapper_info) = accounts.get(4) { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + // ------------------------------------------------------------------ + // ALLOW-LIST ENFORCEMENT (same rules as AddGroupPlugin) + // ------------------------------------------------------------------ + let incoming_plugin_type = PluginType::from(&args.plugin); + + let is_allowed_plugin = matches!( + incoming_plugin_type, + PluginType::Attributes | PluginType::VerifiedCreators | PluginType::Autograph + ); + + if !is_allowed_plugin { + return Err(MplCoreError::InvalidPlugin.into()); + } + + // Authority check against group update authority or delegate + let group = GroupV1::load(group_info, 0)?; + if !is_valid_group_authority(group_info, authority_info)? { + msg!("Error: Invalid authority for group account"); + return Err(MplCoreError::InvalidAuthority.into()); + } + + // Locate existing plugin + let (_plugin_authority, _existing_plugin) = + fetch_wrapped_plugin::(group_info, None, PluginType::from(&args.plugin))?; + + // Fetch plugin meta + let (core_stub, plugin_header_opt, plugin_registry_opt) = + fetch_core_data::(group_info)?; + let mut plugin_header = plugin_header_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + let mut plugin_registry = plugin_registry_opt.ok_or(MplCoreError::PluginsNotInitialized)?; + + // Find registry record + let registry_record = plugin_registry + .registry + .iter() + .find(|r| r.plugin_type == PluginType::from(&args.plugin)) + .ok_or(MplCoreError::PluginNotFound)? + .clone(); + + // Serialize old and new plugin to calculate diff + let old_plugin = Plugin::load(group_info, registry_record.offset)?; + let old_data = old_plugin.try_to_vec()?; + let new_data = args.plugin.try_to_vec()?; + let size_diff = (new_data.len() as isize) - (old_data.len() as isize); + + // If size changes, adjust offsets and account size + if size_diff != 0 { + plugin_registry.bump_offsets(registry_record.offset, size_diff)?; + let new_registry_offset = (plugin_header.plugin_registry_offset as isize + size_diff) + .try_into() + .map_err(|_| MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset; + + let new_account_size = (group_info.data_len() as isize + size_diff) + .try_into() + .map_err(|_| MplCoreError::NumericalOverflow)?; + resize_or_reallocate_account( + group_info, + payer_info, + system_program_info, + new_account_size, + )?; + } + + // Save header, registry, and new plugin + plugin_header.save(group_info, core_stub.len())?; + plugin_registry.save(group_info, plugin_header.plugin_registry_offset)?; + args.plugin.save(group_info, registry_record.offset)?; + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index 91bd7329..a26628c8 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -112,6 +112,11 @@ pub(crate) fn update_collection_plugin<'a>( } } + // Groups plugins must be mutated only through dedicated Group instructions. + if PluginType::from(&args.plugin) == PluginType::Groups { + return Err(MplCoreError::InvalidPlugin.into()); + } + let (target_plugin_authority, _) = fetch_wrapped_plugin::( ctx.accounts.collection, None, @@ -200,17 +205,16 @@ fn process_update_plugin<'a, T: DataBlob + SolanaAccount>( resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; - // SAFETY: `borrow_mut` will always return a valid pointer. - // new_next_plugin_offset is derived from next_plugin_offset and size_diff using - // checked arithmetic, so it will always be less than or equal to account.data_len(). - // This will fail and revert state if there is a memory violation. - unsafe { - let base = account.data.borrow_mut().as_mut_ptr(); - sol_memmove( - base.add(new_next_plugin_offset as usize), - base.add(next_plugin_offset as usize), - registry_offset - (next_plugin_offset as usize), - ); + let copy_len = (registry_offset as usize).saturating_sub(next_plugin_offset as usize); + if copy_len > 0 { + unsafe { + let base = account.data.borrow_mut().as_mut_ptr(); + sol_memmove( + base.add(new_next_plugin_offset as usize), + base.add(next_plugin_offset as usize), + copy_len, + ); + } } plugin_header.save(account, core.len())?; diff --git a/programs/mpl-core/src/processor/write_group_external_plugin_adapter_data.rs b/programs/mpl-core/src/processor/write_group_external_plugin_adapter_data.rs new file mode 100644 index 00000000..cf193214 --- /dev/null +++ b/programs/mpl-core/src/processor/write_group_external_plugin_adapter_data.rs @@ -0,0 +1,135 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; + +use crate::{ + error::MplCoreError, + plugins::{ + create_meta_idempotent, fetch_wrapped_external_plugin_adapter, + initialize_external_plugin_adapter, update_external_plugin_adapter_data, AppData, + DataSectionInitInfo, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, + ExternalPluginAdapterKey, ExternalRegistryRecord, LinkedDataKey, PluginHeaderV1, + PluginRegistryV1, + }, + state::{Authority, DataBlob, GroupV1, SolanaAccount}, + utils::{fetch_core_data, is_valid_group_authority, resolve_authority}, +}; + +/// Arguments for `WriteGroupExternalPluginAdapterDataV1`. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct WriteGroupExternalPluginAdapterDataV1Args { + /// External plugin adapter key (must be `AppData`). + pub key: ExternalPluginAdapterKey, + /// Data to write. If `None`, data will be sourced from the `buffer` account. + pub data: Option>, +} + +pub(crate) fn write_group_external_plugin_adapter_data<'a>( + accounts: &'a [AccountInfo<'a>], + args: WriteGroupExternalPluginAdapterDataV1Args, +) -> ProgramResult { + // Expected accounts: + // 0. [writable] Group account + // 1. [writable, signer] Payer + // 2. [signer] Optional authority (data authority or update authority) + // 3. [optional] Buffer account containing data + // 4. [] System program + // 5. [] Optional SPL Noop + + if accounts.len() < 5 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let group_info = &accounts[0]; + let payer_info = &accounts[1]; + let authority_info_opt = accounts.get(2); + let buffer_info_opt = accounts.get(3); + let system_program_info = &accounts[4]; + let log_wrapper_info_opt = accounts.get(5); + + // Guards. + assert_signer(payer_info)?; + let authority_info = resolve_authority(payer_info, authority_info_opt)?; + + if system_program_info.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + if let Some(wrapper_info) = log_wrapper_info_opt { + if wrapper_info.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + // Only AppData plugins are supported for Groups. + match args.key { + ExternalPluginAdapterKey::AppData(_) => {} + _ => return Err(MplCoreError::UnsupportedOperation.into()), + } + + // Fetch core data for the group account. + let (group_core, mut header_opt, mut registry_opt) = fetch_core_data::(group_info)?; + + // Fetch plugin record & check existence. + let (record, plugin) = + fetch_wrapped_external_plugin_adapter::(group_info, Some(&group_core), &args.key)?; + + // Copy plugin's data authority locally to avoid holding a borrow on `plugin`. + let data_authority = match &plugin { + ExternalPluginAdapter::AppData(AppData { data_authority, .. }) => *data_authority, + _ => return Err(MplCoreError::UnsupportedOperation.into()), + }; + + // Verify authority (update authority or delegate). + if !is_valid_group_authority(group_info, authority_info)? { + let signer_key = *authority_info.key; + + let authorized = if let Authority::Address { + address: plugin_key, + } = data_authority + { + plugin_key == signer_key + } else { + false + }; + + if !authorized { + msg!("Error: Invalid authority for group account or plugin data authority"); + return Err(MplCoreError::InvalidAuthority.into()); + } + } + + // Ensure plugin meta exists. + let header = header_opt + .as_mut() + .ok_or(MplCoreError::PluginsNotInitialized)?; + let registry = registry_opt + .as_mut() + .ok_or(MplCoreError::PluginsNotInitialized)?; + + // Determine data source (avoid returning a reference to a temporary Ref). + let mut owned_data: Vec = Vec::new(); + let data_slice: &[u8] = match (args.data.as_deref(), buffer_info_opt) { + (Some(bytes), None) => bytes, + (None, Some(buffer)) => { + owned_data.extend_from_slice(&buffer.data.borrow()); + &owned_data + } + (Some(_), Some(_)) => return Err(MplCoreError::TwoDataSources.into()), + (None, None) => return Err(MplCoreError::NoDataSources.into()), + }; + + // Write data after the plugin header using existing utility. + update_external_plugin_adapter_data( + &record, + Some(&group_core), + header, + registry, + group_info, + payer_info, + system_program_info, + data_slice, + ) +} diff --git a/programs/mpl-core/src/state/group.rs b/programs/mpl-core/src/state/group.rs new file mode 100644 index 00000000..a445f8f6 --- /dev/null +++ b/programs/mpl-core/src/state/group.rs @@ -0,0 +1,182 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; +use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + +use crate::{ + error::MplCoreError, + plugins::{abstain, approve, CheckResult, ExternalPluginAdapter, Plugin, ValidationResult}, +}; + +use super::{Authority, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority}; + +/// The representation of a taxonomy group which can reference collections and other groups. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] +pub struct GroupV1 { + /// The account discriminator. + pub key: Key, // 1 + /// The update authority for the group. + pub update_authority: Pubkey, // 32 + /// The display name of the group. + pub name: String, // 4 + /// The URI that links to the off-chain JSON describing the group. + /// Same semantics as collection URI. + pub uri: String, // 4 + /// Collections that are direct children of this group. + pub collections: Vec, // 4 + 32 * N + /// Groups that are direct children of this group. + pub groups: Vec, // 4 + 32 * N + /// Groups that this group is a child of. + pub parent_groups: Vec, // 4 + 32 * N + /// Assets that are direct members of this group. + pub assets: Vec, // 4 + 32 * N +} + +impl GroupV1 { + /// The base length of a group with empty name/uri and no relationships. + const BASE_LEN: usize = 1 // Key + + 32 // Update Authority + + 4 // Name length + + 4 // URI length + + 4 // collections vec length + + 4 // groups vec length + + 4 // parent_groups vec length + + 4; // assets vec length + + /// Create a new GroupV1 instance. + pub fn new( + update_authority: Pubkey, + name: String, + uri: String, + collections: Vec, + groups: Vec, + parent_groups: Vec, + assets: Vec, + ) -> Self { + Self { + key: Key::GroupV1, + update_authority, + name, + uri, + collections, + groups, + parent_groups, + assets, + } + } + + /* + Permission & validation helpers. + Group accounts are meant to be lightweight; they do not impose permissions on + asset/collection lifecycle events. They only enforce that the update authority + (or its delegate) is the signer when the group itself is mutated. + */ + + /// Check permissions for mutating the group (add/remove/update parent/children, etc.). + pub fn check_update() -> CheckResult { + CheckResult::CanApprove + } + + /// Validate the authority for mutating the group. Approves when the authority + /// matches the group's update authority. + pub fn validate_update( + &self, + authority_info: &AccountInfo, + _plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, + ) -> Result { + if authority_info.key == &self.update_authority { + approve!() + } else { + abstain!() + } + } +} + +impl DataBlob for GroupV1 { + fn len(&self) -> usize { + Self::BASE_LEN + + self.name.len() + + self.uri.len() + + 32 * self.collections.len() + + 32 * self.groups.len() + + 32 * self.parent_groups.len() + + 32 * self.assets.len() + } +} + +impl SolanaAccount for GroupV1 { + fn key() -> Key { + Key::GroupV1 + } +} + +impl CoreAsset for GroupV1 { + fn update_authority(&self) -> UpdateAuthority { + UpdateAuthority::Address(self.update_authority) + } + + fn owner(&self) -> &Pubkey { + &self.update_authority + } +} + +/// Specifies the category of relationship a `Group` account has with another +/// account. This enum is used only for instruction input (not stored on chain) +/// to keep the `CreateGroup` API compact. +#[repr(u8)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] +pub enum RelationshipKind { + /// Relationship to a `Collection` account the group directly contains. + Collection, + /// Relationship to a child `Group` that this group directly contains. + ChildGroup, + /// Relationship to a parent `Group` that contains this group. + ParentGroup, + /// Relationship to an `Asset` account that is a direct member of the group. + Asset, +} + +/// Compact representation of a single relationship passed into +/// `CreateGroupV1Args`. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] +pub struct RelationshipEntry { + /// The kind of relationship. + pub kind: RelationshipKind, + /// The public key of the related account. + pub key: Pubkey, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_group_len() { + let groups = vec![ + GroupV1 { + key: Key::GroupV1, + update_authority: Pubkey::default(), + name: "".to_string(), + uri: "".to_string(), + collections: vec![], + groups: vec![], + parent_groups: vec![], + assets: vec![], + }, + GroupV1 { + key: Key::GroupV1, + update_authority: Pubkey::default(), + name: "test".to_string(), + uri: "test".to_string(), + collections: vec![Pubkey::new_unique(), Pubkey::new_unique()], + groups: vec![Pubkey::new_unique()], + parent_groups: vec![], + assets: vec![], + }, + ]; + for group in groups { + let serialized = group.try_to_vec().unwrap(); + assert_eq!(serialized.len(), group.len()); + } + } +} diff --git a/programs/mpl-core/src/state/mod.rs b/programs/mpl-core/src/state/mod.rs index a3a469e7..8cd1a838 100644 --- a/programs/mpl-core/src/state/mod.rs +++ b/programs/mpl-core/src/state/mod.rs @@ -26,6 +26,9 @@ pub use traits::*; mod update_authority; pub use update_authority::*; +mod group; +pub use group::*; + use borsh::{BorshDeserialize, BorshSerialize}; use num_derive::{FromPrimitive, ToPrimitive}; use solana_program::pubkey::Pubkey; @@ -100,6 +103,8 @@ pub enum Key { PluginRegistryV1, /// A discriminator indicating the collection. CollectionV1, + /// A discriminator indicating the group. + GroupV1, } impl Key { diff --git a/programs/mpl-core/src/utils/mod.rs b/programs/mpl-core/src/utils/mod.rs index 21577551..de827083 100644 --- a/programs/mpl-core/src/utils/mod.rs +++ b/programs/mpl-core/src/utils/mod.rs @@ -7,13 +7,14 @@ pub(crate) use compression::*; use crate::{ error::MplCoreError, plugins::{ - validate_external_plugin_adapter_checks, validate_plugin_checks, CheckResult, - ExternalCheckResultBits, ExternalPluginAdapter, ExternalPluginAdapterKey, + fetch_wrapped_plugin, validate_external_plugin_adapter_checks, validate_plugin_checks, + CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, HookableLifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, PluginValidationContext, RegistryRecord, ValidationResult, }, state::{ - AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority, + AssetV1, Authority, CollectionV1, CoreAsset, DataBlob, GroupV1, Key, SolanaAccount, + UpdateAuthority, }, }; use mpl_utils::assert_signer; @@ -575,3 +576,63 @@ pub(crate) fn resolve_authority<'a>( None => Ok(payer), } } + +/// Returns true if the `authority_info` represents either the update authority of the group +/// or a valid update delegate (defined by an `UpdateDelegate` plugin on the group). +pub fn is_valid_group_authority( + group_info: &AccountInfo, + authority_info: &AccountInfo, +) -> Result { + // Fast path: signer is the canonical update authority. + let group_core = GroupV1::load(group_info, 0)?; + if authority_info.key == &group_core.update_authority { + return Ok(true); + } + + // Attempt to locate an UpdateDelegate plugin on the group. + if let Ok((_plugin_authority, plugin)) = + fetch_wrapped_plugin::(group_info, Some(&group_core), PluginType::UpdateDelegate) + { + if let crate::plugins::Plugin::UpdateDelegate(update_delegate) = plugin { + // Accept if the signer is listed in additional delegates. + if update_delegate + .additional_delegates + .contains(authority_info.key) + { + return Ok(true); + } + } + } + + Ok(false) +} + +/// Returns true if the `authority_info` represents either the update authority of the collection +/// or a valid update delegate (defined by an `UpdateDelegate` plugin on the collection). +pub fn is_valid_collection_authority( + collection_info: &AccountInfo, + authority_info: &AccountInfo, +) -> Result { + let collection_core = CollectionV1::load(collection_info, 0)?; + if authority_info.key == &collection_core.update_authority { + return Ok(true); + } + + // Attempt to locate an UpdateDelegate plugin on the collection. + if let Ok((_plugin_authority, plugin)) = fetch_wrapped_plugin::( + collection_info, + Some(&collection_core), + PluginType::UpdateDelegate, + ) { + if let crate::plugins::Plugin::UpdateDelegate(update_delegate) = plugin { + if update_delegate + .additional_delegates + .contains(authority_info.key) + { + return Ok(true); + } + } + } + + Ok(false) +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..68cca1a9 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.83.0" +components = ["rustfmt", "clippy"] \ No newline at end of file