Merge pull request #644 from m-sebastiyan/feature/open-edition-instant-sale

feat: implement open edition instant sale
This commit is contained in:
Adam Jeffries 2021-10-07 09:36:56 -05:00 committed by GitHub
commit f4fbe26bd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 71 deletions

View File

@ -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,23 @@ 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 isOpenEditionSale =
auctionView.auction.info.bidState.type === BidStateType.OpenEdition;
const doesInstantSaleHasNoItems =
Number(auctionView.myBidderPot?.info.emptied) !== 0 &&
auctionView.auction.info.bidState.max.toNumber() === bids.length;
const shouldHideInstantSale =
!isOpenEditionSale &&
auctionView.isInstantSale &&
isAuctionManagerAuthorityNotWalletOwner &&
doesInstantSaleHasNoItems;
const shouldHide =
shouldHideInstantSale ||
auctionView.vault.info.state === VaultState.Deactivated;
if (shouldHide) {
return <></>;
}
@ -515,13 +525,16 @@ export const AuctionCard = ({
const instantSale = async () => {
setLoading(true);
const instantSalePrice =
auctionView.auctionDataExtended?.info.instantSalePrice;
const winningConfigType =
auctionView.participationItem?.winningConfigType ||
auctionView.items[0][0].winningConfigType;
const isAuctionItemMaster =
winningConfigType === WinningConfigType.FullRightsTransfer ||
winningConfigType === WinningConfigType.TokenOnlyTransfer;
const isAuctionItemMaster = [
WinningConfigType.FullRightsTransfer,
WinningConfigType.TokenOnlyTransfer,
].includes(winningConfigType);
const allowBidToPublic =
myPayingAccount &&
!auctionView.myBidderPot &&
@ -532,7 +545,10 @@ export const AuctionCard = ({
isAuctionItemMaster;
// Placing a "bid" of the full amount results in a purchase to redeem.
if (instantSalePrice && (allowBidToPublic || allowBidToAuctionOwner)) {
if (
instantSalePrice &&
(allowBidToPublic || allowBidToAuctionOwner)
) {
try {
const bid = await sendPlaceBid(
connection,

View File

@ -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,47 @@ 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 +475,25 @@ 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 +777,28 @@ 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 +810,63 @@ const InstantSaleStep = (props: {
<Col xl={24}>
<ArtSelector
filter={artistFilter}
selected={props.attributes.items}
selected={attributes.items}
setSelected={items => {
props.setAttributes({ ...props.attributes, items });
setAttributes({ ...attributes, items });
}}
allowMultiple={false}
>
Select NFT
</ArtSelector>
<label className="action-field">
<Checkbox
defaultChecked={false}
checked={copiesChecked}
disabled={!copiesEnabled}
onChange={e => setCopiesChecked(e.target.checked)}
>
<span className="field-title">
Create copies of a Master Edition NFT?
</span>
</Checkbox>
{copiesChecked && copiesEnabled && (
<>
<span className="field-info">
Each copy will be given unique edition number e.g. 1 of 30
</span>
<Input
autoFocus
className="input"
placeholder="Enter number of copies sold"
allowClear
onChange={info =>
props.setAttributes({
...props.attributes,
editions: parseInt(info.target.value),
})
}
/>
</>
)}
</label>
{shouldRenderSelect && (
<label className="action-field">
<Select
defaultValue={
attributes.instantSaleType || InstantSaleType.Single
}
onChange={value =>
setAttributes({
...attributes,
instantSaleType: value,
})
}
>
<Option value={InstantSaleType.Single}>
Sell unique token
</Option>
{copiesEnabled && (
<Option value={InstantSaleType.Limited}>
Sell limited number of copies
</Option>
)}
{!copiesEnabled && (
<Option value={InstantSaleType.Open}>
Sell unlimited number of copies
</Option>
)}
</Select>
{isLimitedEdition && (
<>
<span className="field-info">
Each copy will be given unique edition number e.g. 1 of 30
</span>
<Input
autoFocus
className="input"
placeholder="Enter number of copies sold"
allowClear
onChange={info =>
setAttributes({
...attributes,
editions: parseInt(info.target.value),
})
}
/>
</>
)}
</label>
)}
<label className="action-field">
<span className="field-title">Price</span>
@ -822,8 +882,8 @@ const InstantSaleStep = (props: {
prefix="◎"
suffix="SOL"
onChange={info =>
props.setAttributes({
...props.attributes,
setAttributes({
...attributes,
priceFloor: parseFloat(info.target.value),
instantSalePrice: parseFloat(info.target.value),
})
@ -837,7 +897,7 @@ const InstantSaleStep = (props: {
type="primary"
size="large"
onClick={() => {
props.confirm();
confirm();
}}
className="action-btn"
>

View File

@ -25,7 +25,7 @@ export const AuctionListView = () => {
const auctions = useAuctions(AuctionViewState.Live);
const auctionsEnded = [
...useAuctions(AuctionViewState.Ended),
...useAuctions(AuctionViewState.BuyNow)
...useAuctions(AuctionViewState.BuyNow),
];
const [activeKey, setActiveKey] = useState(LiveAuctionViewState.All);
const { isLoading } = useMeta();