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
bb3f9fd192
|
@ -49,6 +49,18 @@ export const decodeAuction = (buffer: Buffer) => {
|
|||
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
|
||||
};
|
||||
|
||||
export const decodeBidderPot = (buffer: Buffer) => {
|
||||
return deserializeBorsh(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot;
|
||||
};
|
||||
|
||||
export const decodeBidderMetadata = (buffer: Buffer) => {
|
||||
return deserializeBorsh(
|
||||
AUCTION_SCHEMA,
|
||||
BidderMetadata,
|
||||
buffer,
|
||||
) as BidderMetadata;
|
||||
};
|
||||
|
||||
export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9;
|
||||
|
||||
export class AuctionData {
|
||||
|
@ -127,8 +139,16 @@ export class BidderMetadata {
|
|||
export class BidderPot {
|
||||
/// Points at actual pot that is a token account
|
||||
bidderPot: PublicKey;
|
||||
constructor(args: { bidderPot: PublicKey }) {
|
||||
bidderAct: PublicKey;
|
||||
auctionAct: PublicKey;
|
||||
constructor(args: {
|
||||
bidderPot: PublicKey;
|
||||
bidderAct: PublicKey;
|
||||
auctionAct: PublicKey;
|
||||
}) {
|
||||
this.bidderPot = args.bidderPot;
|
||||
this.bidderAct = args.bidderAct;
|
||||
this.auctionAct = args.auctionAct;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,8 +260,8 @@ export const AUCTION_SCHEMA = new Map<any, any>([
|
|||
kind: 'struct',
|
||||
fields: [
|
||||
['instruction', 'u8'],
|
||||
['resource', 'pubkey'],
|
||||
['amount', 'u64'],
|
||||
['resource', 'pubkey'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -300,7 +320,11 @@ export const AUCTION_SCHEMA = new Map<any, any>([
|
|||
BidderPot,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [['bidderPot', 'pubkey']],
|
||||
fields: [
|
||||
['bidderPot', 'pubkey'],
|
||||
['bidderAct', 'pubkey'],
|
||||
['auctionAct', 'pubkey'],
|
||||
],
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
@ -490,7 +514,7 @@ export async function placeBid(
|
|||
const keys = [
|
||||
{
|
||||
pubkey: bidderPubkey,
|
||||
isSigner: true,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
ParsedAccount,
|
||||
SequenceType,
|
||||
sendTransactionWithRetry,
|
||||
placeBid,
|
||||
programIds,
|
||||
BidderPot,
|
||||
models,
|
||||
} from '@oyster/common';
|
||||
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import { AuctionView } from '../hooks';
|
||||
import BN from 'bn.js';
|
||||
const { createTokenAccount } = actions;
|
||||
const { approve } = models;
|
||||
|
||||
export async function sendPlaceBid(
|
||||
connection: Connection,
|
||||
wallet: any,
|
||||
bidderAccount: PublicKey,
|
||||
auctionView: AuctionView,
|
||||
amount: number,
|
||||
) {
|
||||
let signers: Account[] = [];
|
||||
let instructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span,
|
||||
);
|
||||
|
||||
let bidderPotTokenAccount: PublicKey;
|
||||
if (!auctionView.myBidderPot) {
|
||||
bidderPotTokenAccount = createTokenAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
auctionView.auction.info.tokenMint,
|
||||
programIds().auction,
|
||||
signers,
|
||||
);
|
||||
} else bidderPotTokenAccount = auctionView.myBidderPot?.info.bidderPot;
|
||||
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
[],
|
||||
bidderAccount,
|
||||
wallet.publicKey,
|
||||
amount,
|
||||
);
|
||||
|
||||
signers.push(transferAuthority);
|
||||
|
||||
await placeBid(
|
||||
bidderAccount,
|
||||
bidderPotTokenAccount,
|
||||
auctionView.auction.info.tokenMint,
|
||||
transferAuthority.publicKey,
|
||||
wallet.publicKey,
|
||||
auctionView.auctionManager.info.vault,
|
||||
new BN(amount),
|
||||
instructions,
|
||||
);
|
||||
|
||||
await sendTransactionWithRetry(
|
||||
connection,
|
||||
wallet,
|
||||
instructions,
|
||||
signers,
|
||||
'single',
|
||||
);
|
||||
}
|
|
@ -10,16 +10,21 @@ import {
|
|||
TokenAccount,
|
||||
useConnection,
|
||||
useUserAccounts,
|
||||
hooks,
|
||||
contexts,
|
||||
} from '@oyster/common';
|
||||
import { AuctionView } from '../../hooks';
|
||||
|
||||
import { sendPlaceBid } from '../../actions/sendPlaceBid';
|
||||
const { useWallet } = contexts.Wallet;
|
||||
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();
|
||||
const { wallet } = useWallet();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const [value, setValue] = useState<number>();
|
||||
const accountByMint = userAccounts.reduce((prev, acc) => {
|
||||
prev.set(acc.info.mint.toBase58(), acc);
|
||||
return prev;
|
||||
|
@ -84,14 +89,8 @@ export const AuctionCard = ({ auctionView }: { auctionView: AuctionView }) => {
|
|||
<InputNumber
|
||||
autoFocus
|
||||
className="input"
|
||||
placeholder="Max 50 characters"
|
||||
// value={props.attributes.name}
|
||||
// onChange={info =>
|
||||
// props.setAttributes({
|
||||
// ...props.attributes,
|
||||
// name: info.target.value,
|
||||
// })
|
||||
// }
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
@ -106,6 +105,18 @@ export const AuctionCard = ({ auctionView }: { auctionView: AuctionView }) => {
|
|||
type="primary"
|
||||
size="large"
|
||||
className="action-btn"
|
||||
disabled={!myPayingAccount || value === undefined}
|
||||
onClick={() => {
|
||||
console.log('Auctionview', auctionView);
|
||||
if (myPayingAccount && value)
|
||||
sendPlaceBid(
|
||||
connection,
|
||||
wallet,
|
||||
myPayingAccount.pubkey,
|
||||
auctionView,
|
||||
value,
|
||||
);
|
||||
}}
|
||||
style={{ marginTop: 20 }}
|
||||
>
|
||||
PLACE BID
|
||||
|
|
|
@ -20,6 +20,10 @@ import {
|
|||
SafetyDepositBox,
|
||||
VaultKey,
|
||||
decodeSafetyDeposit,
|
||||
BidderMetadata,
|
||||
decodeBidderMetadata,
|
||||
BidderPot,
|
||||
decodeBidderPot,
|
||||
} from '@oyster/common';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { Connection, PublicKey, PublicKeyAndAccount } from '@solana/web3.js';
|
||||
|
@ -42,10 +46,15 @@ export interface MetaContextState {
|
|||
masterEditions: Record<string, ParsedAccount<MasterEdition>>;
|
||||
auctionManagers: Record<string, ParsedAccount<AuctionManager>>;
|
||||
auctions: Record<string, ParsedAccount<AuctionData>>;
|
||||
bidderMetadataByAuctionAndBidder: Record<
|
||||
string,
|
||||
ParsedAccount<BidderMetadata>
|
||||
>;
|
||||
safetyDepositBoxesByVaultAndIndex: Record<
|
||||
string,
|
||||
ParsedAccount<SafetyDepositBox>
|
||||
>;
|
||||
bidderPotsByAuctionAndBidder: Record<string, ParsedAccount<BidderPot>>;
|
||||
}
|
||||
|
||||
const MetaContext = React.createContext<MetaContextState>({
|
||||
|
@ -56,7 +65,9 @@ const MetaContext = React.createContext<MetaContextState>({
|
|||
editions: {},
|
||||
auctionManagers: {},
|
||||
auctions: {},
|
||||
bidderMetadataByAuctionAndBidder: {},
|
||||
safetyDepositBoxesByVaultAndIndex: {},
|
||||
bidderPotsByAuctionAndBidder: {},
|
||||
});
|
||||
|
||||
export function MetaProvider({ children = null as any }) {
|
||||
|
@ -80,6 +91,14 @@ export function MetaProvider({ children = null as any }) {
|
|||
const [auctions, setAuctions] = useState<
|
||||
Record<string, ParsedAccount<AuctionData>>
|
||||
>({});
|
||||
const [
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
setBidderMetadataByAuctionAndBidder,
|
||||
] = useState<Record<string, ParsedAccount<BidderMetadata>>>({});
|
||||
const [
|
||||
bidderPotsByAuctionAndBidder,
|
||||
setBidderPotsByAuctionAndBidder,
|
||||
] = useState<Record<string, ParsedAccount<BidderPot>>>({});
|
||||
const [
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
setSafetyDepositBoxesByVaultAndIndex,
|
||||
|
@ -107,6 +126,42 @@ export function MetaProvider({ children = null as any }) {
|
|||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
try {
|
||||
const bidderMetadata = await decodeBidderMetadata(a.account.data);
|
||||
|
||||
const account: ParsedAccount<BidderMetadata> = {
|
||||
pubkey: a.pubkey,
|
||||
account: a.account,
|
||||
info: bidderMetadata,
|
||||
};
|
||||
setBidderMetadataByAuctionAndBidder(e => ({
|
||||
...e,
|
||||
[bidderMetadata.auctionPubkey.toBase58() +
|
||||
'-' +
|
||||
bidderMetadata.bidderPubkey.toBase58()]: account,
|
||||
}));
|
||||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
try {
|
||||
const bidderPot = await decodeBidderPot(a.account.data);
|
||||
|
||||
const account: ParsedAccount<BidderPot> = {
|
||||
pubkey: a.pubkey,
|
||||
account: a.account,
|
||||
info: bidderPot,
|
||||
};
|
||||
setBidderPotsByAuctionAndBidder(e => ({
|
||||
...e,
|
||||
[bidderPot.auctionAct.toBase58() +
|
||||
'-' +
|
||||
bidderPot.bidderAct.toBase58()]: account,
|
||||
}));
|
||||
} catch {
|
||||
// ignore errors
|
||||
// add type as first byte for easier deserialization
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -355,6 +410,8 @@ export function MetaProvider({ children = null as any }) {
|
|||
auctions,
|
||||
metadataByMint,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import { useConnection } from '@oyster/common';
|
||||
import { TokenAccount, useConnection, useUserAccounts } from '@oyster/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AuctionView, processAccountsIntoAuctionView } from '.';
|
||||
import { useMeta } from '../contexts';
|
||||
|
||||
export const useAuction = (id: string) => {
|
||||
const connection = useConnection();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const accountByMint = userAccounts.reduce((prev, acc) => {
|
||||
prev.set(acc.info.mint.toBase58(), acc);
|
||||
return prev;
|
||||
}, new Map<string, TokenAccount>());
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const [auctionView, setAuctionView] = useState<AuctionView | null>(null);
|
||||
const [existingAuctionView, setAuctionView] = useState<AuctionView | null>(
|
||||
null,
|
||||
);
|
||||
useEffect(() => {
|
||||
connection.getSlot().then(setClock);
|
||||
}, [connection]);
|
||||
|
@ -16,6 +23,8 @@ export const useAuction = (id: string) => {
|
|||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
} = useMeta();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -26,8 +35,12 @@ export const useAuction = (id: string) => {
|
|||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
accountByMint,
|
||||
clock,
|
||||
undefined,
|
||||
existingAuctionView || undefined,
|
||||
);
|
||||
if (auctionView) setAuctionView(auctionView);
|
||||
}
|
||||
|
@ -37,6 +50,9 @@ export const useAuction = (id: string) => {
|
|||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
userAccounts,
|
||||
]);
|
||||
return auctionView;
|
||||
return existingAuctionView;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,11 @@ import {
|
|||
AuctionData,
|
||||
useConnection,
|
||||
AuctionState,
|
||||
BidderMetadata,
|
||||
BidderPot,
|
||||
useWallet,
|
||||
useUserAccounts,
|
||||
TokenAccount,
|
||||
} from '@oyster/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMeta } from '../contexts';
|
||||
|
@ -30,12 +35,23 @@ export interface AuctionView {
|
|||
openEditionItem?: AuctionViewItem;
|
||||
state: AuctionViewState;
|
||||
thumbnail: AuctionViewItem;
|
||||
myBidderMetadata?: ParsedAccount<BidderMetadata>;
|
||||
myBidderPot?: ParsedAccount<BidderPot>;
|
||||
totallyComplete: boolean;
|
||||
}
|
||||
|
||||
export const useAuctions = (state: AuctionViewState) => {
|
||||
const connection = useConnection();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const accountByMint = userAccounts.reduce((prev, acc) => {
|
||||
prev.set(acc.info.mint.toBase58(), acc);
|
||||
return prev;
|
||||
}, new Map<string, TokenAccount>());
|
||||
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const [auctionViews, setAuctionViews] = useState<AuctionView[]>([]);
|
||||
const [auctionViews, setAuctionViews] = useState<
|
||||
Record<string, AuctionView | undefined>
|
||||
>({});
|
||||
useEffect(() => {
|
||||
connection.getSlot().then(setClock);
|
||||
}, [connection]);
|
||||
|
@ -45,23 +61,28 @@ export const useAuctions = (state: AuctionViewState) => {
|
|||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
} = useMeta();
|
||||
|
||||
useEffect(() => {
|
||||
const newAuctionViews: AuctionView[] = [];
|
||||
Object.keys(auctions).forEach(a => {
|
||||
const auction = auctions[a];
|
||||
const auctionView = processAccountsIntoAuctionView(
|
||||
const existingAuctionView = auctionViews[a];
|
||||
const nextAuctionView = processAccountsIntoAuctionView(
|
||||
auction,
|
||||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
accountByMint,
|
||||
clock,
|
||||
state,
|
||||
existingAuctionView,
|
||||
);
|
||||
if (auctionView) newAuctionViews.push(auctionView);
|
||||
setAuctionViews(nA => ({ ...nA, [a]: nextAuctionView }));
|
||||
});
|
||||
setAuctionViews(newAuctionViews);
|
||||
}, [
|
||||
clock,
|
||||
state,
|
||||
|
@ -69,9 +90,12 @@ export const useAuctions = (state: AuctionViewState) => {
|
|||
auctionManagers,
|
||||
safetyDepositBoxesByVaultAndIndex,
|
||||
metadataByMint,
|
||||
bidderMetadataByAuctionAndBidder,
|
||||
bidderPotsByAuctionAndBidder,
|
||||
userAccounts,
|
||||
]);
|
||||
|
||||
return auctionViews;
|
||||
return Object.values(auctionViews).filter(v => v) as AuctionView[];
|
||||
};
|
||||
|
||||
export function processAccountsIntoAuctionView(
|
||||
|
@ -82,9 +106,16 @@ export function processAccountsIntoAuctionView(
|
|||
ParsedAccount<SafetyDepositBox>
|
||||
>,
|
||||
metadataByMint: Record<string, ParsedAccount<Metadata>>,
|
||||
bidderMetadataByAuctionAndBidder: Record<
|
||||
string,
|
||||
ParsedAccount<BidderMetadata>
|
||||
>,
|
||||
bidderPotsByAuctionAndBidder: Record<string, ParsedAccount<BidderPot>>,
|
||||
accountByMint: Map<string, TokenAccount>,
|
||||
clock: number,
|
||||
desiredState: AuctionViewState | undefined,
|
||||
) {
|
||||
existingAuctionView?: AuctionView,
|
||||
): AuctionView | undefined {
|
||||
let state: AuctionViewState;
|
||||
if (
|
||||
auction.info.state == AuctionState.Ended ||
|
||||
|
@ -102,12 +133,32 @@ export function processAccountsIntoAuctionView(
|
|||
state = AuctionViewState.BuyNow;
|
||||
}
|
||||
|
||||
if (desiredState && desiredState != state) return null;
|
||||
if (desiredState && desiredState != state) return undefined;
|
||||
|
||||
const myPayingAccount = accountByMint.get(auction.info.tokenMint.toBase58());
|
||||
|
||||
const auctionManager =
|
||||
auctionManagers[auction.info.auctionManagerKey?.toBase58() || ''];
|
||||
if (auctionManager) {
|
||||
const boxesExpected = auctionManager.info.state.winningConfigsValidated;
|
||||
const bidderMetadata =
|
||||
bidderMetadataByAuctionAndBidder[
|
||||
auction.pubkey.toBase58() + '-' + myPayingAccount?.pubkey.toBase58()
|
||||
];
|
||||
const bidderPot =
|
||||
bidderPotsByAuctionAndBidder[
|
||||
auction.pubkey.toBase58() + '-' + myPayingAccount?.pubkey.toBase58()
|
||||
];
|
||||
if (existingAuctionView && existingAuctionView.totallyComplete) {
|
||||
// If totally complete, we know we arent updating anythign else, let's speed things up
|
||||
// and only update the two things that could possibly change
|
||||
existingAuctionView.myBidderPot = bidderPot;
|
||||
existingAuctionView.myBidderMetadata = bidderMetadata;
|
||||
return existingAuctionView;
|
||||
}
|
||||
|
||||
let boxes: ParsedAccount<SafetyDepositBox>[] = [];
|
||||
|
||||
let box =
|
||||
safetyDepositBoxesByVaultAndIndex[
|
||||
auctionManager.info.vault.toBase58() + '-0'
|
||||
|
@ -126,7 +177,7 @@ export function processAccountsIntoAuctionView(
|
|||
}
|
||||
|
||||
if (boxes.length > 0) {
|
||||
let view: any = {
|
||||
let view: Partial<AuctionView> = {
|
||||
auction,
|
||||
auctionManager,
|
||||
state,
|
||||
|
@ -150,13 +201,22 @@ export function processAccountsIntoAuctionView(
|
|||
boxes[auctionManager.info.settings.openEditionConfig],
|
||||
}
|
||||
: undefined,
|
||||
myBidderMetadata: bidderMetadata,
|
||||
myBidderPot: bidderPot,
|
||||
};
|
||||
|
||||
view.thumbnail = view.items[0] || view.openEditionItem;
|
||||
if (!view.thumbnail || !view.thumbnail.metadata) return null;
|
||||
return view;
|
||||
view.thumbnail = (view.items || [])[0] || view.openEditionItem;
|
||||
view.totallyComplete = !!(
|
||||
view.thumbnail &&
|
||||
boxesExpected == (view.items || []).length &&
|
||||
(auctionManager.info.settings.openEditionConfig == null ||
|
||||
(auctionManager.info.settings.openEditionConfig != null &&
|
||||
view.openEditionItem))
|
||||
);
|
||||
if (!view.thumbnail || !view.thumbnail.metadata) return undefined;
|
||||
return view as AuctionView;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue