From ad18a590023cbc3f2e880f27c5e44fd3c3c6a5e8 Mon Sep 17 00:00:00 2001 From: Michael Sebastiyan Date: Wed, 6 Oct 2021 18:07:48 +0300 Subject: [PATCH 1/3] feature: implement open edition instant sale --- .../web/src/components/AuctionCard/index.tsx | 26 +-- .../web/src/views/auctionCreate/index.tsx | 153 +++++++++++------- 2 files changed, 109 insertions(+), 70 deletions(-) diff --git a/js/packages/web/src/components/AuctionCard/index.tsx b/js/packages/web/src/components/AuctionCard/index.tsx index 12a7cd9..ab28c09 100644 --- a/js/packages/web/src/components/AuctionCard/index.tsx +++ b/js/packages/web/src/components/AuctionCard/index.tsx @@ -21,6 +21,7 @@ import { MAX_EDITION_LEN, useWalletModal, VaultState, + BidStateType, } from '@oyster/common'; import { useWallet } from '@solana/wallet-adapter-react'; import { AuctionView, useBidsForAuction, useUserBalance } from '../../hooks'; @@ -254,14 +255,16 @@ export const AuctionCard = ({ const isAuctionNotStarted = auctionView.auction.info.state === AuctionState.Created; - //if instant sale auction bid and claimed hide buttons - if ( - (auctionView.isInstantSale && - Number(auctionView.myBidderPot?.info.emptied) !== 0 && - isAuctionManagerAuthorityNotWalletOwner && - auctionView.auction.info.bidState.max.toNumber() === bids.length) || - auctionView.vault.info.state === VaultState.Deactivated - ) { + const isOpenEditionInstantSale = auctionView.auction.info.bidState.type === BidStateType.OpenEdition; + const isInstantSaleHasNoItems = Number(auctionView.myBidderPot?.info.emptied) !== 0 && auctionView.auction.info.bidState.max.toNumber() === bids.length; + + const shouldHideInstantSale = auctionView.isInstantSale + && isAuctionManagerAuthorityNotWalletOwner + && (!isOpenEditionInstantSale && isInstantSaleHasNoItems) + + const shouldHide = shouldHideInstantSale || auctionView.vault.info.state === VaultState.Deactivated + + if (shouldHide) { return <>; } @@ -515,13 +518,12 @@ export const AuctionCard = ({ const instantSale = async () => { setLoading(true); + const instantSalePrice = auctionView.auctionDataExtended?.info.instantSalePrice; const winningConfigType = - auctionView.items[0][0].winningConfigType; - const isAuctionItemMaster = - winningConfigType === WinningConfigType.FullRightsTransfer || - winningConfigType === WinningConfigType.TokenOnlyTransfer; + auctionView.participationItem?.winningConfigType || auctionView.items[0][0].winningConfigType; + const isAuctionItemMaster = [WinningConfigType.FullRightsTransfer, WinningConfigType.TokenOnlyTransfer].includes(winningConfigType) const allowBidToPublic = myPayingAccount && !auctionView.myBidderPot && diff --git a/js/packages/web/src/views/auctionCreate/index.tsx b/js/packages/web/src/views/auctionCreate/index.tsx index 57531fd..950273b 100644 --- a/js/packages/web/src/views/auctionCreate/index.tsx +++ b/js/packages/web/src/views/auctionCreate/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { Divider, Steps, @@ -67,6 +67,12 @@ export enum AuctionCategory { Tiered, } +enum InstantSaleType { + Limited, + Single, + Open, +} + interface TierDummyEntry { safetyDepositBoxIndex: number; amount: number; @@ -123,6 +129,7 @@ export interface AuctionState { winnersCount: number; instantSalePrice?: number; + instantSaleType?: InstantSaleType; } export const AuctionCreateView = () => { @@ -173,26 +180,44 @@ export const AuctionCreateView = () => { const createAuction = async () => { let winnerLimit: WinnerLimit; - if (attributes.category === AuctionCategory.InstantSale) { - if (attributes.items.length > 0) { - const item = attributes.items[0]; - if (!attributes.editions) { + if (attributes.category === AuctionCategory.InstantSale && attributes.instantSaleType === InstantSaleType.Open) { + const { items, instantSalePrice } = attributes + + if (items.length > 0 && items[0].participationConfig) { + items[0].participationConfig.fixedPrice = new BN( + toLamports(instantSalePrice, mint) || 0, + ); + } + + winnerLimit = new WinnerLimit({ + type: WinnerLimitType.Unlimited, + usize: ZERO, + }); + } else if (attributes.category === AuctionCategory.InstantSale) { + const { items, editions } = attributes + + if (items.length > 0) { + const item = items[0]; + + if (!editions) { item.winningConfigType = item.metadata.info.updateAuthority === (wallet?.publicKey || SystemProgram.programId).toBase58() ? WinningConfigType.FullRightsTransfer : WinningConfigType.TokenOnlyTransfer; } + item.amountRanges = [ new AmountRange({ amount: new BN(1), - length: new BN(attributes.editions || 1), + length: new BN(editions || 1), }), ]; } + winnerLimit = new WinnerLimit({ type: WinnerLimitType.Capped, - usize: new BN(attributes.editions || 1), + usize: new BN(editions || 1), }); } else if (attributes.category === AuctionCategory.Open) { if ( @@ -447,19 +472,24 @@ export const AuctionCreateView = () => { name: null, }; + + const isOpenEdition = attributes.category === AuctionCategory.Open || attributes.instantSaleType === InstantSaleType.Open + const safetyDepositDrafts = isOpenEdition + ? [] + : attributes.category !== AuctionCategory.Tiered + ? attributes.items + : tieredAttributes.items; + const participationSafetyDepositDraft = isOpenEdition + ? attributes.items[0] + : attributes.participationNFT; + const _auctionObj = await createAuctionManager( connection, wallet, whitelistedCreatorsByCreator, auctionSettings, - attributes.category === AuctionCategory.Open - ? [] - : attributes.category !== AuctionCategory.Tiered - ? attributes.items - : tieredAttributes.items, - attributes.category === AuctionCategory.Open - ? attributes.items[0] - : attributes.participationNFT, + safetyDepositDrafts, + participationSafetyDepositDraft, QUOTE_MINT.toBase58(), ); setAuctionObj(_auctionObj); @@ -743,19 +773,21 @@ const CategoryStep = (props: { ); }; -const InstantSaleStep = (props: { +const InstantSaleStep = ({ + attributes, setAttributes, confirm +}: { attributes: AuctionState; setAttributes: (attr: AuctionState) => void; confirm: () => void; }) => { - const [copiesChecked, setCopiesChecked] = useState(false); - const copiesEnabled = React.useMemo( - () => !!props.attributes?.items?.[0]?.masterEdition?.info?.maxSupply, - [props.attributes?.items?.[0]], + const copiesEnabled = useMemo( + () => !!attributes?.items?.[0]?.masterEdition?.info?.maxSupply, + [attributes?.items?.[0]], ); + const artistFilter = useCallback((i: SafetyDepositDraft) => !(i.metadata.info.data.creators || []).some((c: Creator) => !c.verified), []) - let artistFilter = (i: SafetyDepositDraft) => - !(i.metadata.info.data.creators || []).find((c: Creator) => !c.verified); + const isLimitedEdition = attributes.instantSaleType === InstantSaleType.Limited; + const shouldRenderSelect = attributes.items.length > 0; return ( <> @@ -767,46 +799,51 @@ const InstantSaleStep = (props: { { - props.setAttributes({ ...props.attributes, items }); + setAttributes({ ...attributes, items }); }} allowMultiple={false} > Select NFT - + {shouldRenderSelect && ( + + )}