From c3b81445b131b34ce52841858cb8166baa6f72c5 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Mon, 18 Nov 2024 16:59:25 -0500 Subject: [PATCH] Adding v2 tests, minor fixes. --- clients/js/src/generated/accounts/escrowV1.ts | 33 +++ clients/js/src/generated/accounts/escrowV2.ts | 34 +++ clients/js/src/generated/accounts/recipeV1.ts | 33 +++ .../src/generated/instructions/captureV2.ts | 62 +++- .../js/src/generated/instructions/index.ts | 2 +- .../generated/instructions/initEscrowV2.ts | 11 +- .../{initRecipe.ts => initRecipeV1.ts} | 64 ++-- .../src/generated/instructions/releaseV2.ts | 62 +++- clients/js/test/{ => v1}/capture.test.ts | 4 +- clients/js/test/{ => v1}/initEscrow.test.ts | 4 +- clients/js/test/{ => v1}/initNftData.test.ts | 4 +- clients/js/test/{ => v1}/release.test.ts | 4 +- clients/js/test/v2/captureV2.test.ts | 278 ++++++++++++++++++ clients/js/test/v2/initEscrowV2.test.ts | 42 +++ clients/js/test/v2/initRecipe.test.ts | 175 +++++++++++ clients/js/test/v2/releaseV2.test.ts | 278 ++++++++++++++++++ .../rust/src/generated/accounts/escrow_v1.rs | 25 ++ .../rust/src/generated/accounts/escrow_v2.rs | 25 ++ .../rust/src/generated/accounts/recipe_v1.rs | 25 ++ .../src/generated/instructions/capture_v2.rs | 63 ++-- .../{init_recipe.rs => init_recipe_v1.rs} | 71 ++--- .../rust/src/generated/instructions/mod.rs | 4 +- .../src/generated/instructions/release_v2.rs | 63 ++-- configs/kinobi.cjs | 82 +++++- idls/mpl_hybrid.json | 2 +- .../mpl-hybrid/src/instructions/capture.rs | 4 +- .../mpl-hybrid/src/instructions/capture_v2.rs | 13 +- .../mpl-hybrid/src/instructions/release_v2.rs | 8 +- programs/mpl-hybrid/src/lib.rs | 13 +- 29 files changed, 1330 insertions(+), 158 deletions(-) rename clients/js/src/generated/instructions/{initRecipe.ts => initRecipeV1.ts} (69%) rename clients/js/test/{ => v1}/capture.test.ts (98%) rename clients/js/test/{ => v1}/initEscrow.test.ts (98%) rename clients/js/test/{ => v1}/initNftData.test.ts (98%) rename clients/js/test/{ => v1}/release.test.ts (98%) create mode 100644 clients/js/test/v2/captureV2.test.ts create mode 100644 clients/js/test/v2/initEscrowV2.test.ts create mode 100644 clients/js/test/v2/initRecipe.test.ts create mode 100644 clients/js/test/v2/releaseV2.test.ts rename clients/rust/src/generated/instructions/{init_recipe.rs => init_recipe_v1.rs} (93%) diff --git a/clients/js/src/generated/accounts/escrowV1.ts b/clients/js/src/generated/accounts/escrowV1.ts index ec31ec0..13af98c 100644 --- a/clients/js/src/generated/accounts/escrowV1.ts +++ b/clients/js/src/generated/accounts/escrowV1.ts @@ -202,3 +202,36 @@ export function getEscrowV1GpaBuilder( .deserializeUsing((account) => deserializeEscrowV1(account)) .whereField('discriminator', [26, 90, 193, 218, 188, 251, 139, 211]); } + +export function findEscrowV1Pda( + context: Pick, + seeds: { + /** The address of the collection */ + collection: PublicKey; + } +): Pda { + const programId = context.programs.getPublicKey( + 'mplHybrid', + 'MPL4o4wMzndgh8T1NVDxELQCj5UQfYTYEkabX3wNKtb' + ); + return context.eddsa.findPda(programId, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(seeds.collection), + ]); +} + +export async function fetchEscrowV1FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return fetchEscrowV1(context, findEscrowV1Pda(context, seeds), options); +} + +export async function safeFetchEscrowV1FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return safeFetchEscrowV1(context, findEscrowV1Pda(context, seeds), options); +} diff --git a/clients/js/src/generated/accounts/escrowV2.ts b/clients/js/src/generated/accounts/escrowV2.ts index 8d3c74b..7822a67 100644 --- a/clients/js/src/generated/accounts/escrowV2.ts +++ b/clients/js/src/generated/accounts/escrowV2.ts @@ -24,6 +24,7 @@ import { array, mapSerializer, publicKey as publicKeySerializer, + string, struct, u8, } from '@metaplex-foundation/umi/serializers'; @@ -140,3 +141,36 @@ export function getEscrowV2GpaBuilder( export function getEscrowV2Size(): number { return 41; } + +export function findEscrowV2Pda( + context: Pick, + seeds: { + /** The address of the authority */ + authority: PublicKey; + } +): Pda { + const programId = context.programs.getPublicKey( + 'mplHybrid', + 'MPL4o4wMzndgh8T1NVDxELQCj5UQfYTYEkabX3wNKtb' + ); + return context.eddsa.findPda(programId, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(seeds.authority), + ]); +} + +export async function fetchEscrowV2FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return fetchEscrowV2(context, findEscrowV2Pda(context, seeds), options); +} + +export async function safeFetchEscrowV2FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return safeFetchEscrowV2(context, findEscrowV2Pda(context, seeds), options); +} diff --git a/clients/js/src/generated/accounts/recipeV1.ts b/clients/js/src/generated/accounts/recipeV1.ts index 30fdce9..4d4136c 100644 --- a/clients/js/src/generated/accounts/recipeV1.ts +++ b/clients/js/src/generated/accounts/recipeV1.ts @@ -212,3 +212,36 @@ export function getRecipeV1GpaBuilder( .deserializeUsing((account) => deserializeRecipeV1(account)) .whereField('discriminator', [137, 249, 37, 80, 19, 50, 78, 169]); } + +export function findRecipeV1Pda( + context: Pick, + seeds: { + /** The address of the collection */ + collection: PublicKey; + } +): Pda { + const programId = context.programs.getPublicKey( + 'mplHybrid', + 'MPL4o4wMzndgh8T1NVDxELQCj5UQfYTYEkabX3wNKtb' + ); + return context.eddsa.findPda(programId, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(seeds.collection), + ]); +} + +export async function fetchRecipeV1FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return fetchRecipeV1(context, findRecipeV1Pda(context, seeds), options); +} + +export async function safeFetchRecipeV1FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return safeFetchRecipeV1(context, findRecipeV1Pda(context, seeds), options); +} diff --git a/clients/js/src/generated/instructions/captureV2.ts b/clients/js/src/generated/instructions/captureV2.ts index 465bc3c..7740b16 100644 --- a/clients/js/src/generated/instructions/captureV2.ts +++ b/clients/js/src/generated/instructions/captureV2.ts @@ -6,12 +6,14 @@ * @see https://github.com/metaplex-foundation/kinobi */ +import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox'; import { Context, Pda, PublicKey, Signer, TransactionBuilder, + publicKey, transactionBuilder, } from '@metaplex-foundation/umi'; import { @@ -24,28 +26,29 @@ import { import { ResolvedAccount, ResolvedAccountsWithIndices, + expectPublicKey, getAccountMetasAndSigners, } from '../shared'; // Accounts. export type CaptureV2InstructionAccounts = { owner: Signer; - authority?: Signer; + authority?: PublicKey | Pda | Signer; recipe: PublicKey | Pda; escrow: PublicKey | Pda; asset: PublicKey | Pda; collection: PublicKey | Pda; - userTokenAccount: PublicKey | Pda; - escrowTokenAccount: PublicKey | Pda; + userTokenAccount?: PublicKey | Pda; + escrowTokenAccount?: PublicKey | Pda; token: PublicKey | Pda; - feeTokenAccount: PublicKey | Pda; - feeSolAccount: PublicKey | Pda; + feeTokenAccount?: PublicKey | Pda; + feeSolAccount?: PublicKey | Pda; feeProjectAccount: PublicKey | Pda; - recentBlockhashes: PublicKey | Pda; - mplCore: PublicKey | Pda; + recentBlockhashes?: PublicKey | Pda; + mplCore?: PublicKey | Pda; systemProgram?: PublicKey | Pda; tokenProgram?: PublicKey | Pda; - associatedTokenProgram: PublicKey | Pda; + associatedTokenProgram?: PublicKey | Pda; }; // Data. @@ -75,7 +78,7 @@ export function getCaptureV2InstructionDataSerializer(): Serializer< // Instruction. export function captureV2( - context: Pick, + context: Pick, input: CaptureV2InstructionAccounts ): TransactionBuilder { // Program ID. @@ -177,6 +180,42 @@ export function captureV2( if (!resolvedAccounts.authority.value) { resolvedAccounts.authority.value = context.identity; } + if (!resolvedAccounts.userTokenAccount.value) { + resolvedAccounts.userTokenAccount.value = findAssociatedTokenPda(context, { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.owner.value), + }); + } + if (!resolvedAccounts.escrowTokenAccount.value) { + resolvedAccounts.escrowTokenAccount.value = findAssociatedTokenPda( + context, + { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.escrow.value), + } + ); + } + if (!resolvedAccounts.feeTokenAccount.value) { + resolvedAccounts.feeTokenAccount.value = findAssociatedTokenPda(context, { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.feeProjectAccount.value), + }); + } + if (!resolvedAccounts.feeSolAccount.value) { + resolvedAccounts.feeSolAccount.value = publicKey( + 'GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3' + ); + } + if (!resolvedAccounts.recentBlockhashes.value) { + resolvedAccounts.recentBlockhashes.value = publicKey( + 'SysvarS1otHashes111111111111111111111111111' + ); + } + if (!resolvedAccounts.mplCore.value) { + resolvedAccounts.mplCore.value = publicKey( + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', @@ -191,6 +230,11 @@ export function captureV2( ); resolvedAccounts.tokenProgram.isWritable = false; } + if (!resolvedAccounts.associatedTokenProgram.value) { + resolvedAccounts.associatedTokenProgram.value = publicKey( + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' + ); + } // Accounts in order. const orderedAccounts: ResolvedAccount[] = Object.values( diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 13441b9..28b0ca5 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -11,7 +11,7 @@ export * from './captureV2'; export * from './initEscrowV1'; export * from './initEscrowV2'; export * from './initNftDataV1'; -export * from './initRecipe'; +export * from './initRecipeV1'; export * from './migrateNftV1'; export * from './migrateTokensV1'; export * from './releaseV1'; diff --git a/clients/js/src/generated/instructions/initEscrowV2.ts b/clients/js/src/generated/instructions/initEscrowV2.ts index 88e4a56..210e017 100644 --- a/clients/js/src/generated/instructions/initEscrowV2.ts +++ b/clients/js/src/generated/instructions/initEscrowV2.ts @@ -21,15 +21,17 @@ import { struct, u8, } from '@metaplex-foundation/umi/serializers'; +import { findEscrowV2Pda } from '../accounts'; import { ResolvedAccount, ResolvedAccountsWithIndices, + expectPublicKey, getAccountMetasAndSigners, } from '../shared'; // Accounts. export type InitEscrowV2InstructionAccounts = { - escrow: PublicKey | Pda; + escrow?: PublicKey | Pda; authority?: Signer; systemProgram?: PublicKey | Pda; }; @@ -61,7 +63,7 @@ export function getInitEscrowV2InstructionDataSerializer(): Serializer< // Instruction. export function initEscrowV2( - context: Pick, + context: Pick, input: InitEscrowV2InstructionAccounts ): TransactionBuilder { // Program ID. @@ -93,6 +95,11 @@ export function initEscrowV2( if (!resolvedAccounts.authority.value) { resolvedAccounts.authority.value = context.identity; } + if (!resolvedAccounts.escrow.value) { + resolvedAccounts.escrow.value = findEscrowV2Pda(context, { + authority: expectPublicKey(resolvedAccounts.authority.value), + }); + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', diff --git a/clients/js/src/generated/instructions/initRecipe.ts b/clients/js/src/generated/instructions/initRecipeV1.ts similarity index 69% rename from clients/js/src/generated/instructions/initRecipe.ts rename to clients/js/src/generated/instructions/initRecipeV1.ts index 66289cb..1caf07e 100644 --- a/clients/js/src/generated/instructions/initRecipe.ts +++ b/clients/js/src/generated/instructions/initRecipeV1.ts @@ -6,12 +6,14 @@ * @see https://github.com/metaplex-foundation/kinobi */ +import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox'; import { Context, Pda, PublicKey, Signer, TransactionBuilder, + publicKey, transactionBuilder, } from '@metaplex-foundation/umi'; import { @@ -24,28 +26,30 @@ import { u64, u8, } from '@metaplex-foundation/umi/serializers'; +import { findRecipeV1Pda } from '../accounts'; import { ResolvedAccount, ResolvedAccountsWithIndices, + expectPublicKey, getAccountMetasAndSigners, } from '../shared'; // Accounts. -export type InitRecipeInstructionAccounts = { - recipe: PublicKey | Pda; +export type InitRecipeV1InstructionAccounts = { + recipe?: PublicKey | Pda; authority?: Signer; collection: PublicKey | Pda; token: PublicKey | Pda; feeLocation: PublicKey | Pda; /** The ATA for token fees to be stored */ - feeAta: PublicKey | Pda; + feeAta?: PublicKey | Pda; systemProgram?: PublicKey | Pda; tokenProgram?: PublicKey | Pda; - associatedTokenProgram: PublicKey | Pda; + associatedTokenProgram?: PublicKey | Pda; }; // Data. -export type InitRecipeInstructionData = { +export type InitRecipeV1InstructionData = { discriminator: Array; name: string; uri: string; @@ -59,7 +63,7 @@ export type InitRecipeInstructionData = { path: number; }; -export type InitRecipeInstructionDataArgs = { +export type InitRecipeV1InstructionDataArgs = { name: string; uri: string; max: number | bigint; @@ -72,16 +76,16 @@ export type InitRecipeInstructionDataArgs = { path: number; }; -export function getInitRecipeInstructionDataSerializer(): Serializer< - InitRecipeInstructionDataArgs, - InitRecipeInstructionData +export function getInitRecipeV1InstructionDataSerializer(): Serializer< + InitRecipeV1InstructionDataArgs, + InitRecipeV1InstructionData > { return mapSerializer< - InitRecipeInstructionDataArgs, + InitRecipeV1InstructionDataArgs, any, - InitRecipeInstructionData + InitRecipeV1InstructionData >( - struct( + struct( [ ['discriminator', array(u8(), { size: 8 })], ['name', string()], @@ -95,22 +99,22 @@ export function getInitRecipeInstructionDataSerializer(): Serializer< ['solFeeAmountRelease', u64()], ['path', u16()], ], - { description: 'InitRecipeInstructionData' } + { description: 'InitRecipeV1InstructionData' } ), (value) => ({ ...value, - discriminator: [196, 35, 249, 242, 64, 106, 51, 53], + discriminator: [212, 22, 246, 254, 234, 63, 108, 246], }) - ) as Serializer; + ) as Serializer; } // Args. -export type InitRecipeInstructionArgs = InitRecipeInstructionDataArgs; +export type InitRecipeV1InstructionArgs = InitRecipeV1InstructionDataArgs; // Instruction. -export function initRecipe( - context: Pick, - input: InitRecipeInstructionAccounts & InitRecipeInstructionArgs +export function initRecipeV1( + context: Pick, + input: InitRecipeV1InstructionAccounts & InitRecipeV1InstructionArgs ): TransactionBuilder { // Program ID. const programId = context.programs.getPublicKey( @@ -168,12 +172,23 @@ export function initRecipe( } satisfies ResolvedAccountsWithIndices; // Arguments. - const resolvedArgs: InitRecipeInstructionArgs = { ...input }; + const resolvedArgs: InitRecipeV1InstructionArgs = { ...input }; // Default values. + if (!resolvedAccounts.recipe.value) { + resolvedAccounts.recipe.value = findRecipeV1Pda(context, { + collection: expectPublicKey(resolvedAccounts.collection.value), + }); + } if (!resolvedAccounts.authority.value) { resolvedAccounts.authority.value = context.identity; } + if (!resolvedAccounts.feeAta.value) { + resolvedAccounts.feeAta.value = findAssociatedTokenPda(context, { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.feeLocation.value), + }); + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', @@ -188,6 +203,11 @@ export function initRecipe( ); resolvedAccounts.tokenProgram.isWritable = false; } + if (!resolvedAccounts.associatedTokenProgram.value) { + resolvedAccounts.associatedTokenProgram.value = publicKey( + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' + ); + } // Accounts in order. const orderedAccounts: ResolvedAccount[] = Object.values( @@ -202,8 +222,8 @@ export function initRecipe( ); // Data. - const data = getInitRecipeInstructionDataSerializer().serialize( - resolvedArgs as InitRecipeInstructionDataArgs + const data = getInitRecipeV1InstructionDataSerializer().serialize( + resolvedArgs as InitRecipeV1InstructionDataArgs ); // Bytes Created On Chain. diff --git a/clients/js/src/generated/instructions/releaseV2.ts b/clients/js/src/generated/instructions/releaseV2.ts index 91be222..cbd78af 100644 --- a/clients/js/src/generated/instructions/releaseV2.ts +++ b/clients/js/src/generated/instructions/releaseV2.ts @@ -6,12 +6,14 @@ * @see https://github.com/metaplex-foundation/kinobi */ +import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox'; import { Context, Pda, PublicKey, Signer, TransactionBuilder, + publicKey, transactionBuilder, } from '@metaplex-foundation/umi'; import { @@ -24,28 +26,29 @@ import { import { ResolvedAccount, ResolvedAccountsWithIndices, + expectPublicKey, getAccountMetasAndSigners, } from '../shared'; // Accounts. export type ReleaseV2InstructionAccounts = { owner: Signer; - authority?: Signer; + authority?: PublicKey | Pda | Signer; recipe: PublicKey | Pda; escrow: PublicKey | Pda; asset: PublicKey | Pda; collection: PublicKey | Pda; - userTokenAccount: PublicKey | Pda; - escrowTokenAccount: PublicKey | Pda; + userTokenAccount?: PublicKey | Pda; + escrowTokenAccount?: PublicKey | Pda; token: PublicKey | Pda; - feeTokenAccount: PublicKey | Pda; - feeSolAccount: PublicKey | Pda; + feeTokenAccount?: PublicKey | Pda; + feeSolAccount?: PublicKey | Pda; feeProjectAccount: PublicKey | Pda; - recentBlockhashes: PublicKey | Pda; - mplCore: PublicKey | Pda; + recentBlockhashes?: PublicKey | Pda; + mplCore?: PublicKey | Pda; systemProgram?: PublicKey | Pda; tokenProgram?: PublicKey | Pda; - associatedTokenProgram: PublicKey | Pda; + associatedTokenProgram?: PublicKey | Pda; }; // Data. @@ -75,7 +78,7 @@ export function getReleaseV2InstructionDataSerializer(): Serializer< // Instruction. export function releaseV2( - context: Pick, + context: Pick, input: ReleaseV2InstructionAccounts ): TransactionBuilder { // Program ID. @@ -177,6 +180,42 @@ export function releaseV2( if (!resolvedAccounts.authority.value) { resolvedAccounts.authority.value = context.identity; } + if (!resolvedAccounts.userTokenAccount.value) { + resolvedAccounts.userTokenAccount.value = findAssociatedTokenPda(context, { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.owner.value), + }); + } + if (!resolvedAccounts.escrowTokenAccount.value) { + resolvedAccounts.escrowTokenAccount.value = findAssociatedTokenPda( + context, + { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.escrow.value), + } + ); + } + if (!resolvedAccounts.feeTokenAccount.value) { + resolvedAccounts.feeTokenAccount.value = findAssociatedTokenPda(context, { + mint: expectPublicKey(resolvedAccounts.token.value), + owner: expectPublicKey(resolvedAccounts.feeProjectAccount.value), + }); + } + if (!resolvedAccounts.feeSolAccount.value) { + resolvedAccounts.feeSolAccount.value = publicKey( + 'GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3' + ); + } + if (!resolvedAccounts.recentBlockhashes.value) { + resolvedAccounts.recentBlockhashes.value = publicKey( + 'SysvarS1otHashes111111111111111111111111111' + ); + } + if (!resolvedAccounts.mplCore.value) { + resolvedAccounts.mplCore.value = publicKey( + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', @@ -191,6 +230,11 @@ export function releaseV2( ); resolvedAccounts.tokenProgram.isWritable = false; } + if (!resolvedAccounts.associatedTokenProgram.value) { + resolvedAccounts.associatedTokenProgram.value = publicKey( + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' + ); + } // Accounts in order. const orderedAccounts: ResolvedAccount[] = Object.values( diff --git a/clients/js/test/capture.test.ts b/clients/js/test/v1/capture.test.ts similarity index 98% rename from clients/js/test/capture.test.ts rename to clients/js/test/v1/capture.test.ts index d60ae93..80bcad3 100644 --- a/clients/js/test/capture.test.ts +++ b/clients/js/test/v1/capture.test.ts @@ -22,8 +22,8 @@ import { initEscrowV1, MPL_HYBRID_PROGRAM_ID, Path, -} from '../src'; -import { createCoreCollection, createUmi } from './_setup'; +} from '../../src'; +import { createCoreCollection, createUmi } from '../_setup'; test('it can swap tokens for an asset', async (t) => { // Given a Umi instance using the project's plugin. diff --git a/clients/js/test/initEscrow.test.ts b/clients/js/test/v1/initEscrow.test.ts similarity index 98% rename from clients/js/test/initEscrow.test.ts rename to clients/js/test/v1/initEscrow.test.ts index ba02fe9..bc1cb05 100644 --- a/clients/js/test/initEscrow.test.ts +++ b/clients/js/test/v1/initEscrow.test.ts @@ -9,14 +9,14 @@ import { findAssociatedTokenPda, SPL_ASSOCIATED_TOKEN_PROGRAM_ID, } from '@metaplex-foundation/mpl-toolbox'; -import { createCoreCollection, createUmi } from './_setup'; +import { createCoreCollection, createUmi } from '../_setup'; import { EscrowV1, fetchEscrowV1, initEscrowV1, MPL_HYBRID_PROGRAM_ID, Path, -} from '../src'; +} from '../../src'; test('it can initialize the escrow', async (t) => { // Given a Umi instance using the project's plugin. diff --git a/clients/js/test/initNftData.test.ts b/clients/js/test/v1/initNftData.test.ts similarity index 98% rename from clients/js/test/initNftData.test.ts rename to clients/js/test/v1/initNftData.test.ts index 2379ad5..ba63881 100644 --- a/clients/js/test/initNftData.test.ts +++ b/clients/js/test/v1/initNftData.test.ts @@ -5,14 +5,14 @@ import { publicKey as publicKeySerializer, } from '@metaplex-foundation/umi/serializers'; import { createFungible } from '@metaplex-foundation/mpl-token-metadata'; -import { createCoreCollection, createUmi } from './_setup'; +import { createCoreCollection, createUmi } from '../_setup'; import { fetchNftDataV1, initNftDataV1, MPL_HYBRID_PROGRAM_ID, NftDataV1, Path, -} from '../src'; +} from '../../src'; test('it can initialize the nft data', async (t) => { // Given a Umi instance using the project's plugin. diff --git a/clients/js/test/release.test.ts b/clients/js/test/v1/release.test.ts similarity index 98% rename from clients/js/test/release.test.ts rename to clients/js/test/v1/release.test.ts index 973dc79..8aa5495 100644 --- a/clients/js/test/release.test.ts +++ b/clients/js/test/v1/release.test.ts @@ -18,8 +18,8 @@ import { MPL_HYBRID_PROGRAM_ID, Path, releaseV1, -} from '../src'; -import { createCoreCollection, createUmi } from './_setup'; +} from '../../src'; +import { createCoreCollection, createUmi } from '../_setup'; test('it can swap an asset for tokens', async (t) => { // Given a Umi instance using the project's plugin. diff --git a/clients/js/test/v2/captureV2.test.ts b/clients/js/test/v2/captureV2.test.ts new file mode 100644 index 0000000..ef23c17 --- /dev/null +++ b/clients/js/test/v2/captureV2.test.ts @@ -0,0 +1,278 @@ +import test from 'ava'; +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import { + createFungible, + fetchDigitalAssetWithAssociatedToken, + mintV1, + TokenStandard, +} from '@metaplex-foundation/mpl-token-metadata'; +import { + string, + publicKey as publicKeySerializer, +} from '@metaplex-foundation/umi/serializers'; +import { + addCollectionPlugin, + fetchAsset, + transfer, +} from '@metaplex-foundation/mpl-core'; +import { + captureV2, + EscrowV2, + fetchEscrowV2, + fetchRecipeV1, + initEscrowV2, + initRecipeV1, + MPL_HYBRID_PROGRAM_ID, + Path, +} from '../../src'; +import { createCoreCollection, createUmi } from '../_setup'; + +test('it can swap tokens for an asset', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { assets, collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + await mintV1(umi, { + mint: tokenMint.publicKey, + tokenStandard: TokenStandard.Fungible, + tokenOwner: umi.identity.publicKey, + amount: 1000, + }).sendAndConfirm(umi); + + await initEscrowV2(umi, {}).sendAndConfirm(umi); + + const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(umi.identity.publicKey), + ]); + + t.like(await fetchEscrowV2(umi, escrow), { + authority: umi.identity.publicKey, + bump: escrow[1], + }); + + // Transfer the assets to the escrow. + // eslint-disable-next-line no-restricted-syntax + for (const asset of assets) { + // eslint-disable-next-line no-await-in-loop + await transfer(umi, { + asset, + collection, + newOwner: escrow, + }).sendAndConfirm(umi); + } + + const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(collection.publicKey), + ]); + + await initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9, + min: 0, + amount: 5, + feeAmountCapture: 1, + feeAmountRelease: 1, + solFeeAmountCapture: 890_880n, + solFeeAmountRelease: 100_000n, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + t.like(await fetchRecipeV1(umi, recipe), { + publicKey: publicKey(recipe), + collection: collection.publicKey, + authority: umi.identity.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9n, + min: 0n, + amount: 5n, + feeAmountCapture: 1n, + feeAmountRelease: 1n, + solFeeAmountCapture: 890_880n, + solFeeAmountRelease: 100_000n, + count: 1n, + path: Path.RerollMetadata, + bump: recipe[1], + }); + + const userTokenBefore = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.deepEqual(userTokenBefore.token.amount, 1000n); + try { + await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.fail('Escrow token account should not exist'); + } catch (e) { + t.is(e.name, 'AccountNotFoundError'); + } + + const assetBefore = await fetchAsset(umi, assets[0].publicKey); + t.is(assetBefore.owner, publicKey(escrow)); + + await captureV2(umi, { + owner: umi.identity, + authority: umi.identity, + recipe, + escrow, + asset: assets[0].publicKey, + collection: collection.publicKey, + feeProjectAccount: feeLocation.publicKey, + token: tokenMint.publicKey, + }).sendAndConfirm(umi); + + const escrowTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.deepEqual(escrowTokenAfter.token.amount, 5n); + const userTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.deepEqual(userTokenAfter.token.amount, 994n); + const feeTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + feeLocation.publicKey + ); + t.deepEqual(feeTokenAfter.token.amount, 1n); + const assetAfter = await fetchAsset(umi, assets[0].publicKey); + t.is(assetAfter.owner, umi.identity.publicKey); +}); + +test('it can swap tokens for an asset as UpdateDelegate', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { assets, collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + await mintV1(umi, { + mint: tokenMint.publicKey, + tokenStandard: TokenStandard.Fungible, + tokenOwner: umi.identity.publicKey, + amount: 1000, + }).sendAndConfirm(umi); + + await initEscrowV2(umi, {}).sendAndConfirm(umi); + + const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(umi.identity.publicKey), + ]); + + t.like(await fetchEscrowV2(umi, escrow), { + authority: umi.identity.publicKey, + bump: escrow[1], + }); + + // Transfer the assets to the escrow. + // eslint-disable-next-line no-restricted-syntax + for (const asset of assets) { + // eslint-disable-next-line no-await-in-loop + await transfer(umi, { + asset, + collection, + newOwner: escrow, + }).sendAndConfirm(umi); + } + + const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(collection.publicKey), + ]); + + await initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9, + min: 0, + amount: 5, + feeAmountCapture: 1, + feeAmountRelease: 1, + solFeeAmountCapture: 890_880n, + solFeeAmountRelease: 100_000n, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + await addCollectionPlugin(umi, { + collection: collection.publicKey, + plugin: { + type: 'UpdateDelegate', + additionalDelegates: [], + authority: { type: 'Address', address: publicKey(recipe) }, + }, + }).sendAndConfirm(umi); + + t.like(await fetchRecipeV1(umi, recipe), { + publicKey: publicKey(recipe), + collection: collection.publicKey, + authority: umi.identity.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9n, + min: 0n, + amount: 5n, + feeAmountCapture: 1n, + feeAmountRelease: 1n, + solFeeAmountCapture: 890_880n, + solFeeAmountRelease: 100_000n, + count: 1n, + path: Path.RerollMetadata, + bump: recipe[1], + }); + + await captureV2(umi, { + owner: umi.identity, + authority: recipe, + recipe, + escrow, + asset: assets[0].publicKey, + collection: collection.publicKey, + feeProjectAccount: feeLocation.publicKey, + token: tokenMint.publicKey, + }).sendAndConfirm(umi); +}); diff --git a/clients/js/test/v2/initEscrowV2.test.ts b/clients/js/test/v2/initEscrowV2.test.ts new file mode 100644 index 0000000..576ef9b --- /dev/null +++ b/clients/js/test/v2/initEscrowV2.test.ts @@ -0,0 +1,42 @@ +import test from 'ava'; +import { generateSigner } from '@metaplex-foundation/umi'; +import { + string, + publicKey as publicKeySerializer, +} from '@metaplex-foundation/umi/serializers'; +import { createFungible } from '@metaplex-foundation/mpl-token-metadata'; +import { createUmi } from '../_setup'; +import { + EscrowV2, + fetchEscrowV2, + initEscrowV2, + MPL_HYBRID_PROGRAM_ID, +} from '../../src'; + +test('it can initialize the escrow', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(umi.identity.publicKey), + ]); + + await initEscrowV2(umi, {}).sendAndConfirm(umi); + + t.like(await fetchEscrowV2(umi, escrow), { + authority: umi.identity.publicKey, + bump: escrow[1], + }); +}); diff --git a/clients/js/test/v2/initRecipe.test.ts b/clients/js/test/v2/initRecipe.test.ts new file mode 100644 index 0000000..7c48448 --- /dev/null +++ b/clients/js/test/v2/initRecipe.test.ts @@ -0,0 +1,175 @@ +import test from 'ava'; +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import { + string, + publicKey as publicKeySerializer, +} from '@metaplex-foundation/umi/serializers'; +import { createFungible } from '@metaplex-foundation/mpl-token-metadata'; +import { createCoreCollection, createUmi } from '../_setup'; +import { + fetchRecipeV1, + initRecipeV1, + MPL_HYBRID_PROGRAM_ID, + Path, +} from '../../src'; + +test('it can initialize the recipe', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(collection.publicKey), + ]); + + await initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 2, + min: 1, + amount: 3, + feeAmountCapture: 4, + feeAmountRelease: 6, + solFeeAmountCapture: 7, + solFeeAmountRelease: 8, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + t.like(await fetchRecipeV1(umi, recipe), { + publicKey: publicKey(recipe), + collection: collection.publicKey, + authority: umi.identity.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 2n, + min: 1n, + amount: 3n, + feeAmountCapture: 4n, + feeAmountRelease: 6n, + solFeeAmountCapture: 7n, + solFeeAmountRelease: 8n, + count: 1n, + path: Path.RerollMetadata, + bump: recipe[1], + }); +}); + +test('it cannot use an invalid collection', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const collection = generateSigner(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + const result = initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 2, + min: 1, + amount: 3, + feeAmountCapture: 4, + feeAmountRelease: 6, + solFeeAmountCapture: 7, + solFeeAmountRelease: 8, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidCollectionAccount' }); +}); + +test('it cannot use an invalid token mint', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + + const result = initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 2, + min: 1, + amount: 3, + feeAmountCapture: 4, + feeAmountRelease: 6, + solFeeAmountCapture: 7, + solFeeAmountRelease: 8, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { + message: + /AnchorError caused by account: token. Error Code: AccountNotInitialized./, + }); +}); + +test('it cannot set min higher than max', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + const result = initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 1, + min: 2, + amount: 3, + feeAmountCapture: 4, + feeAmountRelease: 6, + solFeeAmountCapture: 7, + solFeeAmountRelease: 8, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'MaxMustBeGreaterThanMin' }); +}); + diff --git a/clients/js/test/v2/releaseV2.test.ts b/clients/js/test/v2/releaseV2.test.ts new file mode 100644 index 0000000..fe72d5e --- /dev/null +++ b/clients/js/test/v2/releaseV2.test.ts @@ -0,0 +1,278 @@ +import test from 'ava'; +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; +import { + createFungible, + fetchDigitalAssetWithAssociatedToken, + mintV1, + TokenStandard, +} from '@metaplex-foundation/mpl-token-metadata'; +import { + string, + publicKey as publicKeySerializer, +} from '@metaplex-foundation/umi/serializers'; +import { addCollectionPlugin, fetchAsset } from '@metaplex-foundation/mpl-core'; +import { + EscrowV2, + fetchEscrowV2, + fetchRecipeV1, + initEscrowV2, + initRecipeV1, + MPL_HYBRID_PROGRAM_ID, + Path, + releaseV2, +} from '../../src'; +import { createCoreCollection, createUmi } from '../_setup'; + +test('it can swap an asset for tokens', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { assets, collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + await initEscrowV2(umi, {}).sendAndConfirm(umi); + + const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(umi.identity.publicKey), + ]); + + t.like(await fetchEscrowV2(umi, escrow), { + authority: umi.identity.publicKey, + bump: escrow[1], + }); + + await mintV1(umi, { + mint: tokenMint.publicKey, + tokenStandard: TokenStandard.Fungible, + tokenOwner: escrow, + amount: 1000, + }).sendAndConfirm(umi); + + await initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9, + min: 0, + amount: 5, + feeAmountCapture: 1, + feeAmountRelease: 0, + solFeeAmountCapture: 100_000n, + solFeeAmountRelease: 890_880n, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(collection.publicKey), + ]); + + t.like(await fetchRecipeV1(umi, recipe), { + publicKey: publicKey(recipe), + collection: collection.publicKey, + authority: umi.identity.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9n, + min: 0n, + amount: 5n, + feeAmountCapture: 1n, + feeAmountRelease: 0n, + solFeeAmountCapture: 100_000n, + solFeeAmountRelease: 890_880n, + count: 1n, + path: Path.RerollMetadata, + bump: recipe[1], + }); + + const escrowTokenBefore = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.deepEqual(escrowTokenBefore.token.amount, 1000n); + try { + await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.fail('User token account should not exist'); + } catch (e) { + t.is(e.name, 'AccountNotFoundError'); + } + + t.is(assets[0].owner, umi.identity.publicKey); + + await releaseV2(umi, { + owner: umi.identity, + recipe, + escrow, + asset: assets[0].publicKey, + collection: collection.publicKey, + feeProjectAccount: feeLocation.publicKey, + token: tokenMint.publicKey, + }).sendAndConfirm(umi); + + const escrowTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.deepEqual(escrowTokenAfter.token.amount, 995n); + const userTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.deepEqual(userTokenAfter.token.amount, 5n); + const assetAfter = await fetchAsset(umi, assets[0].publicKey); + t.is(assetAfter.owner, publicKey(escrow)); +}); + +test('it can swap an asset for tokens as UpdateDelegate', async (t) => { + // Given a Umi instance using the project's plugin. + const umi = await createUmi(); + const feeLocation = generateSigner(umi); + const { assets, collection } = await createCoreCollection(umi); + const tokenMint = generateSigner(umi); + await createFungible(umi, { + name: 'Test Token', + uri: 'www.fungible.com', + sellerFeeBasisPoints: { + basisPoints: 0n, + identifier: '%', + decimals: 2, + }, + mint: tokenMint, + }).sendAndConfirm(umi); + + await initEscrowV2(umi, {}).sendAndConfirm(umi); + + const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('escrow'), + publicKeySerializer().serialize(umi.identity.publicKey), + ]); + + t.like(await fetchEscrowV2(umi, escrow), { + authority: umi.identity.publicKey, + bump: escrow[1], + }); + + await mintV1(umi, { + mint: tokenMint.publicKey, + tokenStandard: TokenStandard.Fungible, + tokenOwner: escrow, + amount: 1000, + }).sendAndConfirm(umi); + + await initRecipeV1(umi, { + collection: collection.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9, + min: 0, + amount: 5, + feeAmountCapture: 1, + feeAmountRelease: 0, + solFeeAmountCapture: 100_000n, + solFeeAmountRelease: 890_880n, + path: Path.RerollMetadata, + }).sendAndConfirm(umi); + + const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [ + string({ size: 'variable' }).serialize('recipe'), + publicKeySerializer().serialize(collection.publicKey), + ]); + + await addCollectionPlugin(umi, { + collection: collection.publicKey, + plugin: { + type: 'UpdateDelegate', + additionalDelegates: [], + authority: { type: 'Address', address: publicKey(recipe) }, + }, + }).sendAndConfirm(umi); + + t.like(await fetchRecipeV1(umi, recipe), { + publicKey: publicKey(recipe), + collection: collection.publicKey, + authority: umi.identity.publicKey, + token: tokenMint.publicKey, + feeLocation: feeLocation.publicKey, + name: 'Test Escrow', + uri: 'www.test.com', + max: 9n, + min: 0n, + amount: 5n, + feeAmountCapture: 1n, + feeAmountRelease: 0n, + solFeeAmountCapture: 100_000n, + solFeeAmountRelease: 890_880n, + count: 1n, + path: Path.RerollMetadata, + bump: recipe[1], + }); + + const escrowTokenBefore = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.deepEqual(escrowTokenBefore.token.amount, 1000n); + try { + await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.fail('User token account should not exist'); + } catch (e) { + t.is(e.name, 'AccountNotFoundError'); + } + + t.is(assets[0].owner, umi.identity.publicKey); + + await releaseV2(umi, { + owner: umi.identity, + authority: recipe, + recipe, + escrow, + asset: assets[0].publicKey, + collection: collection.publicKey, + feeProjectAccount: feeLocation.publicKey, + token: tokenMint.publicKey, + }).sendAndConfirm(umi); + + const escrowTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + publicKey(escrow) + ); + t.deepEqual(escrowTokenAfter.token.amount, 995n); + const userTokenAfter = await fetchDigitalAssetWithAssociatedToken( + umi, + tokenMint.publicKey, + umi.identity.publicKey + ); + t.deepEqual(userTokenAfter.token.amount, 5n); + const assetAfter = await fetchAsset(umi, assets[0].publicKey); + t.is(assetAfter.owner, publicKey(escrow)); +}); diff --git a/clients/rust/src/generated/accounts/escrow_v1.rs b/clients/rust/src/generated/accounts/escrow_v1.rs index 11038ac..4daab0d 100644 --- a/clients/rust/src/generated/accounts/escrow_v1.rs +++ b/clients/rust/src/generated/accounts/escrow_v1.rs @@ -50,6 +50,31 @@ pub struct EscrowV1 { } impl EscrowV1 { + /// Prefix values used to generate a PDA for this account. + /// + /// Values are positional and appear in the following order: + /// + /// 0. `EscrowV1::PREFIX` + /// 1. collection (`Pubkey`) + pub const PREFIX: &'static [u8] = "escrow".as_bytes(); + + pub fn create_pda( + collection: Pubkey, + bump: u8, + ) -> Result { + solana_program::pubkey::Pubkey::create_program_address( + &["escrow".as_bytes(), collection.as_ref(), &[bump]], + &crate::MPL_HYBRID_ID, + ) + } + + pub fn find_pda(collection: &Pubkey) -> (solana_program::pubkey::Pubkey, u8) { + solana_program::pubkey::Pubkey::find_program_address( + &["escrow".as_bytes(), collection.as_ref()], + &crate::MPL_HYBRID_ID, + ) + } + #[inline(always)] pub fn from_bytes(data: &[u8]) -> Result { let mut data = data; diff --git a/clients/rust/src/generated/accounts/escrow_v2.rs b/clients/rust/src/generated/accounts/escrow_v2.rs index 5d2a3fb..8369b2a 100644 --- a/clients/rust/src/generated/accounts/escrow_v2.rs +++ b/clients/rust/src/generated/accounts/escrow_v2.rs @@ -28,6 +28,31 @@ pub struct EscrowV2 { impl EscrowV2 { pub const LEN: usize = 41; + /// Prefix values used to generate a PDA for this account. + /// + /// Values are positional and appear in the following order: + /// + /// 0. `EscrowV2::PREFIX` + /// 1. authority (`Pubkey`) + pub const PREFIX: &'static [u8] = "escrow".as_bytes(); + + pub fn create_pda( + authority: Pubkey, + bump: u8, + ) -> Result { + solana_program::pubkey::Pubkey::create_program_address( + &["escrow".as_bytes(), authority.as_ref(), &[bump]], + &crate::MPL_HYBRID_ID, + ) + } + + pub fn find_pda(authority: &Pubkey) -> (solana_program::pubkey::Pubkey, u8) { + solana_program::pubkey::Pubkey::find_program_address( + &["escrow".as_bytes(), authority.as_ref()], + &crate::MPL_HYBRID_ID, + ) + } + #[inline(always)] pub fn from_bytes(data: &[u8]) -> Result { let mut data = data; diff --git a/clients/rust/src/generated/accounts/recipe_v1.rs b/clients/rust/src/generated/accounts/recipe_v1.rs index 114abf4..c7c97ab 100644 --- a/clients/rust/src/generated/accounts/recipe_v1.rs +++ b/clients/rust/src/generated/accounts/recipe_v1.rs @@ -52,6 +52,31 @@ pub struct RecipeV1 { } impl RecipeV1 { + /// Prefix values used to generate a PDA for this account. + /// + /// Values are positional and appear in the following order: + /// + /// 0. `RecipeV1::PREFIX` + /// 1. collection (`Pubkey`) + pub const PREFIX: &'static [u8] = "recipe".as_bytes(); + + pub fn create_pda( + collection: Pubkey, + bump: u8, + ) -> Result { + solana_program::pubkey::Pubkey::create_program_address( + &["recipe".as_bytes(), collection.as_ref(), &[bump]], + &crate::MPL_HYBRID_ID, + ) + } + + pub fn find_pda(collection: &Pubkey) -> (solana_program::pubkey::Pubkey, u8) { + solana_program::pubkey::Pubkey::find_program_address( + &["recipe".as_bytes(), collection.as_ref()], + &crate::MPL_HYBRID_ID, + ) + } + #[inline(always)] pub fn from_bytes(data: &[u8]) -> Result { let mut data = data; diff --git a/clients/rust/src/generated/instructions/capture_v2.rs b/clients/rust/src/generated/instructions/capture_v2.rs index b3ad7ea..41ffbfe 100644 --- a/clients/rust/src/generated/instructions/capture_v2.rs +++ b/clients/rust/src/generated/instructions/capture_v2.rs @@ -14,7 +14,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub struct CaptureV2 { pub owner: solana_program::pubkey::Pubkey, - pub authority: solana_program::pubkey::Pubkey, + pub authority: (solana_program::pubkey::Pubkey, bool), pub recipe: solana_program::pubkey::Pubkey, @@ -61,8 +61,8 @@ impl CaptureV2 { self.owner, true, )); accounts.push(solana_program::instruction::AccountMeta::new( - self.authority, - true, + self.authority.0, + self.authority.1, )); accounts.push(solana_program::instruction::AccountMeta::new( self.recipe, @@ -161,17 +161,17 @@ impl CaptureV2InstructionData { /// 7. `[writable]` escrow_token_account /// 8. `[]` token /// 9. `[writable]` fee_token_account -/// 10. `[writable]` fee_sol_account +/// 10. `[writable, optional]` fee_sol_account (default to `GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3`) /// 11. `[writable]` fee_project_account -/// 12. `[]` recent_blockhashes -/// 13. `[]` mpl_core +/// 12. `[optional]` recent_blockhashes (default to `SysvarS1otHashes111111111111111111111111111`) +/// 13. `[optional]` mpl_core (default to `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d`) /// 14. `[optional]` system_program (default to `11111111111111111111111111111111`) /// 15. `[optional]` token_program (default to `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) -/// 16. `[]` associated_token_program +/// 16. `[optional]` associated_token_program (default to `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`) #[derive(Default)] pub struct CaptureV2Builder { owner: Option, - authority: Option, + authority: Option<(solana_program::pubkey::Pubkey, bool)>, recipe: Option, escrow: Option, asset: Option, @@ -200,8 +200,12 @@ impl CaptureV2Builder { self } #[inline(always)] - pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); + pub fn authority( + &mut self, + authority: solana_program::pubkey::Pubkey, + as_signer: bool, + ) -> &mut Self { + self.authority = Some((authority, as_signer)); self } #[inline(always)] @@ -253,6 +257,7 @@ impl CaptureV2Builder { self.fee_token_account = Some(fee_token_account); self } + /// `[optional account, default to 'GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3']` #[inline(always)] pub fn fee_sol_account( &mut self, @@ -269,6 +274,7 @@ impl CaptureV2Builder { self.fee_project_account = Some(fee_project_account); self } + /// `[optional account, default to 'SysvarS1otHashes111111111111111111111111111']` #[inline(always)] pub fn recent_blockhashes( &mut self, @@ -277,6 +283,7 @@ impl CaptureV2Builder { self.recent_blockhashes = Some(recent_blockhashes); self } + /// `[optional account, default to 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d']` #[inline(always)] pub fn mpl_core(&mut self, mpl_core: solana_program::pubkey::Pubkey) -> &mut Self { self.mpl_core = Some(mpl_core); @@ -294,6 +301,7 @@ impl CaptureV2Builder { self.token_program = Some(token_program); self } + /// `[optional account, default to 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL']` #[inline(always)] pub fn associated_token_program( &mut self, @@ -339,23 +347,27 @@ impl CaptureV2Builder { fee_token_account: self .fee_token_account .expect("fee_token_account is not set"), - fee_sol_account: self.fee_sol_account.expect("fee_sol_account is not set"), + fee_sol_account: self.fee_sol_account.unwrap_or(solana_program::pubkey!( + "GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3" + )), fee_project_account: self .fee_project_account .expect("fee_project_account is not set"), - recent_blockhashes: self - .recent_blockhashes - .expect("recent_blockhashes is not set"), - mpl_core: self.mpl_core.expect("mpl_core is not set"), + recent_blockhashes: self.recent_blockhashes.unwrap_or(solana_program::pubkey!( + "SysvarS1otHashes111111111111111111111111111" + )), + mpl_core: self.mpl_core.unwrap_or(solana_program::pubkey!( + "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" + )), system_program: self .system_program .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), token_program: self.token_program.unwrap_or(solana_program::pubkey!( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" )), - associated_token_program: self - .associated_token_program - .expect("associated_token_program is not set"), + associated_token_program: self.associated_token_program.unwrap_or( + solana_program::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"), + ), }; accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) @@ -366,7 +378,7 @@ impl CaptureV2Builder { pub struct CaptureV2CpiAccounts<'a, 'b> { pub owner: &'b solana_program::account_info::AccountInfo<'a>, - pub authority: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: (&'b solana_program::account_info::AccountInfo<'a>, bool), pub recipe: &'b solana_program::account_info::AccountInfo<'a>, @@ -406,7 +418,7 @@ pub struct CaptureV2Cpi<'a, 'b> { pub owner: &'b solana_program::account_info::AccountInfo<'a>, - pub authority: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: (&'b solana_program::account_info::AccountInfo<'a>, bool), pub recipe: &'b solana_program::account_info::AccountInfo<'a>, @@ -504,8 +516,8 @@ impl<'a, 'b> CaptureV2Cpi<'a, 'b> { true, )); accounts.push(solana_program::instruction::AccountMeta::new( - *self.authority.key, - true, + *self.authority.0.key, + self.authority.1, )); accounts.push(solana_program::instruction::AccountMeta::new( *self.recipe.key, @@ -584,7 +596,7 @@ impl<'a, 'b> CaptureV2Cpi<'a, 'b> { let mut account_infos = Vec::with_capacity(17 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.owner.clone()); - account_infos.push(self.authority.clone()); + account_infos.push(self.authority.0.clone()); account_infos.push(self.recipe.clone()); account_infos.push(self.escrow.clone()); account_infos.push(self.asset.clone()); @@ -671,8 +683,9 @@ impl<'a, 'b> CaptureV2CpiBuilder<'a, 'b> { pub fn authority( &mut self, authority: &'b solana_program::account_info::AccountInfo<'a>, + as_signer: bool, ) -> &mut Self { - self.instruction.authority = Some(authority); + self.instruction.authority = Some((authority, as_signer)); self } #[inline(always)] @@ -904,7 +917,7 @@ impl<'a, 'b> CaptureV2CpiBuilder<'a, 'b> { struct CaptureV2CpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, - authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<(&'b solana_program::account_info::AccountInfo<'a>, bool)>, recipe: Option<&'b solana_program::account_info::AccountInfo<'a>>, escrow: Option<&'b solana_program::account_info::AccountInfo<'a>>, asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/src/generated/instructions/init_recipe.rs b/clients/rust/src/generated/instructions/init_recipe_v1.rs similarity index 93% rename from clients/rust/src/generated/instructions/init_recipe.rs rename to clients/rust/src/generated/instructions/init_recipe_v1.rs index 90b25b4..ffb7c9c 100644 --- a/clients/rust/src/generated/instructions/init_recipe.rs +++ b/clients/rust/src/generated/instructions/init_recipe_v1.rs @@ -11,7 +11,7 @@ use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; use borsh::{BorshDeserialize, BorshSerialize}; /// Accounts. -pub struct InitRecipe { +pub struct InitRecipeV1 { pub recipe: solana_program::pubkey::Pubkey, pub authority: solana_program::pubkey::Pubkey, @@ -31,17 +31,17 @@ pub struct InitRecipe { pub associated_token_program: solana_program::pubkey::Pubkey, } -impl InitRecipe { +impl InitRecipeV1 { pub fn instruction( &self, - args: InitRecipeInstructionArgs, + args: InitRecipeV1InstructionArgs, ) -> solana_program::instruction::Instruction { self.instruction_with_remaining_accounts(args, &[]) } #[allow(clippy::vec_init_then_push)] pub fn instruction_with_remaining_accounts( &self, - args: InitRecipeInstructionArgs, + args: InitRecipeV1InstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { let mut accounts = Vec::with_capacity(9 + remaining_accounts.len()); @@ -81,7 +81,7 @@ impl InitRecipe { false, )); accounts.extend_from_slice(remaining_accounts); - let mut data = InitRecipeInstructionData::new().try_to_vec().unwrap(); + let mut data = InitRecipeV1InstructionData::new().try_to_vec().unwrap(); let mut args = args.try_to_vec().unwrap(); data.append(&mut args); @@ -95,14 +95,14 @@ impl InitRecipe { #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] -pub struct InitRecipeInstructionData { +pub struct InitRecipeV1InstructionData { discriminator: [u8; 8], } -impl InitRecipeInstructionData { +impl InitRecipeV1InstructionData { pub fn new() -> Self { Self { - discriminator: [196, 35, 249, 242, 64, 106, 51, 53], + discriminator: [212, 22, 246, 254, 234, 63, 108, 246], } } } @@ -111,7 +111,7 @@ impl InitRecipeInstructionData { #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct InitRecipeInstructionArgs { +pub struct InitRecipeV1InstructionArgs { pub name: String, pub uri: String, pub max: u64, @@ -124,7 +124,7 @@ pub struct InitRecipeInstructionArgs { pub path: u16, } -/// Instruction builder for `InitRecipe`. +/// Instruction builder for `InitRecipeV1`. /// /// ### Accounts: /// @@ -136,9 +136,9 @@ pub struct InitRecipeInstructionArgs { /// 5. `[writable]` fee_ata /// 6. `[optional]` system_program (default to `11111111111111111111111111111111`) /// 7. `[optional]` token_program (default to `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) -/// 8. `[]` associated_token_program +/// 8. `[optional]` associated_token_program (default to `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`) #[derive(Default)] -pub struct InitRecipeBuilder { +pub struct InitRecipeV1Builder { recipe: Option, authority: Option, collection: Option, @@ -161,7 +161,7 @@ pub struct InitRecipeBuilder { __remaining_accounts: Vec, } -impl InitRecipeBuilder { +impl InitRecipeV1Builder { pub fn new() -> Self { Self::default() } @@ -208,6 +208,7 @@ impl InitRecipeBuilder { self.token_program = Some(token_program); self } + /// `[optional account, default to 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL']` #[inline(always)] pub fn associated_token_program( &mut self, @@ -286,7 +287,7 @@ impl InitRecipeBuilder { } #[allow(clippy::clone_on_copy)] pub fn instruction(&self) -> solana_program::instruction::Instruction { - let accounts = InitRecipe { + let accounts = InitRecipeV1 { recipe: self.recipe.expect("recipe is not set"), authority: self.authority.expect("authority is not set"), collection: self.collection.expect("collection is not set"), @@ -299,11 +300,11 @@ impl InitRecipeBuilder { token_program: self.token_program.unwrap_or(solana_program::pubkey!( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" )), - associated_token_program: self - .associated_token_program - .expect("associated_token_program is not set"), + associated_token_program: self.associated_token_program.unwrap_or( + solana_program::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"), + ), }; - let args = InitRecipeInstructionArgs { + let args = InitRecipeV1InstructionArgs { name: self.name.clone().expect("name is not set"), uri: self.uri.clone().expect("uri is not set"), max: self.max.clone().expect("max is not set"), @@ -332,8 +333,8 @@ impl InitRecipeBuilder { } } -/// `init_recipe` CPI accounts. -pub struct InitRecipeCpiAccounts<'a, 'b> { +/// `init_recipe_v1` CPI accounts. +pub struct InitRecipeV1CpiAccounts<'a, 'b> { pub recipe: &'b solana_program::account_info::AccountInfo<'a>, pub authority: &'b solana_program::account_info::AccountInfo<'a>, @@ -353,8 +354,8 @@ pub struct InitRecipeCpiAccounts<'a, 'b> { pub associated_token_program: &'b solana_program::account_info::AccountInfo<'a>, } -/// `init_recipe` CPI instruction. -pub struct InitRecipeCpi<'a, 'b> { +/// `init_recipe_v1` CPI instruction. +pub struct InitRecipeV1Cpi<'a, 'b> { /// The program to invoke. pub __program: &'b solana_program::account_info::AccountInfo<'a>, @@ -376,14 +377,14 @@ pub struct InitRecipeCpi<'a, 'b> { pub associated_token_program: &'b solana_program::account_info::AccountInfo<'a>, /// The arguments for the instruction. - pub __args: InitRecipeInstructionArgs, + pub __args: InitRecipeV1InstructionArgs, } -impl<'a, 'b> InitRecipeCpi<'a, 'b> { +impl<'a, 'b> InitRecipeV1Cpi<'a, 'b> { pub fn new( program: &'b solana_program::account_info::AccountInfo<'a>, - accounts: InitRecipeCpiAccounts<'a, 'b>, - args: InitRecipeInstructionArgs, + accounts: InitRecipeV1CpiAccounts<'a, 'b>, + args: InitRecipeV1InstructionArgs, ) -> Self { Self { __program: program, @@ -476,7 +477,7 @@ impl<'a, 'b> InitRecipeCpi<'a, 'b> { is_writable: remaining_account.2, }) }); - let mut data = InitRecipeInstructionData::new().try_to_vec().unwrap(); + let mut data = InitRecipeV1InstructionData::new().try_to_vec().unwrap(); let mut args = self.__args.try_to_vec().unwrap(); data.append(&mut args); @@ -508,7 +509,7 @@ impl<'a, 'b> InitRecipeCpi<'a, 'b> { } } -/// Instruction builder for `InitRecipe` via CPI. +/// Instruction builder for `InitRecipeV1` via CPI. /// /// ### Accounts: /// @@ -521,13 +522,13 @@ impl<'a, 'b> InitRecipeCpi<'a, 'b> { /// 6. `[]` system_program /// 7. `[]` token_program /// 8. `[]` associated_token_program -pub struct InitRecipeCpiBuilder<'a, 'b> { - instruction: Box>, +pub struct InitRecipeV1CpiBuilder<'a, 'b> { + instruction: Box>, } -impl<'a, 'b> InitRecipeCpiBuilder<'a, 'b> { +impl<'a, 'b> InitRecipeV1CpiBuilder<'a, 'b> { pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { - let instruction = Box::new(InitRecipeCpiBuilderInstruction { + let instruction = Box::new(InitRecipeV1CpiBuilderInstruction { __program: program, recipe: None, authority: None, @@ -713,7 +714,7 @@ impl<'a, 'b> InitRecipeCpiBuilder<'a, 'b> { &self, signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { - let args = InitRecipeInstructionArgs { + let args = InitRecipeV1InstructionArgs { name: self.instruction.name.clone().expect("name is not set"), uri: self.instruction.uri.clone().expect("uri is not set"), max: self.instruction.max.clone().expect("max is not set"), @@ -741,7 +742,7 @@ impl<'a, 'b> InitRecipeCpiBuilder<'a, 'b> { .expect("sol_fee_amount_release is not set"), path: self.instruction.path.clone().expect("path is not set"), }; - let instruction = InitRecipeCpi { + let instruction = InitRecipeV1Cpi { __program: self.instruction.__program, recipe: self.instruction.recipe.expect("recipe is not set"), @@ -782,7 +783,7 @@ impl<'a, 'b> InitRecipeCpiBuilder<'a, 'b> { } } -struct InitRecipeCpiBuilderInstruction<'a, 'b> { +struct InitRecipeV1CpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, recipe: Option<&'b solana_program::account_info::AccountInfo<'a>>, authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index 900bf20..427a378 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -10,7 +10,7 @@ pub(crate) mod r#capture_v2; pub(crate) mod r#init_escrow_v1; pub(crate) mod r#init_escrow_v2; pub(crate) mod r#init_nft_data_v1; -pub(crate) mod r#init_recipe; +pub(crate) mod r#init_recipe_v1; pub(crate) mod r#migrate_nft_v1; pub(crate) mod r#migrate_tokens_v1; pub(crate) mod r#release_v1; @@ -24,7 +24,7 @@ pub use self::r#capture_v2::*; pub use self::r#init_escrow_v1::*; pub use self::r#init_escrow_v2::*; pub use self::r#init_nft_data_v1::*; -pub use self::r#init_recipe::*; +pub use self::r#init_recipe_v1::*; pub use self::r#migrate_nft_v1::*; pub use self::r#migrate_tokens_v1::*; pub use self::r#release_v1::*; diff --git a/clients/rust/src/generated/instructions/release_v2.rs b/clients/rust/src/generated/instructions/release_v2.rs index cd9c0a2..4b016ab 100644 --- a/clients/rust/src/generated/instructions/release_v2.rs +++ b/clients/rust/src/generated/instructions/release_v2.rs @@ -14,7 +14,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub struct ReleaseV2 { pub owner: solana_program::pubkey::Pubkey, - pub authority: solana_program::pubkey::Pubkey, + pub authority: (solana_program::pubkey::Pubkey, bool), pub recipe: solana_program::pubkey::Pubkey, @@ -61,8 +61,8 @@ impl ReleaseV2 { self.owner, true, )); accounts.push(solana_program::instruction::AccountMeta::new( - self.authority, - true, + self.authority.0, + self.authority.1, )); accounts.push(solana_program::instruction::AccountMeta::new( self.recipe, @@ -161,17 +161,17 @@ impl ReleaseV2InstructionData { /// 7. `[writable]` escrow_token_account /// 8. `[]` token /// 9. `[writable]` fee_token_account -/// 10. `[writable]` fee_sol_account +/// 10. `[writable, optional]` fee_sol_account (default to `GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3`) /// 11. `[writable]` fee_project_account -/// 12. `[]` recent_blockhashes -/// 13. `[]` mpl_core +/// 12. `[optional]` recent_blockhashes (default to `SysvarS1otHashes111111111111111111111111111`) +/// 13. `[optional]` mpl_core (default to `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d`) /// 14. `[optional]` system_program (default to `11111111111111111111111111111111`) /// 15. `[optional]` token_program (default to `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) -/// 16. `[]` associated_token_program +/// 16. `[optional]` associated_token_program (default to `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`) #[derive(Default)] pub struct ReleaseV2Builder { owner: Option, - authority: Option, + authority: Option<(solana_program::pubkey::Pubkey, bool)>, recipe: Option, escrow: Option, asset: Option, @@ -200,8 +200,12 @@ impl ReleaseV2Builder { self } #[inline(always)] - pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); + pub fn authority( + &mut self, + authority: solana_program::pubkey::Pubkey, + as_signer: bool, + ) -> &mut Self { + self.authority = Some((authority, as_signer)); self } #[inline(always)] @@ -253,6 +257,7 @@ impl ReleaseV2Builder { self.fee_token_account = Some(fee_token_account); self } + /// `[optional account, default to 'GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3']` #[inline(always)] pub fn fee_sol_account( &mut self, @@ -269,6 +274,7 @@ impl ReleaseV2Builder { self.fee_project_account = Some(fee_project_account); self } + /// `[optional account, default to 'SysvarS1otHashes111111111111111111111111111']` #[inline(always)] pub fn recent_blockhashes( &mut self, @@ -277,6 +283,7 @@ impl ReleaseV2Builder { self.recent_blockhashes = Some(recent_blockhashes); self } + /// `[optional account, default to 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d']` #[inline(always)] pub fn mpl_core(&mut self, mpl_core: solana_program::pubkey::Pubkey) -> &mut Self { self.mpl_core = Some(mpl_core); @@ -294,6 +301,7 @@ impl ReleaseV2Builder { self.token_program = Some(token_program); self } + /// `[optional account, default to 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL']` #[inline(always)] pub fn associated_token_program( &mut self, @@ -339,23 +347,27 @@ impl ReleaseV2Builder { fee_token_account: self .fee_token_account .expect("fee_token_account is not set"), - fee_sol_account: self.fee_sol_account.expect("fee_sol_account is not set"), + fee_sol_account: self.fee_sol_account.unwrap_or(solana_program::pubkey!( + "GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3" + )), fee_project_account: self .fee_project_account .expect("fee_project_account is not set"), - recent_blockhashes: self - .recent_blockhashes - .expect("recent_blockhashes is not set"), - mpl_core: self.mpl_core.expect("mpl_core is not set"), + recent_blockhashes: self.recent_blockhashes.unwrap_or(solana_program::pubkey!( + "SysvarS1otHashes111111111111111111111111111" + )), + mpl_core: self.mpl_core.unwrap_or(solana_program::pubkey!( + "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" + )), system_program: self .system_program .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), token_program: self.token_program.unwrap_or(solana_program::pubkey!( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" )), - associated_token_program: self - .associated_token_program - .expect("associated_token_program is not set"), + associated_token_program: self.associated_token_program.unwrap_or( + solana_program::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"), + ), }; accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) @@ -366,7 +378,7 @@ impl ReleaseV2Builder { pub struct ReleaseV2CpiAccounts<'a, 'b> { pub owner: &'b solana_program::account_info::AccountInfo<'a>, - pub authority: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: (&'b solana_program::account_info::AccountInfo<'a>, bool), pub recipe: &'b solana_program::account_info::AccountInfo<'a>, @@ -406,7 +418,7 @@ pub struct ReleaseV2Cpi<'a, 'b> { pub owner: &'b solana_program::account_info::AccountInfo<'a>, - pub authority: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: (&'b solana_program::account_info::AccountInfo<'a>, bool), pub recipe: &'b solana_program::account_info::AccountInfo<'a>, @@ -504,8 +516,8 @@ impl<'a, 'b> ReleaseV2Cpi<'a, 'b> { true, )); accounts.push(solana_program::instruction::AccountMeta::new( - *self.authority.key, - true, + *self.authority.0.key, + self.authority.1, )); accounts.push(solana_program::instruction::AccountMeta::new( *self.recipe.key, @@ -584,7 +596,7 @@ impl<'a, 'b> ReleaseV2Cpi<'a, 'b> { let mut account_infos = Vec::with_capacity(17 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.owner.clone()); - account_infos.push(self.authority.clone()); + account_infos.push(self.authority.0.clone()); account_infos.push(self.recipe.clone()); account_infos.push(self.escrow.clone()); account_infos.push(self.asset.clone()); @@ -671,8 +683,9 @@ impl<'a, 'b> ReleaseV2CpiBuilder<'a, 'b> { pub fn authority( &mut self, authority: &'b solana_program::account_info::AccountInfo<'a>, + as_signer: bool, ) -> &mut Self { - self.instruction.authority = Some(authority); + self.instruction.authority = Some((authority, as_signer)); self } #[inline(always)] @@ -904,7 +917,7 @@ impl<'a, 'b> ReleaseV2CpiBuilder<'a, 'b> { struct ReleaseV2CpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, - authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<(&'b solana_program::account_info::AccountInfo<'a>, bool)>, recipe: Option<&'b solana_program::account_info::AccountInfo<'a>>, escrow: Option<&'b solana_program::account_info::AccountInfo<'a>>, asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/configs/kinobi.cjs b/configs/kinobi.cjs index 7018bba..eaa713b 100644 --- a/configs/kinobi.cjs +++ b/configs/kinobi.cjs @@ -17,6 +17,42 @@ kinobi.update( }) ); +// Update Accounts. +kinobi.update( + k.updateAccountsVisitor({ + escrowV1: { + seeds: [ + k.constantPdaSeedNodeFromString("escrow"), + k.variablePdaSeedNode( + "collection", + k.publicKeyTypeNode(), + "The address of the collection" + ), + ], + }, + escrowV2: { + seeds: [ + k.constantPdaSeedNodeFromString("escrow"), + k.variablePdaSeedNode( + "authority", + k.publicKeyTypeNode(), + "The address of the authority" + ), + ], + }, + recipeV1: { + seeds: [ + k.constantPdaSeedNodeFromString("recipe"), + k.variablePdaSeedNode( + "collection", + k.publicKeyTypeNode(), + "The address of the collection" + ), + ], + }, + }) +); + // Update Instructions. const ataPdaDefault = (mint = "mint", owner = "owner") => k.pdaValueNode(k.pdaLinkNode("associatedToken", "mplToolbox"), [ @@ -61,7 +97,51 @@ kinobi.update( recentBlockhashes: { defaultValue: k.publicKeyValueNode("SysvarS1otHashes111111111111111111111111111") }, feeSolAccount: { defaultValue: k.publicKeyValueNode("GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3") } } - } + }, + initEscrowV2: { + accounts: { + escrow: { + defaultValue: k.pdaValueNode("escrowV2") + }, + } + }, + initRecipeV1: { + accounts: { + recipe: { defaultValue: k.pdaValueNode("recipeV1") }, + feeAta: { defaultValue: ataPdaDefault("token", "feeLocation") }, + associatedTokenProgram: { + defaultValue: k.publicKeyValueNode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + } + } + }, + captureV2: { + accounts: { + authority: { isSigner: 'either' }, + feeTokenAccount: { defaultValue: ataPdaDefault("token", "feeProjectAccount") }, + escrowTokenAccount: { defaultValue: ataPdaDefault("token", "escrow") }, + userTokenAccount: { defaultValue: ataPdaDefault("token", "owner") }, + associatedTokenProgram: { + defaultValue: k.publicKeyValueNode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + }, + mplCore: { defaultValue: k.publicKeyValueNode("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d") }, + recentBlockhashes: { defaultValue: k.publicKeyValueNode("SysvarS1otHashes111111111111111111111111111") }, + feeSolAccount: { defaultValue: k.publicKeyValueNode("GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3") } + } + }, + releaseV2: { + accounts: { + authority: { isSigner: 'either' }, + feeTokenAccount: { defaultValue: ataPdaDefault("token", "feeProjectAccount") }, + escrowTokenAccount: { defaultValue: ataPdaDefault("token", "escrow") }, + userTokenAccount: { defaultValue: ataPdaDefault("token", "owner") }, + associatedTokenProgram: { + defaultValue: k.publicKeyValueNode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + }, + mplCore: { defaultValue: k.publicKeyValueNode("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d") }, + recentBlockhashes: { defaultValue: k.publicKeyValueNode("SysvarS1otHashes111111111111111111111111111") }, + feeSolAccount: { defaultValue: k.publicKeyValueNode("GjF4LqmEhV33riVyAwHwiEeAHx4XXFn2yMY3fmMigoP3") } + } + }, }) ); diff --git a/idls/mpl_hybrid.json b/idls/mpl_hybrid.json index 5c6a45e..868c807 100644 --- a/idls/mpl_hybrid.json +++ b/idls/mpl_hybrid.json @@ -3,7 +3,7 @@ "name": "mpl_hybrid", "instructions": [ { - "name": "initRecipe", + "name": "initRecipeV1", "accounts": [ { "name": "recipe", diff --git a/programs/mpl-hybrid/src/instructions/capture.rs b/programs/mpl-hybrid/src/instructions/capture.rs index 3798c1a..a2e206b 100644 --- a/programs/mpl-hybrid/src/instructions/capture.rs +++ b/programs/mpl-hybrid/src/instructions/capture.rs @@ -268,7 +268,7 @@ pub fn handler_capture_v1(ctx: Context) -> Result<()> { ); //invoke protocol the transfer fee sol instruction - let _sol_fee_result = invoke( + invoke( &sol_fee_ix, &[owner.to_account_info(), fee_sol_account.to_account_info()], )?; @@ -281,7 +281,7 @@ pub fn handler_capture_v1(ctx: Context) -> Result<()> { ); //invoke project the transfer fee sol instruction for project - let _sol_fee_project_result = invoke( + invoke( &sol_fee_project_ix, &[ owner.to_account_info(), diff --git a/programs/mpl-hybrid/src/instructions/capture_v2.rs b/programs/mpl-hybrid/src/instructions/capture_v2.rs index 2ea361a..32b1e2f 100644 --- a/programs/mpl-hybrid/src/instructions/capture_v2.rs +++ b/programs/mpl-hybrid/src/instructions/capture_v2.rs @@ -43,7 +43,7 @@ pub struct CaptureV2Ctx<'info> { mut, seeds = [ "escrow".as_bytes(), - authority.key().as_ref() + recipe.authority.as_ref() ], bump=escrow.bump, )] @@ -224,9 +224,9 @@ pub fn handler_capture_v2(ctx: Context) -> Result<()> { if authority_info.key == &recipe.authority { //invoke the update instruction update_ix.invoke()?; - } else if authority_info.key == &escrow.key() { + } else if authority_info.key == &recipe.key() { // The auth has been delegated as the UpdateDelegate on the asset. - update_ix.invoke_signed(&[&[b"escrow", authority.key.as_ref(), &[escrow.bump]]])?; + update_ix.invoke_signed(&[&[b"recipe", collection.key.as_ref(), &[recipe.bump]]])?; } else { return Err(MplHybridError::InvalidUpdateAuthority.into()); } @@ -248,8 +248,7 @@ pub fn handler_capture_v2(ctx: Context) -> Result<()> { }; //invoke the transfer instruction with seeds - let _transfer_nft_result = - transfer_nft_ix.invoke_signed(&[&[b"escrow", authority.key.as_ref(), &[escrow.bump]]]); + transfer_nft_ix.invoke_signed(&[&[b"escrow", recipe.authority.as_ref(), &[escrow.bump]]])?; let cpi_program = token_program.to_account_info(); @@ -283,7 +282,7 @@ pub fn handler_capture_v2(ctx: Context) -> Result<()> { ); //invoke protocol the transfer fee sol instruction - let _sol_fee_result = invoke( + invoke( &sol_fee_ix, &[owner.to_account_info(), fee_sol_account.to_account_info()], )?; @@ -296,7 +295,7 @@ pub fn handler_capture_v2(ctx: Context) -> Result<()> { ); //invoke project the transfer fee sol instruction for project - let _sol_fee_project_result = invoke( + invoke( &sol_fee_project_ix, &[ owner.to_account_info(), diff --git a/programs/mpl-hybrid/src/instructions/release_v2.rs b/programs/mpl-hybrid/src/instructions/release_v2.rs index 9e28f11..bd2e2cf 100644 --- a/programs/mpl-hybrid/src/instructions/release_v2.rs +++ b/programs/mpl-hybrid/src/instructions/release_v2.rs @@ -43,7 +43,7 @@ pub struct ReleaseV2Ctx<'info> { mut, seeds = [ "escrow".as_bytes(), - authority.key().as_ref() + recipe.authority.as_ref() ], bump=escrow.bump, )] @@ -211,9 +211,9 @@ pub fn handler_release_v2(ctx: Context) -> Result<()> { if authority_info.key == &recipe.authority { //invoke the update instruction update_ix.invoke()?; - } else if authority_info.key == &escrow.key() { + } else if authority_info.key == &recipe.key() { // The auth has been delegated as the UpdateDelegate on the asset. - update_ix.invoke_signed(&[&[b"escrow", authority.key.as_ref(), &[escrow.bump]]])?; + update_ix.invoke_signed(&[&[b"recipe", collection.key.as_ref(), &[recipe.bump]]])?; } else { return Err(MplHybridError::InvalidUpdateAuthority.into()); } @@ -240,7 +240,7 @@ pub fn handler_release_v2(ctx: Context) -> Result<()> { //create transfer token instruction let cpi_program = token_program.to_account_info(); - let signer_seeds = &[b"escrow", authority.key.as_ref(), &[escrow.bump]]; + let signer_seeds = &[b"escrow", recipe.authority.as_ref(), &[escrow.bump]]; let signer = &[&signer_seeds[..]]; diff --git a/programs/mpl-hybrid/src/lib.rs b/programs/mpl-hybrid/src/lib.rs index 585aff7..59dc008 100644 --- a/programs/mpl-hybrid/src/lib.rs +++ b/programs/mpl-hybrid/src/lib.rs @@ -14,7 +14,7 @@ pub mod mpl_hybrid { use super::*; - pub fn init_recipe(ctx: Context, ix: InitRecipeV1Ix) -> Result<()> { + pub fn init_recipe_v1(ctx: Context, ix: InitRecipeV1Ix) -> Result<()> { init_recipe::handler_init_recipe_v1(ctx, ix) } @@ -36,7 +36,7 @@ pub mod mpl_hybrid { pub fn capture_v2(ctx: Context) -> Result<()> { capture_v2::handler_capture_v2(ctx) - } + } pub fn release_v1(ctx: Context) -> Result<()> { release::handler_release_v1(ctx) @@ -44,7 +44,7 @@ pub mod mpl_hybrid { pub fn release_v2(ctx: Context) -> Result<()> { release_v2::handler_release_v2(ctx) - } + } pub fn update_recipe_v1(ctx: Context, ix: UpdateRecipeV1Ix) -> Result<()> { update_recipe::handler_update_recipe_v1(ctx, ix) @@ -65,7 +65,10 @@ pub mod mpl_hybrid { migrate_nft_v1::handler_migrate_nft_v1(ctx) } - pub fn migrate_tokens_v1(ctx: Context, ix: MigrateTokensV1Ix) -> Result<()> { - migrate_tokens_v1::handler_migrate_tokens_v1(ctx,ix) + pub fn migrate_tokens_v1( + ctx: Context, + ix: MigrateTokensV1Ix, + ) -> Result<()> { + migrate_tokens_v1::handler_migrate_tokens_v1(ctx, ix) } }