diff --git a/packages/metavinci/src/actions/addTokensToVault.ts b/packages/metavinci/src/actions/addTokensToVault.ts index d6af722..6a47fc3 100644 --- a/packages/metavinci/src/actions/addTokensToVault.ts +++ b/packages/metavinci/src/actions/addTokensToVault.ts @@ -4,13 +4,28 @@ import { PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { utils, actions, models } from '@oyster/common'; +import { + utils, + actions, + models, + MasterEdition, + Metadata, + ParsedAccount, +} from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; import BN from 'bn.js'; +import { SafetyDepositDraft } from './createAuctionManager'; const { createTokenAccount, addTokenToInactiveVault, VAULT_PREFIX } = actions; const { approve } = models; +export interface SafetyDepositInstructionConfig { + tokenAccount: PublicKey; + tokenMint: PublicKey; + amount: BN; + draft: SafetyDepositDraft; +} + const BATCH_SIZE = 4; // This command batches out adding tokens to a vault using a prefilled payer account, and then activates and combines // the vault for use. It issues a series of transaction instructions and signers for the sendTransactions batch. @@ -18,7 +33,7 @@ export async function addTokensToVault( connection: Connection, wallet: any, vault: PublicKey, - nfts: { tokenAccount: PublicKey; tokenMint: PublicKey; amount: BN }[], + nfts: SafetyDepositInstructionConfig[], ): Promise<{ instructions: Array; signers: Array; diff --git a/packages/metavinci/src/actions/createAuctionManager.ts b/packages/metavinci/src/actions/createAuctionManager.ts index 39afcf2..cfb70b2 100644 --- a/packages/metavinci/src/actions/createAuctionManager.ts +++ b/packages/metavinci/src/actions/createAuctionManager.ts @@ -28,10 +28,14 @@ import { initAuctionManager, startAuction, validateSafetyDepositBox, + WinningConfig, } from '../models/metaplex'; import { createVault } from './createVault'; import { closeVault } from './closeVault'; -import { addTokensToVault } from './addTokensToVault'; +import { + addTokensToVault, + SafetyDepositInstructionConfig, +} from './addTokensToVault'; import { makeAuction } from './makeAuction'; import { createExternalPriceAccount } from './createExternalPriceAccount'; const { createTokenAccount } = actions; @@ -75,7 +79,8 @@ export async function createAuctionManager( winnerLimit: WinnerLimit, endAuctionAt: BN, auctionGap: BN, - safetyDeposits: SafetyDepositDraft[], + safetyDepositDrafts: SafetyDepositDraft[], + openEditionSafetyDepositDraft: SafetyDepositDraft | undefined, paymentMint: PublicKey, ): Promise<{ vault: PublicKey; @@ -111,37 +116,11 @@ export async function createAuctionManager( paymentMint, ); - let nftConfigs = safetyDeposits.map((w, i) => { - let winningConfig = settings.winningConfigs.find( - ow => ow.safetyDepositBoxIndex == i, - ); - let mintToUse = w.metadata.info.mint; - let holdingToUse = w.holding; - // When selling a Limited Edition with intention to print copies of it, we use it's master mint - // as token type because we are actually selling authorization tokens, not the limited edition itself.. - if ( - winningConfig && - winningConfig.editionType == EditionType.LimitedEdition && - w.masterEdition?.info.masterMint && - w.masterMintHolding - ) { - mintToUse = w.masterEdition?.info.masterMint; - holdingToUse = w.masterMintHolding; - } - return { - tokenAccount: holdingToUse, - tokenMint: mintToUse, - amount: new BN(winningConfig?.amount || 1), - }; - }); - - let openEditionSafetyDeposit = undefined; - if ( - settings.openEditionConfig != null && - settings.openEditionConfig != undefined - ) { - openEditionSafetyDeposit = safetyDeposits[settings.openEditionConfig]; - } + let safetyDepositConfigs = buildSafetyDepositArray( + safetyDepositDrafts, + openEditionSafetyDepositDraft, + settings.winningConfigs, + ); const { instructions: auctionManagerInstructions, @@ -153,14 +132,14 @@ export async function createAuctionManager( vault, paymentMint, settings, - openEditionSafetyDeposit, + openEditionSafetyDepositDraft, ); const { instructions: addTokenInstructions, signers: addTokenSigners, stores, - } = await addTokensToVault(connection, wallet, vault, nftConfigs); + } = await addTokensToVault(connection, wallet, vault, safetyDepositConfigs); let lookup: byType = { externalPriceAccount: { @@ -194,8 +173,13 @@ export async function createAuctionManager( validateBoxes: await validateBoxes( wallet, vault, - // No need to validate open edition, it's already been during init - safetyDeposits.filter((_, i) => i != settings.openEditionConfig), + // No need to validate open edition, it's already been during init, or if not present, let all in + safetyDepositConfigs.filter( + c => + !openEditionSafetyDepositDraft || + c.draft.metadata.pubkey.toBase58() != + openEditionSafetyDepositDraft.metadata.pubkey.toBase58(), + ), stores, settings, ), @@ -238,13 +222,75 @@ export async function createAuctionManager( return { vault, auction, auctionManager }; } +function buildSafetyDepositArray( + safetyDeposits: SafetyDepositDraft[], + openEditionSafetyDepositDraft: SafetyDepositDraft | undefined, + winningConfigs: WinningConfig[], +): SafetyDepositInstructionConfig[] { + let safetyDepositConfig: SafetyDepositInstructionConfig[] = []; + safetyDeposits.forEach((w, i) => { + let winningConfigsThatShareThisBox = winningConfigs.filter( + ow => ow.safetyDepositBoxIndex == i, + ); + + // Configs where we are selling this safety deposit as a master edition or single nft + let nonLimitedEditionConfigs = winningConfigsThatShareThisBox.filter( + ow => ow.editionType != EditionType.LimitedEdition, + ); + // we may also have an auction where we are selling prints of the master too as secondary prizes + let limitedEditionConfigs = winningConfigsThatShareThisBox.filter( + ow => ow.editionType == EditionType.LimitedEdition, + ); + + const nonLimitedEditionTotal = nonLimitedEditionConfigs + .map(ow => ow.amount) + .reduce((sum, acc) => (sum += acc), 0); + const limitedEditionTotal = limitedEditionConfigs + .map(ow => ow.amount) + .reduce((sum, acc) => (sum += acc), 0); + + if (nonLimitedEditionTotal > 0) { + safetyDepositConfig.push({ + tokenAccount: w.holding, + tokenMint: w.metadata.info.mint, + amount: new BN(nonLimitedEditionTotal), + draft: w, + }); + } + + if ( + limitedEditionTotal > 0 && + w.masterEdition?.info.masterMint && + w.masterMintHolding + ) { + safetyDepositConfig.push({ + tokenAccount: w.masterMintHolding, + tokenMint: w.masterEdition?.info.masterMint, + amount: new BN(limitedEditionTotal), + draft: w, + }); + } + }); + + if (openEditionSafetyDepositDraft) { + safetyDepositConfig.push({ + tokenAccount: openEditionSafetyDepositDraft.holding, + tokenMint: openEditionSafetyDepositDraft.metadata.info.mint, + amount: new BN(1), + draft: openEditionSafetyDepositDraft, + }); + } + + return safetyDepositConfig; +} + async function setupAuctionManagerInstructions( connection: Connection, wallet: any, vault: PublicKey, paymentMint: PublicKey, settings: AuctionManagerSettings, - openEditionSafetyDeposit?: SafetyDepositDraft, + openEditionSafetyDepositDraft?: SafetyDepositDraft, ): Promise<{ instructions: TransactionInstruction[]; signers: Account[]; @@ -269,12 +315,12 @@ async function setupAuctionManagerInstructions( await initAuctionManager( vault, - openEditionSafetyDeposit?.metadata.pubkey, - openEditionSafetyDeposit?.nameSymbol?.pubkey, + openEditionSafetyDepositDraft?.metadata.pubkey, + openEditionSafetyDepositDraft?.nameSymbol?.pubkey, wallet.publicKey, - openEditionSafetyDeposit?.masterEdition?.pubkey, - openEditionSafetyDeposit?.metadata.info.mint, - openEditionSafetyDeposit?.masterEdition?.info.masterMint, + openEditionSafetyDepositDraft?.masterEdition?.pubkey, + openEditionSafetyDepositDraft?.metadata.info.mint, + openEditionSafetyDepositDraft?.masterEdition?.info.masterMint, wallet.publicKey, wallet.publicKey, wallet.publicKey, @@ -304,7 +350,7 @@ async function setupStartAuction( async function validateBoxes( wallet: any, vault: PublicKey, - safetyDeposits: SafetyDepositDraft[], + safetyDeposits: SafetyDepositInstructionConfig[], stores: PublicKey[], settings: AuctionManagerSettings, ): Promise<{ @@ -327,40 +373,40 @@ async function validateBoxes( if (winningConfig) { if ( winningConfig.editionType == EditionType.LimitedEdition && - safetyDeposits[i].masterEdition && - safetyDeposits[i].masterEdition?.info.masterMint + safetyDeposits[i].draft.masterEdition && + safetyDeposits[i].draft.masterEdition?.info.masterMint ) safetyDepositBox = await getSafetyDepositBox( vault, //@ts-ignore - safetyDeposits[i].masterEdition.info.masterMint, + safetyDeposits[i].draft.masterEdition.info.masterMint, ); else safetyDepositBox = await getSafetyDepositBox( vault, - safetyDeposits[i].metadata.info.mint, + safetyDeposits[i].draft.metadata.info.mint, ); const edition: PublicKey = await getEdition( - safetyDeposits[i].metadata.info.mint, + safetyDeposits[i].draft.metadata.info.mint, ); await validateSafetyDepositBox( vault, - safetyDeposits[i].metadata.pubkey, - safetyDeposits[i].nameSymbol?.pubkey, + safetyDeposits[i].draft.metadata.pubkey, + safetyDeposits[i].draft.nameSymbol?.pubkey, safetyDepositBox, stores[i], //@ts-ignore winningConfig.editionType == EditionType.LimitedEdition - ? safetyDeposits[i].masterEdition?.info.masterMint - : safetyDeposits[i].metadata.info.mint, + ? safetyDeposits[i].draft.masterEdition?.info.masterMint + : safetyDeposits[i].draft.metadata.info.mint, wallet.publicKey, wallet.publicKey, wallet.publicKey, tokenInstructions, edition, - safetyDeposits[i].masterEdition?.info.masterMint, - safetyDeposits[i].masterEdition ? wallet.publicKey : undefined, + safetyDeposits[i].draft.masterEdition?.info.masterMint, + safetyDeposits[i].draft.masterEdition ? wallet.publicKey : undefined, ); } signers.push(tokenSigners); diff --git a/packages/metavinci/src/views/auctionCreate/index.tsx b/packages/metavinci/src/views/auctionCreate/index.tsx index b7b4ac5..e90d541 100644 --- a/packages/metavinci/src/views/auctionCreate/index.tsx +++ b/packages/metavinci/src/views/auctionCreate/index.tsx @@ -175,6 +175,31 @@ export const AuctionCreateView = () => { attributes.category == AuctionCategory.Limited || attributes.category == AuctionCategory.Single ) { + // In these cases there is only ever one item in the array. + + let winningConfigs: WinningConfig[]; + if (attributes.category == AuctionCategory.Single) + winningConfigs = [ + new WinningConfig({ + safetyDepositBoxIndex: 0, + amount: 1, + editionType: attributes.items[0].masterEdition + ? EditionType.MasterEdition + : EditionType.NA, + }), + ]; + else { + winningConfigs = []; + for (let i = 0; i < (attributes.editions || 1); i++) { + winningConfigs.push( + new WinningConfig({ + safetyDepositBoxIndex: 0, + amount: 1, + editionType: EditionType.LimitedEdition, + }), + ); + } + } settings = new AuctionManagerSettings({ openEditionWinnerConstraint: attributes.participationNFT ? WinningConstraint.OpenEditionGiven @@ -182,20 +207,7 @@ export const AuctionCreateView = () => { openEditionNonWinningConstraint: attributes.participationNFT ? NonWinningConstraint.GivenForFixedPrice : NonWinningConstraint.NoOpenEdition, - winningConfigs: attributes.items.map( - (item, index) => - new WinningConfig({ - // TODO: check index - safetyDepositBoxIndex: index, - amount: 1, - editionType: - attributes.category == AuctionCategory.Limited - ? EditionType.LimitedEdition - : item.masterEdition - ? EditionType.MasterEdition - : EditionType.NA, - }), - ), + winningConfigs, openEditionConfig: attributes.participationNFT ? attributes.items.length : null, @@ -222,10 +234,8 @@ export const AuctionCreateView = () => { winnerLimit, new BN(endAuctionAt), new BN((attributes.gapTime || 0) * 60), - [ - ...attributes.items, - ...(attributes.participationNFT ? [attributes.participationNFT] : []), - ], + attributes.items, + attributes.participationNFT, // TODO: move to config new PublicKey('4XEUcBjLyBHuMDKTARycf4VXqpsAsDcThMbhWgFuDGsC'), ); @@ -527,7 +537,7 @@ const CopiesStep = (props: { > Select NFT - {props.attributes.category !== AuctionCategory.Open && ( + {props.attributes.category == AuctionCategory.Limited && (