Merge pull request #644 from m-sebastiyan/feature/open-edition-instant-sale
feat: implement open edition instant sale
This commit is contained in:
commit
f4fbe26bd0
|
@ -21,6 +21,7 @@ import {
|
||||||
MAX_EDITION_LEN,
|
MAX_EDITION_LEN,
|
||||||
useWalletModal,
|
useWalletModal,
|
||||||
VaultState,
|
VaultState,
|
||||||
|
BidStateType,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import { useWallet } from '@solana/wallet-adapter-react';
|
import { useWallet } from '@solana/wallet-adapter-react';
|
||||||
import { AuctionView, useBidsForAuction, useUserBalance } from '../../hooks';
|
import { AuctionView, useBidsForAuction, useUserBalance } from '../../hooks';
|
||||||
|
@ -254,14 +255,23 @@ export const AuctionCard = ({
|
||||||
const isAuctionNotStarted =
|
const isAuctionNotStarted =
|
||||||
auctionView.auction.info.state === AuctionState.Created;
|
auctionView.auction.info.state === AuctionState.Created;
|
||||||
|
|
||||||
//if instant sale auction bid and claimed hide buttons
|
const isOpenEditionSale =
|
||||||
if (
|
auctionView.auction.info.bidState.type === BidStateType.OpenEdition;
|
||||||
(auctionView.isInstantSale &&
|
const doesInstantSaleHasNoItems =
|
||||||
Number(auctionView.myBidderPot?.info.emptied) !== 0 &&
|
Number(auctionView.myBidderPot?.info.emptied) !== 0 &&
|
||||||
|
auctionView.auction.info.bidState.max.toNumber() === bids.length;
|
||||||
|
|
||||||
|
const shouldHideInstantSale =
|
||||||
|
!isOpenEditionSale &&
|
||||||
|
auctionView.isInstantSale &&
|
||||||
isAuctionManagerAuthorityNotWalletOwner &&
|
isAuctionManagerAuthorityNotWalletOwner &&
|
||||||
auctionView.auction.info.bidState.max.toNumber() === bids.length) ||
|
doesInstantSaleHasNoItems;
|
||||||
auctionView.vault.info.state === VaultState.Deactivated
|
|
||||||
) {
|
const shouldHide =
|
||||||
|
shouldHideInstantSale ||
|
||||||
|
auctionView.vault.info.state === VaultState.Deactivated;
|
||||||
|
|
||||||
|
if (shouldHide) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,13 +525,16 @@ export const AuctionCard = ({
|
||||||
|
|
||||||
const instantSale = async () => {
|
const instantSale = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const instantSalePrice =
|
const instantSalePrice =
|
||||||
auctionView.auctionDataExtended?.info.instantSalePrice;
|
auctionView.auctionDataExtended?.info.instantSalePrice;
|
||||||
const winningConfigType =
|
const winningConfigType =
|
||||||
|
auctionView.participationItem?.winningConfigType ||
|
||||||
auctionView.items[0][0].winningConfigType;
|
auctionView.items[0][0].winningConfigType;
|
||||||
const isAuctionItemMaster =
|
const isAuctionItemMaster = [
|
||||||
winningConfigType === WinningConfigType.FullRightsTransfer ||
|
WinningConfigType.FullRightsTransfer,
|
||||||
winningConfigType === WinningConfigType.TokenOnlyTransfer;
|
WinningConfigType.TokenOnlyTransfer,
|
||||||
|
].includes(winningConfigType);
|
||||||
const allowBidToPublic =
|
const allowBidToPublic =
|
||||||
myPayingAccount &&
|
myPayingAccount &&
|
||||||
!auctionView.myBidderPot &&
|
!auctionView.myBidderPot &&
|
||||||
|
@ -532,7 +545,10 @@ export const AuctionCard = ({
|
||||||
isAuctionItemMaster;
|
isAuctionItemMaster;
|
||||||
|
|
||||||
// Placing a "bid" of the full amount results in a purchase to redeem.
|
// Placing a "bid" of the full amount results in a purchase to redeem.
|
||||||
if (instantSalePrice && (allowBidToPublic || allowBidToAuctionOwner)) {
|
if (
|
||||||
|
instantSalePrice &&
|
||||||
|
(allowBidToPublic || allowBidToAuctionOwner)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const bid = await sendPlaceBid(
|
const bid = await sendPlaceBid(
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Divider,
|
Divider,
|
||||||
Steps,
|
Steps,
|
||||||
|
@ -67,6 +67,12 @@ export enum AuctionCategory {
|
||||||
Tiered,
|
Tiered,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum InstantSaleType {
|
||||||
|
Limited,
|
||||||
|
Single,
|
||||||
|
Open,
|
||||||
|
}
|
||||||
|
|
||||||
interface TierDummyEntry {
|
interface TierDummyEntry {
|
||||||
safetyDepositBoxIndex: number;
|
safetyDepositBoxIndex: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
@ -123,6 +129,7 @@ export interface AuctionState {
|
||||||
winnersCount: number;
|
winnersCount: number;
|
||||||
|
|
||||||
instantSalePrice?: number;
|
instantSalePrice?: number;
|
||||||
|
instantSaleType?: InstantSaleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuctionCreateView = () => {
|
export const AuctionCreateView = () => {
|
||||||
|
@ -173,26 +180,47 @@ export const AuctionCreateView = () => {
|
||||||
|
|
||||||
const createAuction = async () => {
|
const createAuction = async () => {
|
||||||
let winnerLimit: WinnerLimit;
|
let winnerLimit: WinnerLimit;
|
||||||
if (attributes.category === AuctionCategory.InstantSale) {
|
if (
|
||||||
if (attributes.items.length > 0) {
|
attributes.category === AuctionCategory.InstantSale &&
|
||||||
const item = attributes.items[0];
|
attributes.instantSaleType === InstantSaleType.Open
|
||||||
if (!attributes.editions) {
|
) {
|
||||||
|
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.winningConfigType =
|
||||||
item.metadata.info.updateAuthority ===
|
item.metadata.info.updateAuthority ===
|
||||||
(wallet?.publicKey || SystemProgram.programId).toBase58()
|
(wallet?.publicKey || SystemProgram.programId).toBase58()
|
||||||
? WinningConfigType.FullRightsTransfer
|
? WinningConfigType.FullRightsTransfer
|
||||||
: WinningConfigType.TokenOnlyTransfer;
|
: WinningConfigType.TokenOnlyTransfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.amountRanges = [
|
item.amountRanges = [
|
||||||
new AmountRange({
|
new AmountRange({
|
||||||
amount: new BN(1),
|
amount: new BN(1),
|
||||||
length: new BN(attributes.editions || 1),
|
length: new BN(editions || 1),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
winnerLimit = new WinnerLimit({
|
winnerLimit = new WinnerLimit({
|
||||||
type: WinnerLimitType.Capped,
|
type: WinnerLimitType.Capped,
|
||||||
usize: new BN(attributes.editions || 1),
|
usize: new BN(editions || 1),
|
||||||
});
|
});
|
||||||
} else if (attributes.category === AuctionCategory.Open) {
|
} else if (attributes.category === AuctionCategory.Open) {
|
||||||
if (
|
if (
|
||||||
|
@ -447,19 +475,25 @@ export const AuctionCreateView = () => {
|
||||||
name: null,
|
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(
|
const _auctionObj = await createAuctionManager(
|
||||||
connection,
|
connection,
|
||||||
wallet,
|
wallet,
|
||||||
whitelistedCreatorsByCreator,
|
whitelistedCreatorsByCreator,
|
||||||
auctionSettings,
|
auctionSettings,
|
||||||
attributes.category === AuctionCategory.Open
|
safetyDepositDrafts,
|
||||||
? []
|
participationSafetyDepositDraft,
|
||||||
: attributes.category !== AuctionCategory.Tiered
|
|
||||||
? attributes.items
|
|
||||||
: tieredAttributes.items,
|
|
||||||
attributes.category === AuctionCategory.Open
|
|
||||||
? attributes.items[0]
|
|
||||||
: attributes.participationNFT,
|
|
||||||
QUOTE_MINT.toBase58(),
|
QUOTE_MINT.toBase58(),
|
||||||
);
|
);
|
||||||
setAuctionObj(_auctionObj);
|
setAuctionObj(_auctionObj);
|
||||||
|
@ -743,19 +777,28 @@ const CategoryStep = (props: {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const InstantSaleStep = (props: {
|
const InstantSaleStep = ({
|
||||||
|
attributes,
|
||||||
|
setAttributes,
|
||||||
|
confirm,
|
||||||
|
}: {
|
||||||
attributes: AuctionState;
|
attributes: AuctionState;
|
||||||
setAttributes: (attr: AuctionState) => void;
|
setAttributes: (attr: AuctionState) => void;
|
||||||
confirm: () => void;
|
confirm: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [copiesChecked, setCopiesChecked] = useState(false);
|
const copiesEnabled = useMemo(
|
||||||
const copiesEnabled = React.useMemo(
|
() => !!attributes?.items?.[0]?.masterEdition?.info?.maxSupply,
|
||||||
() => !!props.attributes?.items?.[0]?.masterEdition?.info?.maxSupply,
|
[attributes?.items?.[0]],
|
||||||
[props.attributes?.items?.[0]],
|
);
|
||||||
|
const artistFilter = useCallback(
|
||||||
|
(i: SafetyDepositDraft) =>
|
||||||
|
!(i.metadata.info.data.creators || []).some((c: Creator) => !c.verified),
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
let artistFilter = (i: SafetyDepositDraft) =>
|
const isLimitedEdition =
|
||||||
!(i.metadata.info.data.creators || []).find((c: Creator) => !c.verified);
|
attributes.instantSaleType === InstantSaleType.Limited;
|
||||||
|
const shouldRenderSelect = attributes.items.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -767,27 +810,43 @@ const InstantSaleStep = (props: {
|
||||||
<Col xl={24}>
|
<Col xl={24}>
|
||||||
<ArtSelector
|
<ArtSelector
|
||||||
filter={artistFilter}
|
filter={artistFilter}
|
||||||
selected={props.attributes.items}
|
selected={attributes.items}
|
||||||
setSelected={items => {
|
setSelected={items => {
|
||||||
props.setAttributes({ ...props.attributes, items });
|
setAttributes({ ...attributes, items });
|
||||||
}}
|
}}
|
||||||
allowMultiple={false}
|
allowMultiple={false}
|
||||||
>
|
>
|
||||||
Select NFT
|
Select NFT
|
||||||
</ArtSelector>
|
</ArtSelector>
|
||||||
|
|
||||||
|
{shouldRenderSelect && (
|
||||||
<label className="action-field">
|
<label className="action-field">
|
||||||
<Checkbox
|
<Select
|
||||||
defaultChecked={false}
|
defaultValue={
|
||||||
checked={copiesChecked}
|
attributes.instantSaleType || InstantSaleType.Single
|
||||||
disabled={!copiesEnabled}
|
}
|
||||||
onChange={e => setCopiesChecked(e.target.checked)}
|
onChange={value =>
|
||||||
|
setAttributes({
|
||||||
|
...attributes,
|
||||||
|
instantSaleType: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className="field-title">
|
<Option value={InstantSaleType.Single}>
|
||||||
Create copies of a Master Edition NFT?
|
Sell unique token
|
||||||
</span>
|
</Option>
|
||||||
</Checkbox>
|
{copiesEnabled && (
|
||||||
{copiesChecked && 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">
|
<span className="field-info">
|
||||||
Each copy will be given unique edition number e.g. 1 of 30
|
Each copy will be given unique edition number e.g. 1 of 30
|
||||||
|
@ -798,8 +857,8 @@ const InstantSaleStep = (props: {
|
||||||
placeholder="Enter number of copies sold"
|
placeholder="Enter number of copies sold"
|
||||||
allowClear
|
allowClear
|
||||||
onChange={info =>
|
onChange={info =>
|
||||||
props.setAttributes({
|
setAttributes({
|
||||||
...props.attributes,
|
...attributes,
|
||||||
editions: parseInt(info.target.value),
|
editions: parseInt(info.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -807,6 +866,7 @@ const InstantSaleStep = (props: {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
<label className="action-field">
|
<label className="action-field">
|
||||||
<span className="field-title">Price</span>
|
<span className="field-title">Price</span>
|
||||||
|
@ -822,8 +882,8 @@ const InstantSaleStep = (props: {
|
||||||
prefix="◎"
|
prefix="◎"
|
||||||
suffix="SOL"
|
suffix="SOL"
|
||||||
onChange={info =>
|
onChange={info =>
|
||||||
props.setAttributes({
|
setAttributes({
|
||||||
...props.attributes,
|
...attributes,
|
||||||
priceFloor: parseFloat(info.target.value),
|
priceFloor: parseFloat(info.target.value),
|
||||||
instantSalePrice: parseFloat(info.target.value),
|
instantSalePrice: parseFloat(info.target.value),
|
||||||
})
|
})
|
||||||
|
@ -837,7 +897,7 @@ const InstantSaleStep = (props: {
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.confirm();
|
confirm();
|
||||||
}}
|
}}
|
||||||
className="action-btn"
|
className="action-btn"
|
||||||
>
|
>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const AuctionListView = () => {
|
||||||
const auctions = useAuctions(AuctionViewState.Live);
|
const auctions = useAuctions(AuctionViewState.Live);
|
||||||
const auctionsEnded = [
|
const auctionsEnded = [
|
||||||
...useAuctions(AuctionViewState.Ended),
|
...useAuctions(AuctionViewState.Ended),
|
||||||
...useAuctions(AuctionViewState.BuyNow)
|
...useAuctions(AuctionViewState.BuyNow),
|
||||||
];
|
];
|
||||||
const [activeKey, setActiveKey] = useState(LiveAuctionViewState.All);
|
const [activeKey, setActiveKey] = useState(LiveAuctionViewState.All);
|
||||||
const { isLoading } = useMeta();
|
const { isLoading } = useMeta();
|
||||||
|
|
Loading…
Reference in New Issue