diff --git a/clients/js/package.json b/clients/js/package.json index 6ca7f47a..7e1f2de7 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -28,7 +28,7 @@ "@metaplex-foundation/umi": "^0.8.2" }, "dependencies": { - "@metaplex-foundation/mpl-toolbox": "^0.8.0" + "@metaplex-foundation/mpl-toolbox": "^0.8.1" }, "devDependencies": { "@ava/typescript": "^3.0.1", diff --git a/clients/js/pnpm-lock.yaml b/clients/js/pnpm-lock.yaml index 9dd9d2b4..8d2cd7b8 100644 --- a/clients/js/pnpm-lock.yaml +++ b/clients/js/pnpm-lock.yaml @@ -2,8 +2,8 @@ lockfileVersion: '6.0' dependencies: '@metaplex-foundation/mpl-toolbox': - specifier: ^0.8.0 - version: 0.8.0(@metaplex-foundation/umi@0.8.2) + specifier: ^0.8.1 + version: 0.8.1(@metaplex-foundation/umi@0.8.2) devDependencies: '@ava/typescript': @@ -1619,8 +1619,8 @@ packages: - supports-color dev: true - /@metaplex-foundation/mpl-toolbox@0.8.0(@metaplex-foundation/umi@0.8.2): - resolution: {integrity: sha512-SK1VUPU4hCaL3sozgtoVjjbZxqx2gWiRt0YTFbwEt5LAHWOlCb7J7rcrrA5XwymX4iV2bIWygYs0yz7hYyx2rg==} + /@metaplex-foundation/mpl-toolbox@0.8.1(@metaplex-foundation/umi@0.8.2): + resolution: {integrity: sha512-PYU+qU6hg+h6V42z+MDnBTiWZXTTzWhIn5ieyRZYyszTru/LiJKHdsHGKe1VBfuXEuRdfRhAzivMjquZVaqO4A==} peerDependencies: '@metaplex-foundation/umi': ^0.8.2 dependencies: diff --git a/clients/js/src/generated/accounts/editionMarkerV2.ts b/clients/js/src/generated/accounts/editionMarkerV2.ts index cdb4bd51..2b3809f6 100644 --- a/clients/js/src/generated/accounts/editionMarkerV2.ts +++ b/clients/js/src/generated/accounts/editionMarkerV2.ts @@ -22,6 +22,8 @@ import { import { Serializer, bytes, + publicKey as publicKeySerializer, + string, struct, } from '@metaplex-foundation/umi/serializers'; import { Key, KeyArgs, getKeySerializer } from '../types'; @@ -145,3 +147,47 @@ export function getEditionMarkerV2GpaBuilder( deserializeEditionMarkerV2(account) ); } + +export function findEditionMarkerV2Pda( + context: Pick, + seeds: { + /** The address of the mint account */ + mint: PublicKey; + } +): Pda { + const programId = context.programs.getPublicKey( + 'mplTokenMetadata', + 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s' + ); + return context.eddsa.findPda(programId, [ + string({ size: 'variable' }).serialize('metadata'), + publicKeySerializer().serialize(programId), + publicKeySerializer().serialize(seeds.mint), + string({ size: 'variable' }).serialize('edition'), + string({ size: 'variable' }).serialize('marker'), + ]); +} + +export async function fetchEditionMarkerV2FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return fetchEditionMarkerV2( + context, + findEditionMarkerV2Pda(context, seeds), + options + ); +} + +export async function safeFetchEditionMarkerV2FromSeeds( + context: Pick, + seeds: Parameters[1], + options?: RpcGetAccountOptions +): Promise { + return safeFetchEditionMarkerV2( + context, + findEditionMarkerV2Pda(context, seeds), + options + ); +} diff --git a/clients/js/src/generated/instructions/printV1.ts b/clients/js/src/generated/instructions/printV1.ts index 873cff40..282be1db 100644 --- a/clients/js/src/generated/instructions/printV1.ts +++ b/clients/js/src/generated/instructions/printV1.ts @@ -6,6 +6,7 @@ * @see https://github.com/metaplex-foundation/kinobi */ +import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox'; import { AccountMeta, Context, @@ -23,36 +24,42 @@ import { u64, u8, } from '@metaplex-foundation/umi/serializers'; +import { + resolveEditionMarkerForPrint, + resolveTokenRecordForPrint, +} from '../../hooked'; +import { findMasterEditionPda, findMetadataPda } from '../accounts'; import { addAccountMeta, addObjectProperty } from '../shared'; +import { TokenStandardArgs } from '../types'; // Accounts. export type PrintV1InstructionAccounts = { /** New Metadata key (pda of ['metadata', program id, mint id]) */ - editionMetadata: PublicKey | Pda; + editionMetadata?: PublicKey | Pda; /** New Edition (pda of ['metadata', program id, mint id, 'edition']) */ - edition: PublicKey | Pda; + edition?: PublicKey | Pda; /** Mint of new token - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY */ - editionMint: PublicKey | Pda; + editionMint: PublicKey | Pda | Signer; /** Owner of the token account of new token */ - editionTokenAccountOwner: PublicKey | Pda; + editionTokenAccountOwner?: PublicKey | Pda; /** Token account of new token */ - editionTokenAccount: PublicKey | Pda; + editionTokenAccount?: PublicKey | Pda; /** Mint authority of new mint */ - editionMintAuthority: Signer; + editionMintAuthority?: Signer; /** Token record account */ editionTokenRecord?: PublicKey | Pda; /** Master Record Edition V2 (pda of ['metadata', program id, master metadata mint id, 'edition']) */ - masterEdition: PublicKey | Pda; + masterEdition?: PublicKey | Pda; /** Edition pda to mark creation - will be checked for pre-existence. (pda of ['metadata', program id, master metadata mint id, 'edition', edition_number]) where edition_number is NOT the edition number you pass in args but actually edition_number = floor(edition/EDITION_MARKER_BIT_SIZE). */ - editionMarkerPda: PublicKey | Pda; + editionMarkerPda?: PublicKey | Pda; /** payer */ payer?: Signer; /** owner of token account containing master token */ - masterTokenAccountOwner: Signer; + masterTokenAccountOwner?: Signer; /** token account containing token from master metadata mint */ - masterTokenAccount: PublicKey | Pda; + masterTokenAccount?: PublicKey | Pda; /** Master record metadata account */ - masterMetadata: PublicKey | Pda; + masterMetadata?: PublicKey | Pda; /** The update authority of the master edition. */ updateAuthority?: PublicKey | Pda; /** Token program */ @@ -69,10 +76,10 @@ export type PrintV1InstructionAccounts = { export type PrintV1InstructionData = { discriminator: number; printV1Discriminator: number; - edition: bigint; + editionNumber: bigint; }; -export type PrintV1InstructionDataArgs = { edition: number | bigint }; +export type PrintV1InstructionDataArgs = { editionNumber: number | bigint }; /** @deprecated Use `getPrintV1InstructionDataSerializer()` without any argument instead. */ export function getPrintV1InstructionDataSerializer( @@ -90,7 +97,7 @@ export function getPrintV1InstructionDataSerializer( [ ['discriminator', u8()], ['printV1Discriminator', u8()], - ['edition', u64()], + ['editionNumber', u64()], ], { description: 'PrintV1InstructionData' } ), @@ -98,14 +105,20 @@ export function getPrintV1InstructionDataSerializer( ) as Serializer; } +// Extra Args. +export type PrintV1InstructionExtraArgs = { + masterEditionMint: PublicKey; + tokenStandard: TokenStandardArgs; +}; + // Args. -export type PrintV1InstructionArgs = PrintV1InstructionDataArgs; +export type PrintV1InstructionArgs = PrintV1InstructionDataArgs & + PrintV1InstructionExtraArgs; // Instruction. export function printV1( - context: Pick, - accounts: PrintV1InstructionAccounts, - args: PrintV1InstructionArgs + context: Pick, + input: PrintV1InstructionAccounts & PrintV1InstructionArgs ): TransactionBuilder { const signers: Signer[] = []; const keys: AccountMeta[] = []; @@ -118,48 +131,151 @@ export function printV1( // Resolved inputs. const resolvedAccounts = { - editionMetadata: [accounts.editionMetadata, true] as const, - edition: [accounts.edition, true] as const, - editionMint: [accounts.editionMint, true] as const, - editionTokenAccountOwner: [ - accounts.editionTokenAccountOwner, - false, - ] as const, - editionTokenAccount: [accounts.editionTokenAccount, true] as const, - editionMintAuthority: [accounts.editionMintAuthority, false] as const, - masterEdition: [accounts.masterEdition, true] as const, - editionMarkerPda: [accounts.editionMarkerPda, true] as const, - masterTokenAccountOwner: [accounts.masterTokenAccountOwner, false] as const, - masterTokenAccount: [accounts.masterTokenAccount, false] as const, - masterMetadata: [accounts.masterMetadata, false] as const, + editionMint: [input.editionMint, true] as const, }; const resolvingArgs = {}; + addObjectProperty( + resolvedAccounts, + 'editionMetadata', + input.editionMetadata + ? ([input.editionMetadata, true] as const) + : ([ + findMetadataPda(context, { + mint: publicKey(input.editionMint, false), + }), + true, + ] as const) + ); + addObjectProperty( + resolvedAccounts, + 'edition', + input.edition + ? ([input.edition, true] as const) + : ([ + findMasterEditionPda(context, { + mint: publicKey(input.editionMint, false), + }), + true, + ] as const) + ); + addObjectProperty( + resolvedAccounts, + 'editionTokenAccountOwner', + input.editionTokenAccountOwner + ? ([input.editionTokenAccountOwner, false] as const) + : ([context.identity.publicKey, false] as const) + ); + addObjectProperty( + resolvedAccounts, + 'editionTokenAccount', + input.editionTokenAccount + ? ([input.editionTokenAccount, true] as const) + : ([ + findAssociatedTokenPda(context, { + mint: publicKey(input.editionMint, false), + owner: publicKey( + resolvedAccounts.editionTokenAccountOwner[0], + false + ), + }), + true, + ] as const) + ); + addObjectProperty( + resolvedAccounts, + 'masterTokenAccountOwner', + input.masterTokenAccountOwner + ? ([input.masterTokenAccountOwner, false] as const) + : ([context.identity, false] as const) + ); + addObjectProperty( + resolvedAccounts, + 'editionMintAuthority', + input.editionMintAuthority + ? ([input.editionMintAuthority, false] as const) + : ([resolvedAccounts.masterTokenAccountOwner[0], false] as const) + ); addObjectProperty( resolvedAccounts, 'editionTokenRecord', - accounts.editionTokenRecord - ? ([accounts.editionTokenRecord, true] as const) - : ([programId, false] as const) + input.editionTokenRecord + ? ([input.editionTokenRecord, true] as const) + : resolveTokenRecordForPrint( + context, + { ...input, ...resolvedAccounts }, + { ...input, ...resolvingArgs }, + programId, + true + ) + ); + addObjectProperty( + resolvedAccounts, + 'masterEdition', + input.masterEdition + ? ([input.masterEdition, true] as const) + : ([ + findMasterEditionPda(context, { mint: input.masterEditionMint }), + true, + ] as const) + ); + addObjectProperty( + resolvedAccounts, + 'editionMarkerPda', + input.editionMarkerPda + ? ([input.editionMarkerPda, true] as const) + : resolveEditionMarkerForPrint( + context, + { ...input, ...resolvedAccounts }, + { ...input, ...resolvingArgs }, + programId, + true + ) ); addObjectProperty( resolvedAccounts, 'payer', - accounts.payer - ? ([accounts.payer, true] as const) + input.payer + ? ([input.payer, true] as const) : ([context.payer, true] as const) ); + addObjectProperty( + resolvedAccounts, + 'masterTokenAccount', + input.masterTokenAccount + ? ([input.masterTokenAccount, false] as const) + : ([ + findAssociatedTokenPda(context, { + mint: input.masterEditionMint, + owner: publicKey( + resolvedAccounts.masterTokenAccountOwner[0], + false + ), + }), + false, + ] as const) + ); + addObjectProperty( + resolvedAccounts, + 'masterMetadata', + input.masterMetadata + ? ([input.masterMetadata, false] as const) + : ([ + findMetadataPda(context, { mint: input.masterEditionMint }), + false, + ] as const) + ); addObjectProperty( resolvedAccounts, 'updateAuthority', - accounts.updateAuthority - ? ([accounts.updateAuthority, false] as const) + input.updateAuthority + ? ([input.updateAuthority, false] as const) : ([context.identity.publicKey, false] as const) ); addObjectProperty( resolvedAccounts, 'splTokenProgram', - accounts.splTokenProgram - ? ([accounts.splTokenProgram, false] as const) + input.splTokenProgram + ? ([input.splTokenProgram, false] as const) : ([ context.programs.getPublicKey( 'splToken', @@ -171,8 +287,8 @@ export function printV1( addObjectProperty( resolvedAccounts, 'splAtaProgram', - accounts.splAtaProgram - ? ([accounts.splAtaProgram, false] as const) + input.splAtaProgram + ? ([input.splAtaProgram, false] as const) : ([ context.programs.getPublicKey( 'splAssociatedToken', @@ -184,8 +300,8 @@ export function printV1( addObjectProperty( resolvedAccounts, 'sysvarInstructions', - accounts.sysvarInstructions - ? ([accounts.sysvarInstructions, false] as const) + input.sysvarInstructions + ? ([input.sysvarInstructions, false] as const) : ([ publicKey('Sysvar1nstructions1111111111111111111111111'), false, @@ -194,8 +310,8 @@ export function printV1( addObjectProperty( resolvedAccounts, 'systemProgram', - accounts.systemProgram - ? ([accounts.systemProgram, false] as const) + input.systemProgram + ? ([input.systemProgram, false] as const) : ([ context.programs.getPublicKey( 'splSystem', @@ -204,7 +320,7 @@ export function printV1( false, ] as const) ); - const resolvedArgs = { ...args, ...resolvingArgs }; + const resolvedArgs = { ...input, ...resolvingArgs }; addAccountMeta(keys, signers, resolvedAccounts.editionMetadata, false); addAccountMeta(keys, signers, resolvedAccounts.edition, false); diff --git a/clients/js/src/hooked/editionMarker.ts b/clients/js/src/hooked/editionMarker.ts index 049cacce..afc18a28 100644 --- a/clients/js/src/hooked/editionMarker.ts +++ b/clients/js/src/hooked/editionMarker.ts @@ -1,7 +1,7 @@ import { Pda, PublicKey } from '@metaplex-foundation/umi'; import { findEditionMarkerPda } from '../generated'; -export function findEditionMarkerPdaFromEditionNumber( +export function findEditionMarkerFromEditionNumberPda( context: Parameters[0], seeds: { /** The address of the mint account */ diff --git a/clients/js/src/hooked/resolvers.ts b/clients/js/src/hooked/resolvers.ts index 09f5172d..280e44e4 100644 --- a/clients/js/src/hooked/resolvers.ts +++ b/clients/js/src/hooked/resolvers.ts @@ -18,12 +18,14 @@ import { TokenStandard, WithWritable, collectionDetails, + findEditionMarkerV2Pda, findMasterEditionPda, findTokenRecordPda, getMasterEditionSize, getMetadataSize, printSupply, } from '../generated'; +import { findEditionMarkerFromEditionNumberPda } from './editionMarker'; export const resolveCollectionDetails = ( context: any, @@ -139,6 +141,50 @@ export const resolveTokenRecord = ( ] : [programId, false]; +export const resolveTokenRecordForPrint = ( + context: Pick, + accounts: { + editionMint: WithWritable; + editionTokenAccount: WithWritable; + }, + args: { tokenStandard: TokenStandard }, + programId: PublicKey, + isWritable: boolean +): WithWritable => + isProgrammable(args.tokenStandard) && accounts.editionTokenAccount[0] + ? [ + findTokenRecordPda(context, { + mint: publicKey(accounts.editionMint[0], false), + token: publicKey(accounts.editionTokenAccount[0], false), + }), + isWritable, + ] + : [programId, false]; + +export const resolveEditionMarkerForPrint = ( + context: Pick, + accounts: any, + args: { + tokenStandard: TokenStandard; + masterEditionMint: PublicKey; + editionNumber: number | bigint; + }, + programId: PublicKey, + isWritable: boolean +): WithWritable => + isProgrammable(args.tokenStandard) + ? [ + findEditionMarkerV2Pda(context, { mint: args.masterEditionMint }), + isWritable, + ] + : [ + findEditionMarkerFromEditionNumberPda(context, { + mint: args.masterEditionMint, + editionNumber: args.editionNumber, + }), + isWritable, + ]; + export const resolveDestinationTokenRecord = ( context: Pick, accounts: { diff --git a/clients/js/test/printV1.test.ts b/clients/js/test/printV1.test.ts new file mode 100644 index 00000000..01d74f8f --- /dev/null +++ b/clients/js/test/printV1.test.ts @@ -0,0 +1,241 @@ +import { createMintWithAssociatedToken } from '@metaplex-foundation/mpl-toolbox'; +import { generateSigner, percentAmount, some } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + DigitalAsset, + DigitalAssetWithToken, + TokenStandard, + fetchDigitalAsset, + fetchDigitalAssetWithAssociatedToken, + findMasterEditionPda, + printSupply, + printV1, +} from '../src'; +import { createDigitalAssetWithToken, createUmi } from './_setup'; + +test('it can print a new edition from a NonFungible', async (t) => { + // Given an existing master edition asset. + const umi = await createUmi(); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: percentAmount(5.42), + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.NonFungible, + }); + + // When we print a new edition of the asset. + const editionMint = generateSigner(umi); + const editionOwner = generateSigner(umi); + await printV1(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.NonFungible, + }).sendAndConfirm(umi); + + // Then the original NFT was updated. + const originalAsset = await fetchDigitalAsset(umi, originalMint.publicKey); + t.like(originalAsset, { + edition: { supply: 1n, maxSupply: some(10n) }, + }); + + // And the printed NFT was created with the same data. + const editionAsset = await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ); + t.like(editionAsset, { + publicKey: editionMint.publicKey, + metadata: { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: 542, + }, + token: { + owner: editionOwner.publicKey, + amount: 1n, + }, + edition: { + isOriginal: false, + parent: findMasterEditionPda(umi, { mint: originalMint.publicKey })[0], + edition: 1n, + }, + }); +}); + +test('it can print a new edition from a ProgrammableNonFungible', async (t) => { + // Given an existing master edition PNFT. + const umi = await createUmi(); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + name: 'My PNFT', + symbol: 'MPNFT', + uri: 'https://example.com/pnft.json', + sellerFeeBasisPoints: percentAmount(5.42), + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.ProgrammableNonFungible, + }); + + // When we print a new edition of the asset. + const editionMint = generateSigner(umi); + const editionOwner = generateSigner(umi); + await printV1(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.ProgrammableNonFungible, + }).sendAndConfirm(umi); + + // Then the original NFT was updated. + const originalAsset = await fetchDigitalAsset(umi, originalMint.publicKey); + t.like(originalAsset, { + edition: { supply: 1n, maxSupply: some(10n) }, + }); + + // And the printed NFT was created with the same data. + const editionAsset = await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ); + t.like(editionAsset, { + publicKey: editionMint.publicKey, + metadata: { + name: 'My PNFT', + symbol: 'MPNFT', + uri: 'https://example.com/pnft.json', + sellerFeeBasisPoints: 542, + tokenStandard: some(TokenStandard.ProgrammableNonFungibleEdition), + }, + token: { + owner: editionOwner.publicKey, + amount: 1n, + }, + edition: { + isOriginal: false, + parent: findMasterEditionPda(umi, { mint: originalMint.publicKey })[0], + edition: 1n, + }, + }); +}); + +test('it can print a new edition from a NonFungible by initializing the mint beforehand', async (t) => { + // Given an existing master edition asset. + const umi = await createUmi(); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: percentAmount(5.42), + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.NonFungible, + }); + + // And given we have the mint of the printed edition initialized. + const editionMint = generateSigner(umi); + const editionMintAuthority = generateSigner(umi); + const editionOwner = generateSigner(umi); + await createMintWithAssociatedToken(umi, { + mint: editionMint, + owner: editionOwner.publicKey, + mintAuthority: editionMintAuthority, + freezeAuthority: editionMintAuthority.publicKey, + amount: 1, + }).sendAndConfirm(umi); + + // When we print a new edition of the asset. + await printV1(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionMintAuthority, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.NonFungible, + }).sendAndConfirm(umi); + + // Then the original NFT was updated. + const originalAsset = await fetchDigitalAsset(umi, originalMint.publicKey); + t.like(originalAsset, { + edition: { + isOriginal: true, + supply: 1n, + maxSupply: some(10n), + }, + }); + + // And the printed NFT was created with the same data. + const editionAsset = await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ); + t.like(editionAsset, { + publicKey: editionMint.publicKey, + metadata: { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: 542, + }, + token: { + owner: editionOwner.publicKey, + amount: 1n, + }, + edition: { + isOriginal: false, + parent: findMasterEditionPda(umi, { mint: originalMint.publicKey })[0], + edition: 1n, + }, + }); +}); + +test('it cannot print a new edition if the initialized edition mint account has more than 1 token of supply', async (t) => { + // Given an existing master edition asset. + const umi = await createUmi(); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.NonFungible, + }); + + // And given we have the mint of the printed edition initialized with more than 1 token. + const editionMint = generateSigner(umi); + const editionMintAuthority = generateSigner(umi); + const editionOwner = generateSigner(umi); + await createMintWithAssociatedToken(umi, { + mint: editionMint, + owner: editionOwner.publicKey, + mintAuthority: editionMintAuthority, + freezeAuthority: editionMintAuthority.publicKey, + amount: 2, + }).sendAndConfirm(umi); + + // When we try to print a new edition of the asset. + const promise = printV1(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionMintAuthority, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.NonFungible, + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'InvalidMintForTokenStandard' }); +}); diff --git a/configs/kinobi.cjs b/configs/kinobi.cjs index 65a8931e..e3fd0321 100755 --- a/configs/kinobi.cjs +++ b/configs/kinobi.cjs @@ -35,6 +35,13 @@ kinobi.update( ), ], }, + editionMarkerV2: { + seeds: [ + ...metadataSeeds, + k.stringConstantSeed("edition"), + k.stringConstantSeed("marker"), + ], + }, tokenRecord: { size: 80, seeds: [ @@ -675,6 +682,67 @@ kinobi.update( }, }, }, + printV1: { + accounts: { + editionMint: { isSigner: "either" }, + editionMintAuthority: { + defaultsTo: k.accountDefault("masterTokenAccountOwner"), + }, + masterTokenAccountOwner: { defaultsTo: k.identityDefault() }, + editionTokenAccountOwner: { defaultsTo: k.identityDefault() }, + editionMetadata: { + defaultsTo: k.pdaDefault("metadata", { + seeds: { mint: k.accountDefault("editionMint") }, + }), + }, + edition: { + defaultsTo: k.pdaDefault("masterEdition", { + seeds: { mint: k.accountDefault("editionMint") }, + }), + }, + editionMarkerPda: { + defaultsTo: k.resolverDefault("resolveEditionMarkerForPrint", [ + k.dependsOnArg("masterEditionMint"), + k.dependsOnArg("editionNumber"), + k.dependsOnArg("tokenStandard"), + ]), + }, + editionTokenAccount: { + defaultsTo: ataPdaDefault("editionMint", "editionTokenAccountOwner"), + }, + masterTokenAccount: { + defaultsTo: k.pdaDefault("associatedToken", { + importFrom: "mplToolbox", + seeds: { + mint: k.argDefault("masterEditionMint"), + owner: k.accountDefault("masterTokenAccountOwner"), + }, + }), + }, + masterMetadata: { + defaultsTo: k.pdaDefault("metadata", { + seeds: { mint: k.argDefault("masterEditionMint") }, + }), + }, + masterEdition: { + defaultsTo: k.pdaDefault("masterEdition", { + seeds: { mint: k.argDefault("masterEditionMint") }, + }), + }, + editionTokenRecord: { + defaultsTo: k.resolverDefault("resolveTokenRecordForPrint", [ + k.dependsOnAccount("editionMint"), + k.dependsOnAccount("editionTokenAccount"), + k.dependsOnArg("tokenStandard"), + ]), + }, + }, + args: { + edition: { name: "editionNumber" }, + masterEditionMint: { type: k.publicKeyTypeNode() }, + tokenStandard: { type: k.linkTypeNode("tokenStandard") }, + }, + }, // Update. updateAsAuthorityItemDelegateV2: updateAsMetadataDelegateDefaults("AuthorityItem"), diff --git a/programs/token-metadata/program/src/processor/metadata/print.rs b/programs/token-metadata/program/src/processor/metadata/print.rs index e776a42b..2ad8c0cf 100644 --- a/programs/token-metadata/program/src/processor/metadata/print.rs +++ b/programs/token-metadata/program/src/processor/metadata/print.rs @@ -106,11 +106,14 @@ fn print_v1(_program_id: &Pubkey, ctx: Context, args: PrintArgs) -> Progr &spl_token::instruction::initialize_mint2( token_program.key, edition_mint_info.key, - edition_account_info.key, - Some(edition_account_info.key), + edition_mint_authority_info.key, + Some(edition_mint_authority_info.key), 0, )?, - &[edition_mint_info.clone(), edition_account_info.clone()], + &[ + edition_mint_info.clone(), + edition_mint_authority_info.clone(), + ], )?; } else { // validates the existing mint account @@ -147,6 +150,23 @@ fn print_v1(_program_id: &Pubkey, ctx: Context, args: PrintArgs) -> Progr edition_token_account_info.clone(), ], )?; + + // mint one token to the associated token account + invoke( + &spl_token::instruction::mint_to( + &spl_token::id(), + edition_mint_info.key, + edition_token_account_info.key, + edition_mint_authority_info.key, + &[], + 1, + )?, + &[ + edition_mint_info.clone(), + edition_token_account_info.clone(), + edition_mint_authority_info.clone(), + ], + )?; } else { assert_owned_by(edition_token_account_info, &spl_token::id())?; let edition_token_account: spl_token::state::Account =