From 7839353a409b16490cf05ae9fa68c8b52f187b1c Mon Sep 17 00:00:00 2001 From: Aaron Heckmann Date: Mon, 17 Jan 2022 09:44:17 -0800 Subject: [PATCH] fix: allow Master Edition max supply to be 0 (#147) * fix: allow Master Edition max supply to be 0 * test: use familiar name * chore: fix lint issues --- src/actions/mintNFT.ts | 6 +- test/actions/createMetadataAndME.test.ts | 6 +- test/actions/mintEditionFromMaster.test.ts | 4 +- test/actions/mintNFT.test.ts | 86 +++++++++++++--------- test/actions/signMetadata.test.ts | 6 +- test/actions/updateMetadata.test.ts | 6 +- test/actions/utility/closeVault.test.ts | 6 +- test/actions/utility/createVault.test.ts | 4 +- test/actions/utility/initAuction.test.ts | 4 +- test/utils.ts | 8 +- 10 files changed, 75 insertions(+), 61 deletions(-) diff --git a/src/actions/mintNFT.ts b/src/actions/mintNFT.ts index 24de79f..aad0b30 100644 --- a/src/actions/mintNFT.ts +++ b/src/actions/mintNFT.ts @@ -14,14 +14,14 @@ import { sendTransaction } from './transactions'; import { lookup } from '../utils/metadata'; import { prepareTokenAccountAndMintTxs } from './shared'; -interface MintNFTParams { +export interface MintNFTParams { connection: Connection; wallet: Wallet; uri: string; maxSupply?: number; } -interface MintNFTResponse { +export interface MintNFTResponse { txId: string; mint: PublicKey; metadata: PublicKey; @@ -90,7 +90,7 @@ export const mintNFT = async ({ updateAuthority: wallet.publicKey, mint: mint.publicKey, mintAuthority: wallet.publicKey, - maxSupply: maxSupply ? new BN(maxSupply) : null, + maxSupply: maxSupply || maxSupply === 0 ? new BN(maxSupply) : null, }, ); diff --git a/test/actions/createMetadataAndME.test.ts b/test/actions/createMetadataAndME.test.ts index 97242ad..6fedb88 100644 --- a/test/actions/createMetadataAndME.test.ts +++ b/test/actions/createMetadataAndME.test.ts @@ -1,7 +1,7 @@ import BN from 'bn.js'; import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { Connection, NodeWallet } from '../../src'; -import { FEE_PAYER, NETWORK, pause } from '../utils'; +import { FEE_PAYER, NETWORK, sleep } from '../utils'; import { Creator, MasterEdition, @@ -51,7 +51,7 @@ describe('creatomg metadata and master edition PDAs', () => { metadataData, }); - await pause(20000); + await sleep(20000); const metadata = await Metadata.getPDA(mint.publicKey); const metadataInfo = await Account.getInfo(connection, metadata); @@ -66,7 +66,7 @@ describe('creatomg metadata and master edition PDAs', () => { }); // had to increase to 25s, or it was failing - await pause(25000); + await sleep(25000); const edition = await MasterEdition.getPDA(mint.publicKey); const editionInfo = await Account.getInfo(connection, edition); diff --git a/test/actions/mintEditionFromMaster.test.ts b/test/actions/mintEditionFromMaster.test.ts index 5843a0f..d5035f0 100644 --- a/test/actions/mintEditionFromMaster.test.ts +++ b/test/actions/mintEditionFromMaster.test.ts @@ -1,6 +1,6 @@ import { Connection, NodeWallet } from '../../src'; import { mintNFT } from '../../src/actions'; -import { FEE_PAYER, NETWORK, pause } from '../utils'; +import { FEE_PAYER, NETWORK, sleep } from '../utils'; import { MasterEdition, Metadata } from '@metaplex-foundation/mpl-token-metadata'; import { mintEditionFromMaster } from '../../src/actions/mintEditionFromMaster'; import { mockAxios200, uri } from './shared'; @@ -26,7 +26,7 @@ describe('minting a limited edition from master', () => { // unfortunately it takes some time for the master mint to propagate // empirically, I found anything below 20s to be unreliable - await pause(20000); + await sleep(20000); const editionMintResponse = await mintEditionFromMaster({ connection, diff --git a/test/actions/mintNFT.test.ts b/test/actions/mintNFT.test.ts index 5ffee9f..0c25801 100644 --- a/test/actions/mintNFT.test.ts +++ b/test/actions/mintNFT.test.ts @@ -1,57 +1,75 @@ import { Keypair } from '@solana/web3.js'; import { Connection, NodeWallet } from '../../src'; -import { mintNFT } from '../../src/actions'; -import { FEE_PAYER, NETWORK } from '../utils'; +import { mintNFT, MintNFTParams } from '../../src/actions'; +import { sleep } from '../utils'; import { MasterEdition, Metadata } from '@metaplex-foundation/mpl-token-metadata'; -import { mockAxios200, mockAxios404, uri } from './shared'; +import { uri, generateConnectionAndWallet, mockAxios200, mockAxios404 } from './shared'; +import { airdrop } from '@metaplex-foundation/amman'; jest.mock('axios'); describe('minting an NFT', () => { - const connection = new Connection(NETWORK); - const wallet = new NodeWallet(FEE_PAYER); - let mint: Keypair; + let connection: Connection; + let wallet: NodeWallet; + let payer: Keypair; - beforeAll(() => { - jest - .spyOn(connection, 'sendRawTransaction') - .mockResolvedValue( - '64Tpr1DNj9UWg1P89Zss5Y4Mh2gGyRUMYZPNenZKY2hiNjsotrCDMBriDrsvhg5BJt3mY4hH6jcparNHCZGhAwf6', - ); - }); + beforeAll(async () => { + const result = await generateConnectionAndWallet(); + connection = result.connection; + wallet = result.wallet; + payer = result.payer; - beforeEach(() => { - mint = Keypair.generate(); - jest.spyOn(Keypair, 'generate').mockReturnValue(mint); + await airdrop(connection, payer.publicKey, 2); }); - describe('when can find metadata json', () => { - beforeEach(() => { - mockAxios200(wallet); + describe('when metadata json is found', () => { + beforeAll(async () => { + mockAxios200(wallet); // metadata json found }); - test('generates a unique mint and creates metadata plus master edition from metadata URL and max supply', async () => { - const mintResponse = await mintNFT({ - connection, - wallet, - uri, - maxSupply: 0, - }); + const values = [0, 3, undefined]; + for (const maxSupply of values) { + describe(`when max supply is "${maxSupply}"`, () => { + test('generates a unique mint, metadata & master editions from metadata URL', async () => { + const arg: MintNFTParams = { + connection, + wallet, + uri, + }; + + if (maxSupply !== undefined) { + arg.maxSupply = maxSupply; + } + + const mintResponse = await mintNFT(arg); + const { mint } = mintResponse; - const metadata = await Metadata.getPDA(mint.publicKey); - const edition = await MasterEdition.getPDA(mint.publicKey); + const metadata = await Metadata.getPDA(mint); + const edition = await MasterEdition.getPDA(mint); - expect(mintResponse).toMatchObject({ - metadata, - edition, - mint: mint.publicKey, + expect(mintResponse).toMatchObject({ + metadata, + edition, + }); + + await sleep(2000); // HACK + + const metadataEdition = (await Metadata.getEdition(connection, mint)) as MasterEdition; + expect(metadataEdition.data?.maxSupply?.toNumber()).toBe(maxSupply); + }); }); - }); + } }); describe('when metadata json not found', () => { beforeEach(() => { mockAxios404(); + + jest + .spyOn(connection, 'sendRawTransaction') + .mockResolvedValue( + '64Tpr1DNj9UWg1P89Zss5Y4Mh2gGyRUMYZPNenZKY2hiNjsotrCDMBriDrsvhg5BJt3mY4hH6jcparNHCZGhAwf6', + ); }); test('exits the action and throws an error', async () => { @@ -63,7 +81,7 @@ describe('minting an NFT', () => { maxSupply: 0, }); } catch (e) { - expect(e).not.toBeNull(); + expect(e.message).toMatch(/unable to get metadata/); } }); }); diff --git a/test/actions/signMetadata.test.ts b/test/actions/signMetadata.test.ts index a4031f3..fb25847 100644 --- a/test/actions/signMetadata.test.ts +++ b/test/actions/signMetadata.test.ts @@ -1,7 +1,7 @@ import { Keypair } from '@solana/web3.js'; import { Connection, NodeWallet } from '../../src'; import { mintNFT } from '../../src/actions'; -import { FEE_PAYER, NETWORK, pause } from '../utils'; +import { FEE_PAYER, NETWORK, sleep } from '../utils'; import { Metadata } from '@metaplex-foundation/mpl-token-metadata'; import { signMetadata } from '../../src/actions/signMetadata'; import { mockAxios200, uri } from './shared'; @@ -30,7 +30,7 @@ describe('signing metadata on a master edition', () => { // unfortunately it takes some time for the master mint to propagate // empirically, I found anything below 20s to be unreliable - await pause(20000); + await sleep(20000); // before signing const metadata = await Metadata.getPDA(masterMintResponse.mint); @@ -45,7 +45,7 @@ describe('signing metadata on a master edition', () => { signer: secondSigner, }); - await pause(20000); + await sleep(20000); //after signing info = await Account.getInfo(connection, metadata); diff --git a/test/actions/updateMetadata.test.ts b/test/actions/updateMetadata.test.ts index 96795e8..2e18c7f 100644 --- a/test/actions/updateMetadata.test.ts +++ b/test/actions/updateMetadata.test.ts @@ -1,7 +1,7 @@ import { Keypair } from '@solana/web3.js'; import { Connection, NodeWallet } from '../../src'; import { mintNFT } from '../../src/actions'; -import { FEE_PAYER, NETWORK, pause } from '../utils'; +import { FEE_PAYER, NETWORK, sleep } from '../utils'; import { Creator, Metadata, MetadataDataData } from '@metaplex-foundation/mpl-token-metadata'; import { updateMetadata } from '../../src/actions/updateMetadata'; import { mockAxios200, uri } from './shared'; @@ -44,7 +44,7 @@ describe('updating metadata on a master edition', () => { // unfortunately it takes some time for the master mint to propagate // empirically, I found anything below 20s to be unreliable - await pause(20000); + await sleep(20000); // before update const metadata = await Metadata.getPDA(masterMintResponse.mint); @@ -63,7 +63,7 @@ describe('updating metadata on a master edition', () => { primarySaleHappened: true, }); - await pause(20000); + await sleep(20000); //after update info = await Account.getInfo(connection, metadata); diff --git a/test/actions/utility/closeVault.test.ts b/test/actions/utility/closeVault.test.ts index 36fedb3..2525f41 100644 --- a/test/actions/utility/closeVault.test.ts +++ b/test/actions/utility/closeVault.test.ts @@ -1,7 +1,7 @@ import { NATIVE_MINT } from '@solana/spl-token'; import { Vault, VaultState } from '@metaplex-foundation/mpl-token-vault'; -import { pause } from '../../utils'; +import { sleep } from '../../utils'; import { generateConnectionAndWallet } from '../shared'; import { closeVault, createVault, createExternalPriceAccount } from '../../../src/actions/utility'; @@ -19,7 +19,7 @@ describe('closing a Vault', () => { ...externalPriceAccountData, }); - await pause(1000); + await sleep(1000); vault = await Vault.load(connection, vaultResponse.vault); expect(vault).toHaveProperty('data'); @@ -32,7 +32,7 @@ describe('closing a Vault', () => { priceMint: NATIVE_MINT, }); - await pause(1000); + await sleep(1000); vault = await Vault.load(connection, vaultResponse.vault); expect(vault).toHaveProperty('data'); diff --git a/test/actions/utility/createVault.test.ts b/test/actions/utility/createVault.test.ts index 9bb6bce..a19c954 100644 --- a/test/actions/utility/createVault.test.ts +++ b/test/actions/utility/createVault.test.ts @@ -1,6 +1,6 @@ import { Vault, VaultState } from '@metaplex-foundation/mpl-token-vault'; -import { pause } from '../../utils'; +import { sleep } from '../../utils'; import { createVault, createExternalPriceAccount } from '../../../src/actions/utility'; import { generateConnectionAndWallet } from '../shared'; @@ -17,7 +17,7 @@ describe('creating a Vault', () => { ...externalPriceAccountData, }); - await pause(1000); + await sleep(1000); const vault = await Vault.load(connection, vaultResponse.vault); expect(vault).toHaveProperty('data'); expect(vault.data.state).toEqual(VaultState.Inactive); diff --git a/test/actions/utility/initAuction.test.ts b/test/actions/utility/initAuction.test.ts index 589840f..5c9fb7f 100644 --- a/test/actions/utility/initAuction.test.ts +++ b/test/actions/utility/initAuction.test.ts @@ -8,7 +8,7 @@ import { WinnerLimitType, } from '@metaplex-foundation/mpl-auction'; -import { pause } from '../../utils'; +import { sleep } from '../../utils'; import { generateConnectionAndWallet } from '../shared'; import { createExternalPriceAccount, createVault, initAuction } from '../../../src/actions/utility'; @@ -45,7 +45,7 @@ describe('initAuction action', () => { auctionSettings, }); - await pause(1000); + await sleep(1000); const auctionInstance = await Auction.load(connection, auction); diff --git a/test/utils.ts b/test/utils.ts index 26f4462..5153768 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -111,12 +111,8 @@ export const projectRoot = path.resolve(__dirname, '..', '..'); export const tmpTestDir = path.resolve(tmpdir(), 'test'); export const serializeConfig = { verifySignatures: false, requireAllSignatures: false }; -export async function pause(ms: number) { - await new Promise((response) => - setTimeout(() => { - response(0); - }, ms), - ); +export async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); } export function getUserKeypairFromFile(keypairPath) {