mirror of https://github.com/certusone/oyster.git
Merge remote-tracking branch 'origin/feature/m-jordan' into feature/m
This commit is contained in:
commit
878e471c05
|
@ -45,6 +45,10 @@ export class BidState {
|
|||
}
|
||||
}
|
||||
|
||||
export const decodeAuction = (buffer: Buffer) => {
|
||||
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
|
||||
};
|
||||
|
||||
export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9;
|
||||
|
||||
export class AuctionData {
|
||||
|
@ -67,6 +71,9 @@ export class AuctionData {
|
|||
/// Gap time is the amount of time in slots after the previous bid at which the auction ends.
|
||||
endAuctionGap?: BN;
|
||||
|
||||
/// Used for precalculation on the front end, not a backend key
|
||||
auctionManagerKey?: PublicKey;
|
||||
|
||||
constructor(args: {
|
||||
authority: PublicKey;
|
||||
resource: PublicKey;
|
||||
|
@ -247,7 +254,7 @@ export const AUCTION_SCHEMA = new Map<any, any>([
|
|||
['resource', 'pubkey'],
|
||||
['tokenMint', 'pubkey'],
|
||||
['state', 'u8'],
|
||||
['bidState', 'BidState'],
|
||||
['bidState', BidState],
|
||||
['lastBid', { kind: 'option', type: 'u64' }],
|
||||
['endedAt', { kind: 'option', type: 'u64' }],
|
||||
['endAuctionAt', { kind: 'option', type: 'u64' }],
|
||||
|
|
|
@ -258,6 +258,14 @@ export const decodeVault = (buffer: Buffer) => {
|
|||
return deserializeBorsh(VAULT_SCHEMA, Vault, buffer) as Vault;
|
||||
};
|
||||
|
||||
export const decodeSafetyDeposit = (buffer: Buffer) => {
|
||||
return deserializeBorsh(
|
||||
VAULT_SCHEMA,
|
||||
SafetyDepositBox,
|
||||
buffer,
|
||||
) as SafetyDepositBox;
|
||||
};
|
||||
|
||||
export async function initVault(
|
||||
allowFurtherShareCreation: boolean,
|
||||
fractionalMint: PublicKey,
|
||||
|
|
|
@ -1,39 +1,46 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Divider,
|
||||
Button,
|
||||
InputNumber
|
||||
} from 'antd'
|
||||
import { Row, Col, Divider, Button, InputNumber } from 'antd';
|
||||
|
||||
import { Auction, Presale } from '../../types'
|
||||
import { Auction, Presale } from '../../types';
|
||||
|
||||
import './index.less'
|
||||
import { getCountdown } from '../../utils/utils'
|
||||
import { shortenAddress } from '@oyster/common';
|
||||
import './index.less';
|
||||
import { getCountdown } from '../../utils/utils';
|
||||
import { shortenAddress, useConnection } from '@oyster/common';
|
||||
import { AuctionView } from '../../hooks';
|
||||
|
||||
export const AuctionCard = ({ auction }: { auction: Auction }) => {
|
||||
const [hours, setHours] = useState<number>(23)
|
||||
const [minutes, setMinutes] = useState<number>(59)
|
||||
const [seconds, setSeconds] = useState<number>(59)
|
||||
export const AuctionCard = ({ auctionView }: { auctionView: AuctionView }) => {
|
||||
const [hours, setHours] = useState<number>(23);
|
||||
const [minutes, setMinutes] = useState<number>(59);
|
||||
const [seconds, setSeconds] = useState<number>(59);
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const connection = useConnection();
|
||||
|
||||
useEffect(() => {
|
||||
connection.getSlot().then(setClock);
|
||||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const { hours, minutes, seconds } = getCountdown(auction.endingTS)
|
||||
const slotDiff =
|
||||
(auctionView.auction.info.endedAt?.toNumber() || 0) - clock;
|
||||
|
||||
setHours(hours)
|
||||
setMinutes(minutes)
|
||||
setSeconds(seconds)
|
||||
}, 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
/* const { hours, minutes, seconds } = getCountdown(
|
||||
auctionView.auction.info.endedAt?.toNumber(),
|
||||
);
|
||||
|
||||
setHours(hours);
|
||||
setMinutes(minutes);
|
||||
setSeconds(seconds);*/
|
||||
setHours(1);
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [clock]);
|
||||
|
||||
return (
|
||||
<div className="presale-card-container">
|
||||
<div className="info-header">STARTING BID</div>
|
||||
<div style={{ fontWeight: 700, fontSize: '1.6rem' }}>◎40.00</div>
|
||||
<br/>
|
||||
<br />
|
||||
<div className="info-header">AUCTION ENDS IN</div>
|
||||
<Row style={{ width: 300 }}>
|
||||
<Col span={8}>
|
||||
|
@ -49,13 +56,17 @@ export const AuctionCard = ({ auction }: { auction: Auction }) => {
|
|||
<div className="cd-label">seconds</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<br/>
|
||||
<div className="info-content" style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}>
|
||||
Any bids placed in the last 15 minutes will extend the auction for another 15 minutes.
|
||||
<br />
|
||||
<div
|
||||
className="info-content"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}
|
||||
>
|
||||
Any bids placed in the last 15 minutes will extend the auction for
|
||||
another 15 minutes.
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div className="info-line"/>
|
||||
<div className="info-line" />
|
||||
|
||||
<InputNumber
|
||||
autoFocus
|
||||
|
@ -70,29 +81,36 @@ export const AuctionCard = ({ auction }: { auction: Auction }) => {
|
|||
// }
|
||||
/>
|
||||
|
||||
<div className="info-content" style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}>
|
||||
<div
|
||||
className="info-content"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}
|
||||
>
|
||||
Your Balance: ◎ {0.0} (${0.0})
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
className="action-btn"
|
||||
style={{ marginTop: 20 }}
|
||||
>
|
||||
PLACE BID
|
||||
type="primary"
|
||||
size="large"
|
||||
className="action-btn"
|
||||
style={{ marginTop: 20 }}
|
||||
>
|
||||
PLACE BID
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const AuctionBidders = (auctionID: string) => {
|
||||
const bids: any[] = [];
|
||||
return <Col>
|
||||
{bids.map((bid, index) => {
|
||||
return <Row>{index+1}. {shortenAddress(bid.address)} {bid.amount}</Row>
|
||||
})}
|
||||
</Col>
|
||||
return (
|
||||
<Col>
|
||||
{bids.map((bid, index) => {
|
||||
return (
|
||||
<Row>
|
||||
{index + 1}. {shortenAddress(bid.address)} {bid.amount}
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
useConnection,
|
||||
decodeMetadata,
|
||||
decodeNameSymbolTuple,
|
||||
decodeAuction,
|
||||
decodeEdition,
|
||||
decodeMasterEdition,
|
||||
Metadata,
|
||||
|
@ -15,30 +16,55 @@ import {
|
|||
Edition,
|
||||
MasterEdition,
|
||||
NameSymbolTuple,
|
||||
AuctionData,
|
||||
SafetyDepositBox,
|
||||
VaultKey,
|
||||
decodeSafetyDeposit,
|
||||
} from '@oyster/common';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { Connection, PublicKey, PublicKeyAndAccount } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
AuctionManager,
|
||||
AuctionManagerStatus,
|
||||
decodeAuctionManager,
|
||||
getAuctionManagerKey,
|
||||
MetaplexKey,
|
||||
} from '../models/metaplex';
|
||||
|
||||
const { MetadataKey } = actions;
|
||||
export interface MetaContextState {
|
||||
metadata: ParsedAccount<Metadata>[];
|
||||
metadataByMint: Record<string, ParsedAccount<Metadata>>;
|
||||
nameSymbolTuples: Record<string, ParsedAccount<NameSymbolTuple>>;
|
||||
editions: Record<string, ParsedAccount<Edition>>;
|
||||
masterEditions: Record<string, ParsedAccount<MasterEdition>>;
|
||||
auctionManagers: Record<string, ParsedAccount<AuctionManager>>;
|
||||
auctions: Record<string, ParsedAccount<AuctionData>>;
|
||||
safetyDepositBoxesByVaultAndIndex: Record<
|
||||
string,
|
||||
ParsedAccount<SafetyDepositBox>
|
||||
>;
|
||||
}
|
||||
|
||||
const MetaContext = React.createContext<MetaContextState>({
|
||||
metadata: [],
|
||||
metadataByMint: {},
|
||||
nameSymbolTuples: {},
|
||||
masterEditions: {},
|
||||
editions: {},
|
||||
auctionManagers: {},
|
||||
auctions: {},
|
||||
safetyDepositBoxesByVaultAndIndex: {},
|
||||
});
|
||||
|
||||
export function MetaProvider({ children = null as any }) {
|
||||
const connection = useConnection();
|
||||
const [metadata, setMetadata] = useState<ParsedAccount<Metadata>[]>([]);
|
||||
const [metadataByMint, setMetadataByMint] = useState<
|
||||
Record<string, ParsedAccount<Metadata>>
|
||||
>({});
|
||||
const [nameSymbolTuples, setNameSymbolTuples] = useState<
|
||||
Record<string, ParsedAccount<NameSymbolTuple>>
|
||||
>({});
|
||||
|
@ -48,12 +74,181 @@ export function MetaProvider({ children = null as any }) {
|
|||
const [editions, setEditions] = useState<
|
||||
Record<string, ParsedAccount<Edition>>
|
||||
>({});
|
||||
const [auctionManagers, setAuctionManagers] = useState<
|
||||
Record<string, ParsedAccount<AuctionManager>>
|
||||
>({});
|
||||
const [auctions, setAuctions] = useState<
|
||||
Record<string, ParsedAccount<AuctionData>>
|
||||
>({});
|
||||
const [
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
setSafetyDepositBoxesByVaultAndIndex,
|
||||
] = useState<Record<string, ParsedAccount<SafetyDepositBox>>>({});
|
||||
|
||||
useEffect(() => {
|
||||
let dispose = () => {};
|
||||
(async () => {
|
||||
const mintToMetadata = new Map<string, ParsedAccount<Metadata>>();
|
||||
const processAuctions = async (a: PublicKeyAndAccount<Buffer>) => {
|
||||
try {
|
||||
const auction = await decodeAuction(a.account.data);
|
||||
auction.auctionManagerKey = await getAuctionManagerKey(
|
||||
auction.resource,
|
||||
a.pubkey,
|
||||
);
|
||||
const account: ParsedAccount<AuctionData> = {
|
||||
pubkey: a.pubkey,
|
||||
account: a.account,
|
||||
info: auction,
|
||||
};
|
||||
setAuctions(e => ({
|
||||
...e,
|
||||
[a.pubkey.toBase58()]: account,
|
||||
}));
|
||||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
}
|
||||
};
|
||||
|
||||
const accounts = await connection.getProgramAccounts(
|
||||
programIds().auction,
|
||||
);
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
await processAuctions(accounts[i]);
|
||||
}
|
||||
|
||||
let subId = connection.onProgramAccountChange(
|
||||
programIds().auction,
|
||||
async info => {
|
||||
const pubkey =
|
||||
typeof info.accountId === 'string'
|
||||
? new PublicKey((info.accountId as unknown) as string)
|
||||
: info.accountId;
|
||||
await processAuctions({
|
||||
pubkey,
|
||||
account: info.accountInfo,
|
||||
});
|
||||
},
|
||||
);
|
||||
dispose = () => {
|
||||
connection.removeProgramAccountChangeListener(subId);
|
||||
};
|
||||
})();
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, setAuctions]);
|
||||
|
||||
useEffect(() => {
|
||||
let dispose = () => {};
|
||||
(async () => {
|
||||
const processSafetyDeposits = async (a: PublicKeyAndAccount<Buffer>) => {
|
||||
try {
|
||||
if (a.account.data[0] == VaultKey.SafetyDepositBoxV1) {
|
||||
const safetyDeposit = await decodeSafetyDeposit(a.account.data);
|
||||
const account: ParsedAccount<SafetyDepositBox> = {
|
||||
pubkey: a.pubkey,
|
||||
account: a.account,
|
||||
info: safetyDeposit,
|
||||
};
|
||||
setSafetyDepositBoxesByVaultAndIndex(e => ({
|
||||
...e,
|
||||
[safetyDeposit.vault.toBase58() +
|
||||
'-' +
|
||||
safetyDeposit.order]: account,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
}
|
||||
};
|
||||
|
||||
const accounts = await connection.getProgramAccounts(programIds().vault);
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
await processSafetyDeposits(accounts[i]);
|
||||
}
|
||||
|
||||
let subId = connection.onProgramAccountChange(
|
||||
programIds().vault,
|
||||
async info => {
|
||||
const pubkey =
|
||||
typeof info.accountId === 'string'
|
||||
? new PublicKey((info.accountId as unknown) as string)
|
||||
: info.accountId;
|
||||
await processSafetyDeposits({
|
||||
pubkey,
|
||||
account: info.accountInfo,
|
||||
});
|
||||
},
|
||||
);
|
||||
dispose = () => {
|
||||
connection.removeProgramAccountChangeListener(subId);
|
||||
};
|
||||
})();
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, setSafetyDepositBoxesByVaultAndIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
let dispose = () => {};
|
||||
(async () => {
|
||||
const processAuctionManagers = async (a: PublicKeyAndAccount<Buffer>) => {
|
||||
try {
|
||||
if (a.account.data[0] == MetaplexKey.AuctionManagerV1) {
|
||||
const auctionManager = await decodeAuctionManager(a.account.data);
|
||||
const account: ParsedAccount<AuctionManager> = {
|
||||
pubkey: a.pubkey,
|
||||
account: a.account,
|
||||
info: auctionManager,
|
||||
};
|
||||
setAuctionManagers(e => ({
|
||||
...e,
|
||||
[a.pubkey.toBase58()]: account,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
}
|
||||
};
|
||||
|
||||
const accounts = await connection.getProgramAccounts(
|
||||
programIds().metaplex,
|
||||
);
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
await processAuctionManagers(accounts[i]);
|
||||
}
|
||||
|
||||
let subId = connection.onProgramAccountChange(
|
||||
programIds().metaplex,
|
||||
async info => {
|
||||
const pubkey =
|
||||
typeof info.accountId === 'string'
|
||||
? new PublicKey((info.accountId as unknown) as string)
|
||||
: info.accountId;
|
||||
await processAuctionManagers({
|
||||
pubkey,
|
||||
account: info.accountInfo,
|
||||
});
|
||||
},
|
||||
);
|
||||
dispose = () => {
|
||||
connection.removeProgramAccountChangeListener(subId);
|
||||
};
|
||||
})();
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, setAuctionManagers]);
|
||||
|
||||
useEffect(() => {
|
||||
let dispose = () => {};
|
||||
(async () => {
|
||||
const processMetaData = async (meta: PublicKeyAndAccount<Buffer>) => {
|
||||
try {
|
||||
if (meta.account.data[0] == MetadataKey.MetadataV1) {
|
||||
|
@ -67,7 +262,10 @@ export function MetaProvider({ children = null as any }) {
|
|||
account: meta.account,
|
||||
info: metadata,
|
||||
};
|
||||
mintToMetadata.set(metadata.mint.toBase58(), account);
|
||||
setMetadataByMint(e => ({
|
||||
...e,
|
||||
[metadata.mint.toBase58()]: account,
|
||||
}));
|
||||
}
|
||||
} else if (meta.account.data[0] == MetadataKey.EditionV1) {
|
||||
const edition = decodeEdition(meta.account.data);
|
||||
|
@ -113,20 +311,21 @@ export function MetaProvider({ children = null as any }) {
|
|||
await processMetaData(accounts[i]);
|
||||
}
|
||||
|
||||
await queryExtendedMetadata(connection, setMetadata, mintToMetadata);
|
||||
await queryExtendedMetadata(connection, setMetadata, metadataByMint);
|
||||
|
||||
let subId = connection.onProgramAccountChange(
|
||||
programIds().metadata,
|
||||
async info => {
|
||||
const pubkey = typeof info.accountId === 'string' ?
|
||||
new PublicKey((info.accountId as unknown) as string) :
|
||||
info.accountId;
|
||||
const pubkey =
|
||||
typeof info.accountId === 'string'
|
||||
? new PublicKey((info.accountId as unknown) as string)
|
||||
: info.accountId;
|
||||
await processMetaData({
|
||||
pubkey,
|
||||
account: info.accountInfo,
|
||||
});
|
||||
|
||||
queryExtendedMetadata(connection, setMetadata, mintToMetadata);
|
||||
queryExtendedMetadata(connection, setMetadata, metadataByMint);
|
||||
},
|
||||
);
|
||||
dispose = () => {
|
||||
|
@ -147,7 +346,16 @@ export function MetaProvider({ children = null as any }) {
|
|||
|
||||
return (
|
||||
<MetaContext.Provider
|
||||
value={{ metadata, editions, masterEditions, nameSymbolTuples }}
|
||||
value={{
|
||||
metadata,
|
||||
editions,
|
||||
masterEditions,
|
||||
nameSymbolTuples,
|
||||
auctionManagers,
|
||||
auctions,
|
||||
metadataByMint,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MetaContext.Provider>
|
||||
|
@ -157,14 +365,14 @@ export function MetaProvider({ children = null as any }) {
|
|||
const queryExtendedMetadata = async (
|
||||
connection: Connection,
|
||||
setMetadata: (metadata: ParsedAccount<Metadata>[]) => void,
|
||||
mintToMeta: Map<string, ParsedAccount<Metadata>>,
|
||||
mintToMeta: Record<string, ParsedAccount<Metadata>>,
|
||||
) => {
|
||||
const mintToMetadata = new Map<string, ParsedAccount<Metadata>>(mintToMeta);
|
||||
const mintToMetadata = { ...mintToMeta };
|
||||
const extendedMetadataFetch = new Map<string, Promise<any>>();
|
||||
|
||||
const mints = await getMultipleAccounts(
|
||||
connection,
|
||||
[...mintToMetadata.keys()].filter(k => !cache.get(k)),
|
||||
[...Object.keys(mintToMetadata)].filter(k => !cache.get(k)),
|
||||
'single',
|
||||
);
|
||||
mints.keys.forEach((key, index) => {
|
||||
|
@ -176,9 +384,9 @@ const queryExtendedMetadata = async (
|
|||
) as ParsedAccount<MintInfo>;
|
||||
if (mint.info.supply.gt(new BN(1)) || mint.info.decimals !== 0) {
|
||||
// naive not NFT check
|
||||
mintToMetadata.delete(key);
|
||||
delete mintToMetadata[key];
|
||||
} else {
|
||||
const metadata = mintToMetadata.get(key);
|
||||
const metadata = mintToMetadata[key];
|
||||
if (metadata && metadata.info.uri) {
|
||||
extendedMetadataFetch.set(
|
||||
key,
|
||||
|
@ -190,19 +398,19 @@ const queryExtendedMetadata = async (
|
|||
!metadata.info.extended ||
|
||||
metadata.info.extended?.files?.length === 0
|
||||
) {
|
||||
mintToMetadata.delete(key);
|
||||
delete mintToMetadata[key];
|
||||
} else {
|
||||
if (metadata.info.extended?.image) {
|
||||
metadata.info.extended.image = `${metadata.info.uri}/${metadata.info.extended.image}`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
mintToMetadata.delete(key);
|
||||
delete mintToMetadata[key];
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
mintToMetadata.delete(key);
|
||||
delete mintToMetadata[key];
|
||||
return undefined;
|
||||
}),
|
||||
);
|
||||
|
@ -212,7 +420,7 @@ const queryExtendedMetadata = async (
|
|||
|
||||
await Promise.all([...extendedMetadataFetch.values()]);
|
||||
|
||||
setMetadata([...mintToMetadata.values()]);
|
||||
setMetadata([...Object.values(mintToMetadata)]);
|
||||
};
|
||||
|
||||
export const useMeta = () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useMeta } from './../contexts';
|
||||
import { useMeta } from '../contexts';
|
||||
import { Art } from '../types';
|
||||
|
||||
export const useArt = (id: PublicKey | string) => {
|
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
ParsedAccount,
|
||||
Metadata,
|
||||
SafetyDepositBox,
|
||||
AuctionData,
|
||||
useConnection,
|
||||
AuctionState,
|
||||
} from '@oyster/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AuctionView, processAccountsIntoAuctionView } from '.';
|
||||
import { useMeta } from '../contexts';
|
||||
import { AuctionManager } from '../models/metaplex';
|
||||
import { sampleAuction } from '../views/home/sampleData';
|
||||
|
||||
export const useAuction = (id: string) => {
|
||||
const connection = useConnection();
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const [auctionView, setAuctionView] = useState<AuctionView | null>(null);
|
||||
useEffect(() => {
|
||||
connection.getSlot().then(setClock);
|
||||
}, [connection]);
|
||||
|
||||
const {
|
||||
auctions,
|
||||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
} = useMeta();
|
||||
|
||||
useEffect(() => {
|
||||
const auction = auctions[id];
|
||||
if (auction) {
|
||||
const auctionView = processAccountsIntoAuctionView(
|
||||
auction,
|
||||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
clock,
|
||||
undefined,
|
||||
);
|
||||
if (auctionView) setAuctionView(auctionView);
|
||||
}
|
||||
}, [clock]);
|
||||
return auctionView;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { sampleAuction } from '../views/home/sampleData';
|
||||
import { useMeta } from './../contexts';
|
||||
|
||||
export const useAuction = (id: string) => {
|
||||
const { metadata } = useMeta();
|
||||
|
||||
return sampleAuction;
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
import {
|
||||
ParsedAccount,
|
||||
Metadata,
|
||||
SafetyDepositBox,
|
||||
AuctionData,
|
||||
useConnection,
|
||||
AuctionState,
|
||||
} from '@oyster/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMeta } from '../contexts';
|
||||
import { AuctionManager } from '../models/metaplex';
|
||||
|
||||
export enum AuctionViewState {
|
||||
Live = '0',
|
||||
Upcoming = '1',
|
||||
Ended = '2',
|
||||
BuyNow = '3',
|
||||
}
|
||||
|
||||
export interface AuctionViewItem {
|
||||
metadata: ParsedAccount<Metadata>;
|
||||
safetyDeposit: ParsedAccount<SafetyDepositBox>;
|
||||
}
|
||||
|
||||
// Flattened surface item for easy display
|
||||
export interface AuctionView {
|
||||
items: AuctionViewItem[];
|
||||
auction: ParsedAccount<AuctionData>;
|
||||
auctionManager: ParsedAccount<AuctionManager>;
|
||||
openEditionItem?: AuctionViewItem;
|
||||
state: AuctionViewState;
|
||||
thumbnail: AuctionViewItem;
|
||||
}
|
||||
|
||||
export const useAuctions = (state: AuctionViewState) => {
|
||||
const connection = useConnection();
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const [auctionViews, setAuctionViews] = useState<AuctionView[]>([]);
|
||||
useEffect(() => {
|
||||
connection.getSlot().then(setClock);
|
||||
}, [connection]);
|
||||
|
||||
const {
|
||||
auctions,
|
||||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
} = useMeta();
|
||||
|
||||
useEffect(() => {
|
||||
const newAuctionViews: AuctionView[] = [];
|
||||
Object.keys(auctions).forEach(a => {
|
||||
const auction = auctions[a];
|
||||
const auctionView = processAccountsIntoAuctionView(
|
||||
auction,
|
||||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
clock,
|
||||
state,
|
||||
);
|
||||
if (auctionView) newAuctionViews.push(auctionView);
|
||||
});
|
||||
setAuctionViews(newAuctionViews);
|
||||
}, [clock, state, auctions]);
|
||||
|
||||
return auctionViews;
|
||||
};
|
||||
|
||||
export function processAccountsIntoAuctionView(
|
||||
auction: ParsedAccount<AuctionData>,
|
||||
auctionManagers: Record<string, ParsedAccount<AuctionManager>>,
|
||||
safetyDepositBoxesByVaultAndIndex: Record<
|
||||
string,
|
||||
ParsedAccount<SafetyDepositBox>
|
||||
>,
|
||||
metadataByMint: Record<string, ParsedAccount<Metadata>>,
|
||||
clock: number,
|
||||
desiredState: AuctionViewState | undefined,
|
||||
) {
|
||||
let state: AuctionViewState;
|
||||
if (
|
||||
auction.info.state == AuctionState.Ended ||
|
||||
(auction.info.endedAt && auction.info.endedAt.toNumber() <= clock)
|
||||
) {
|
||||
state = AuctionViewState.Ended;
|
||||
} else if (
|
||||
auction.info.state == AuctionState.Started ||
|
||||
(auction.info.endedAt && auction.info.endedAt.toNumber() > clock)
|
||||
) {
|
||||
state = AuctionViewState.Live;
|
||||
} else if (auction.info.state == AuctionState.Created) {
|
||||
state = AuctionViewState.Upcoming;
|
||||
} else {
|
||||
state = AuctionViewState.BuyNow;
|
||||
}
|
||||
|
||||
if (desiredState && desiredState != state) return null;
|
||||
|
||||
const auctionManager =
|
||||
auctionManagers[auction.info.auctionManagerKey?.toBase58() || ''];
|
||||
if (auctionManager) {
|
||||
let boxes: ParsedAccount<SafetyDepositBox>[] = [];
|
||||
let box =
|
||||
safetyDepositBoxesByVaultAndIndex[
|
||||
auctionManager.info.vault.toBase58() + '-0'
|
||||
];
|
||||
if (box) {
|
||||
boxes.push(box);
|
||||
let i = 1;
|
||||
while (box) {
|
||||
box =
|
||||
safetyDepositBoxesByVaultAndIndex[
|
||||
auctionManager.info.vault.toBase58() + '-' + i.toString()
|
||||
];
|
||||
if (box) boxes.push(box);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (boxes.length > 0) {
|
||||
let view: any = {
|
||||
auction,
|
||||
auctionManager,
|
||||
state,
|
||||
items: auctionManager.info.settings.winningConfigs.map(w => ({
|
||||
metadata:
|
||||
metadataByMint[
|
||||
boxes[w.safetyDepositBoxIndex].info.tokenMint.toBase58()
|
||||
],
|
||||
safetyDeposit: boxes[w.safetyDepositBoxIndex],
|
||||
})),
|
||||
openEditionItem:
|
||||
auctionManager.info.settings.openEditionConfig != null
|
||||
? {
|
||||
metadata:
|
||||
metadataByMint[
|
||||
boxes[
|
||||
auctionManager.info.settings.openEditionConfig
|
||||
].info.tokenMint.toBase58()
|
||||
],
|
||||
safetyDeposit:
|
||||
boxes[auctionManager.info.settings.openEditionConfig],
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
view.thumbnail = view.items[0] || view.openEditionItem;
|
||||
if (!view.thumbnail || !view.thumbnail.metadata) return null;
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { useMeta } from './../contexts';
|
||||
|
||||
export enum AuctionState {
|
||||
Live = '0',
|
||||
Upcoming = '1',
|
||||
Ended = '2',
|
||||
BuyNow = '3',
|
||||
}
|
||||
|
||||
export const useAuctions = (state: AuctionState) => {
|
||||
const { metadata } = useMeta();
|
||||
|
||||
return metadata;
|
||||
}
|
|
@ -140,6 +140,9 @@ export class WinningConfig {
|
|||
Object.assign(this, args);
|
||||
}
|
||||
}
|
||||
export const decodeAuctionManager = (buffer: Buffer) => {
|
||||
return deserializeBorsh(SCHEMA, AuctionManager, buffer) as AuctionManager;
|
||||
};
|
||||
|
||||
export class WinningConfigState {
|
||||
amountMinted: number = 0;
|
||||
|
@ -313,6 +316,20 @@ export const SCHEMA = new Map<any, any>([
|
|||
],
|
||||
]);
|
||||
|
||||
export async function getAuctionManagerKey(
|
||||
vault: PublicKey,
|
||||
auctionKey: PublicKey,
|
||||
): Promise<PublicKey> {
|
||||
const PROGRAM_IDS = programIds();
|
||||
|
||||
return (
|
||||
await PublicKey.findProgramAddress(
|
||||
[Buffer.from(METAPLEX_PREFIX), auctionKey.toBuffer()],
|
||||
PROGRAM_IDS.metaplex,
|
||||
)
|
||||
)[0];
|
||||
}
|
||||
|
||||
export async function getAuctionKeys(
|
||||
vault: PublicKey,
|
||||
): Promise<{ auctionKey: PublicKey; auctionManagerKey: PublicKey }> {
|
||||
|
@ -329,12 +346,7 @@ export async function getAuctionKeys(
|
|||
)
|
||||
)[0];
|
||||
|
||||
const auctionManagerKey: PublicKey = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[Buffer.from(METAPLEX_PREFIX), auctionKey.toBuffer()],
|
||||
PROGRAM_IDS.metaplex,
|
||||
)
|
||||
)[0];
|
||||
const auctionManagerKey = await getAuctionManagerKey(vault, auctionKey);
|
||||
|
||||
return { auctionKey, auctionManagerKey };
|
||||
}
|
||||
|
|
|
@ -1,32 +1,40 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Row, Col, Divider, Layout, Image } from 'antd';
|
||||
import { Row, Col, Divider, Layout, Image, Spin } from 'antd';
|
||||
import { AuctionCard } from '../../components/AuctionCard';
|
||||
import { useArt, useAuction } from '../../hooks';
|
||||
import { ArtContent } from '../../components/ArtContent';
|
||||
import { sampleArtist } from '../home/sampleData';
|
||||
|
||||
const { Content } = Layout
|
||||
const { Content } = Layout;
|
||||
|
||||
export const AuctionView = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const auction = useAuction(id);
|
||||
const art = useArt(id);
|
||||
const artist = sampleArtist
|
||||
const artist = sampleArtist;
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Col>
|
||||
<Row>
|
||||
<ArtContent category={art.category} content={art.image} className="artwork-image" />
|
||||
<ArtContent
|
||||
category={art.category}
|
||||
content={art.image}
|
||||
className="artwork-image"
|
||||
/>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Row style={{ margin: '0 30px', textAlign: 'left', fontSize: '1.4rem' }}>
|
||||
<Col span={12} >
|
||||
<Row
|
||||
style={{ margin: '0 30px', textAlign: 'left', fontSize: '1.4rem' }}
|
||||
>
|
||||
<Col span={12}>
|
||||
<div style={{ fontWeight: 700 }}>{art.title}</div>
|
||||
<br />
|
||||
<div className="info-header">CREATED BY</div>
|
||||
<div className="info-content"><img src={artist.image} className="artist-image" /> @{art.artist}</div>
|
||||
<div className="info-content">
|
||||
<img src={artist.image} className="artist-image" /> @{art.artist}
|
||||
</div>
|
||||
<br />
|
||||
<div className="info-header">CREATOR ROYALTIES</div>
|
||||
<div className="royalties">{art.royalties}%</div>
|
||||
|
@ -38,7 +46,7 @@ export const AuctionView = () => {
|
|||
<div className="info-content">{artist.about}</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<AuctionCard auction={auction} />
|
||||
{auction ? <AuctionCard auctionView={auction} /> : <Spin />}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
|
|
@ -129,7 +129,7 @@ export const AuctionCreateView = () => {
|
|||
const [attributes, setAttributes] = useState<AuctionState>({
|
||||
reservationPrice: 0,
|
||||
items: [],
|
||||
category: AuctionCategory.Open,
|
||||
category: AuctionCategory.Single,
|
||||
saleType: 'auction',
|
||||
winnersCount: 1,
|
||||
});
|
||||
|
@ -161,6 +161,7 @@ export const AuctionCreateView = () => {
|
|||
usize: ZERO,
|
||||
});
|
||||
} else if (attributes.category == AuctionCategory.Single) {
|
||||
console.log('Using single');
|
||||
settings = new AuctionManagerSettings({
|
||||
openEditionWinnerConstraint: WinningConstraint.NoOpenEdition,
|
||||
openEditionNonWinningConstraint: NonWinningConstraint.NoOpenEdition,
|
||||
|
|
|
@ -1,52 +1,58 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Layout, Row, Col, Tabs } from 'antd'
|
||||
import Masonry from 'react-masonry-css'
|
||||
import React, { useState } from 'react';
|
||||
import { Layout, Row, Col, Tabs } from 'antd';
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
import { PreSaleBanner } from '../../components/PreSaleBanner'
|
||||
import { AuctionState, useAuctions } from '../../hooks'
|
||||
import { PreSaleBanner } from '../../components/PreSaleBanner';
|
||||
import { AuctionViewState, useAuctions } from '../../hooks';
|
||||
|
||||
import './index.less'
|
||||
import { ArtCard } from '../../components/ArtCard'
|
||||
import { Link } from 'react-router-dom'
|
||||
import './index.less';
|
||||
import { ArtCard } from '../../components/ArtCard';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const { Content } = Layout
|
||||
const { Content } = Layout;
|
||||
export const HomeView = () => {
|
||||
const [activeKey, setActiveKey] = useState(AuctionState.Live);
|
||||
const auctions = useAuctions(AuctionState.Live)
|
||||
|
||||
const [activeKey, setActiveKey] = useState(AuctionViewState.Live);
|
||||
const auctions = useAuctions(activeKey);
|
||||
console.log('Auctions', auctions);
|
||||
const breakpointColumnsObj = {
|
||||
default: 4,
|
||||
1100: 3,
|
||||
700: 2,
|
||||
500: 1
|
||||
}
|
||||
500: 1,
|
||||
};
|
||||
|
||||
const auctionGrid = <Masonry
|
||||
const auctionGrid = (
|
||||
<Masonry
|
||||
breakpointCols={breakpointColumnsObj}
|
||||
className="my-masonry-grid"
|
||||
columnClassName="my-masonry-grid_column"
|
||||
>
|
||||
{auctions.map(m => {
|
||||
const id = m.pubkey.toBase58();
|
||||
return <Link to={`/art/${id}`}>
|
||||
<ArtCard key={id}
|
||||
image={m.info.extended?.image}
|
||||
category={m.info.extended?.category}
|
||||
name={m.info?.name}
|
||||
symbol={m.info.symbol}
|
||||
description={m.info.extended?.description}
|
||||
preview={false} />
|
||||
</Link>
|
||||
})}
|
||||
</Masonry>;
|
||||
{auctions.map(m => {
|
||||
const id = m.auction.pubkey.toBase58();
|
||||
return (
|
||||
<Link to={`/art/${id}`}>
|
||||
<ArtCard
|
||||
key={id}
|
||||
image={m.thumbnail.metadata.info.extended?.image}
|
||||
category={m.thumbnail.metadata.info.extended?.category}
|
||||
name={m.thumbnail.metadata?.info.name}
|
||||
symbol={m.thumbnail.metadata?.info.symbol}
|
||||
description={m.thumbnail.metadata?.info.extended?.description}
|
||||
preview={false}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Masonry>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout style={{ margin: 0, marginTop: 30 }}>
|
||||
<PreSaleBanner
|
||||
artistName={"RAC"}
|
||||
productName={"THE BOY COLLECTION"}
|
||||
artistName={'RAC'}
|
||||
productName={'THE BOY COLLECTION'}
|
||||
preSaleTS={1618690343000}
|
||||
image="img/banner1.jpeg"
|
||||
/>
|
||||
|
@ -54,28 +60,31 @@ export const HomeView = () => {
|
|||
<Content style={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||
<Col style={{ width: '100%', marginTop: 10 }}>
|
||||
<Row>
|
||||
<Tabs activeKey={activeKey} onTabClick={(key) => setActiveKey(key as AuctionState)}>
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onTabClick={key => setActiveKey(key as AuctionViewState)}
|
||||
>
|
||||
<TabPane
|
||||
tab={<span className="tab-title">Live</span>}
|
||||
key={AuctionState.Live}
|
||||
key={AuctionViewState.Live}
|
||||
>
|
||||
{auctionGrid}
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={<span className="tab-title">Upcoming</span>}
|
||||
key={AuctionState.Upcoming}
|
||||
key={AuctionViewState.Upcoming}
|
||||
>
|
||||
{auctionGrid}
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={<span className="tab-title">Ended</span>}
|
||||
key={AuctionState.Ended}
|
||||
key={AuctionViewState.Ended}
|
||||
>
|
||||
{auctionGrid}
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={<span className="tab-title">Buy Now</span>}
|
||||
key={AuctionState.BuyNow}
|
||||
key={AuctionViewState.BuyNow}
|
||||
>
|
||||
{auctionGrid}
|
||||
</TabPane>
|
||||
|
|
Loading…
Reference in New Issue