Merge remote-tracking branch 'origin/master' into feature/video
This commit is contained in:
commit
3be750cb69
|
@ -15,6 +15,7 @@ import { findProgramAddress } from '../utils';
|
||||||
export const AUCTION_PREFIX = 'auction';
|
export const AUCTION_PREFIX = 'auction';
|
||||||
export const METADATA = 'metadata';
|
export const METADATA = 'metadata';
|
||||||
export const EXTENDED = 'extended';
|
export const EXTENDED = 'extended';
|
||||||
|
export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200;
|
||||||
|
|
||||||
export enum AuctionState {
|
export enum AuctionState {
|
||||||
Created = 0,
|
Created = 0,
|
||||||
|
@ -91,6 +92,23 @@ export const decodeBidderPot = (buffer: Buffer) => {
|
||||||
return deserializeUnchecked(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot;
|
return deserializeUnchecked(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AuctionDataExtendedParser: AccountParser = (
|
||||||
|
pubkey: PublicKey,
|
||||||
|
account: AccountInfo<Buffer>,
|
||||||
|
) => ({
|
||||||
|
pubkey,
|
||||||
|
account,
|
||||||
|
info: decodeAuctionDataExtended(account.data),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const decodeAuctionDataExtended = (buffer: Buffer) => {
|
||||||
|
return deserializeUnchecked(
|
||||||
|
AUCTION_SCHEMA,
|
||||||
|
AuctionDataExtended,
|
||||||
|
buffer,
|
||||||
|
) as AuctionDataExtended;
|
||||||
|
};
|
||||||
|
|
||||||
export const BidderMetadataParser: AccountParser = (
|
export const BidderMetadataParser: AccountParser = (
|
||||||
pubkey: PublicKey,
|
pubkey: PublicKey,
|
||||||
account: AccountInfo<Buffer>,
|
account: AccountInfo<Buffer>,
|
||||||
|
@ -323,7 +341,24 @@ export class WinnerLimit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateAuctionArgs {
|
export interface IPartialCreateAuctionArgs {
|
||||||
|
/// How many winners are allowed for this auction. See AuctionData.
|
||||||
|
winners: WinnerLimit;
|
||||||
|
/// End time is the cut-off point that the auction is forced to end by. See AuctionData.
|
||||||
|
endAuctionAt: BN | null;
|
||||||
|
/// Gap time is how much time after the previous bid where the auction ends. See AuctionData.
|
||||||
|
auctionGap: BN | null;
|
||||||
|
/// Token mint for the SPL token used for bidding.
|
||||||
|
tokenMint: PublicKey;
|
||||||
|
|
||||||
|
priceFloor: PriceFloor;
|
||||||
|
|
||||||
|
tickSize: BN | null;
|
||||||
|
|
||||||
|
gapTickSizePercentage: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateAuctionArgs implements IPartialCreateAuctionArgs {
|
||||||
instruction: number = 1;
|
instruction: number = 1;
|
||||||
/// How many winners are allowed for this auction. See AuctionData.
|
/// How many winners are allowed for this auction. See AuctionData.
|
||||||
winners: WinnerLimit;
|
winners: WinnerLimit;
|
||||||
|
@ -340,6 +375,10 @@ class CreateAuctionArgs {
|
||||||
|
|
||||||
priceFloor: PriceFloor;
|
priceFloor: PriceFloor;
|
||||||
|
|
||||||
|
tickSize: BN | null;
|
||||||
|
|
||||||
|
gapTickSizePercentage: number | null;
|
||||||
|
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
winners: WinnerLimit;
|
winners: WinnerLimit;
|
||||||
endAuctionAt: BN | null;
|
endAuctionAt: BN | null;
|
||||||
|
@ -348,6 +387,8 @@ class CreateAuctionArgs {
|
||||||
authority: PublicKey;
|
authority: PublicKey;
|
||||||
resource: PublicKey;
|
resource: PublicKey;
|
||||||
priceFloor: PriceFloor;
|
priceFloor: PriceFloor;
|
||||||
|
tickSize: BN | null;
|
||||||
|
gapTickSizePercentage: number | null;
|
||||||
}) {
|
}) {
|
||||||
this.winners = args.winners;
|
this.winners = args.winners;
|
||||||
this.endAuctionAt = args.endAuctionAt;
|
this.endAuctionAt = args.endAuctionAt;
|
||||||
|
@ -356,6 +397,8 @@ class CreateAuctionArgs {
|
||||||
this.authority = args.authority;
|
this.authority = args.authority;
|
||||||
this.resource = args.resource;
|
this.resource = args.resource;
|
||||||
this.priceFloor = args.priceFloor;
|
this.priceFloor = args.priceFloor;
|
||||||
|
this.tickSize = args.tickSize;
|
||||||
|
this.gapTickSizePercentage = args.gapTickSizePercentage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,6 +445,8 @@ export const AUCTION_SCHEMA = new Map<any, any>([
|
||||||
['authority', 'pubkey'],
|
['authority', 'pubkey'],
|
||||||
['resource', 'pubkey'],
|
['resource', 'pubkey'],
|
||||||
['priceFloor', PriceFloor],
|
['priceFloor', PriceFloor],
|
||||||
|
['tickSize', { kind: 'option', type: 'u64' }],
|
||||||
|
['gapTickSizePercentage', { kind: 'option', type: 'u8' }],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -541,39 +586,20 @@ export const decodeAuctionData = (buffer: Buffer) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createAuction(
|
export async function createAuction(
|
||||||
winners: WinnerLimit,
|
settings: CreateAuctionArgs,
|
||||||
resource: PublicKey,
|
|
||||||
endAuctionAt: BN | null,
|
|
||||||
auctionGap: BN | null,
|
|
||||||
priceFloor: PriceFloor,
|
|
||||||
tokenMint: PublicKey,
|
|
||||||
authority: PublicKey,
|
|
||||||
creator: PublicKey,
|
creator: PublicKey,
|
||||||
instructions: TransactionInstruction[],
|
instructions: TransactionInstruction[],
|
||||||
) {
|
) {
|
||||||
const auctionProgramId = programIds().auction;
|
const auctionProgramId = programIds().auction;
|
||||||
|
|
||||||
const data = Buffer.from(
|
const data = Buffer.from(serialize(AUCTION_SCHEMA, settings));
|
||||||
serialize(
|
|
||||||
AUCTION_SCHEMA,
|
|
||||||
new CreateAuctionArgs({
|
|
||||||
winners,
|
|
||||||
resource,
|
|
||||||
endAuctionAt,
|
|
||||||
auctionGap,
|
|
||||||
tokenMint,
|
|
||||||
authority,
|
|
||||||
priceFloor,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const auctionKey: PublicKey = (
|
const auctionKey: PublicKey = (
|
||||||
await findProgramAddress(
|
await findProgramAddress(
|
||||||
[
|
[
|
||||||
Buffer.from(AUCTION_PREFIX),
|
Buffer.from(AUCTION_PREFIX),
|
||||||
auctionProgramId.toBuffer(),
|
auctionProgramId.toBuffer(),
|
||||||
resource.toBuffer(),
|
settings.resource.toBuffer(),
|
||||||
],
|
],
|
||||||
auctionProgramId,
|
auctionProgramId,
|
||||||
)
|
)
|
||||||
|
@ -591,7 +617,10 @@ export async function createAuction(
|
||||||
isWritable: true,
|
isWritable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pubkey: await getAuctionExtended({ auctionProgramId, resource }),
|
pubkey: await getAuctionExtended({
|
||||||
|
auctionProgramId,
|
||||||
|
resource: settings.resource,
|
||||||
|
}),
|
||||||
isSigner: false,
|
isSigner: false,
|
||||||
isWritable: true,
|
isWritable: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -154,17 +154,20 @@ export class ReservationList {
|
||||||
/// What supply counter was on master_edition when this reservation was created.
|
/// What supply counter was on master_edition when this reservation was created.
|
||||||
supplySnapshot: BN | null;
|
supplySnapshot: BN | null;
|
||||||
reservations: Reservation[];
|
reservations: Reservation[];
|
||||||
|
totalReservationSpots: BN;
|
||||||
|
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
key: MetadataKey;
|
key: MetadataKey;
|
||||||
masterEdition: PublicKey;
|
masterEdition: PublicKey;
|
||||||
supplySnapshot: BN | null;
|
supplySnapshot: BN | null;
|
||||||
reservations: Reservation[];
|
reservations: Reservation[];
|
||||||
|
totalReservationSpots: BN;
|
||||||
}) {
|
}) {
|
||||||
this.key = MetadataKey.EditionV1;
|
this.key = MetadataKey.EditionV1;
|
||||||
this.masterEdition = args.masterEdition;
|
this.masterEdition = args.masterEdition;
|
||||||
this.supplySnapshot = args.supplySnapshot;
|
this.supplySnapshot = args.supplySnapshot;
|
||||||
this.reservations = args.reservations;
|
this.reservations = args.reservations;
|
||||||
|
this.totalReservationSpots = args.totalReservationSpots;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +411,7 @@ export const METADATA_SCHEMA = new Map<any, any>([
|
||||||
['masterEdition', 'pubkey'],
|
['masterEdition', 'pubkey'],
|
||||||
['supplySnapshot', { kind: 'option', type: 'u64' }],
|
['supplySnapshot', { kind: 'option', type: 'u64' }],
|
||||||
['reservations', [Reservation]],
|
['reservations', [Reservation]],
|
||||||
|
['totalReservationSpots', 'u64'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
actions,
|
actions,
|
||||||
Metadata,
|
Metadata,
|
||||||
ParsedAccount,
|
ParsedAccount,
|
||||||
WinnerLimit,
|
|
||||||
MasterEdition,
|
MasterEdition,
|
||||||
SequenceType,
|
SequenceType,
|
||||||
sendTransactions,
|
sendTransactions,
|
||||||
|
@ -20,8 +19,8 @@ import {
|
||||||
getSafetyDepositBoxAddress,
|
getSafetyDepositBoxAddress,
|
||||||
createAssociatedTokenAccountInstruction,
|
createAssociatedTokenAccountInstruction,
|
||||||
sendTransactionWithRetry,
|
sendTransactionWithRetry,
|
||||||
PriceFloor,
|
|
||||||
findProgramAddress,
|
findProgramAddress,
|
||||||
|
IPartialCreateAuctionArgs,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
|
|
||||||
import { AccountLayout, Token } from '@solana/spl-token';
|
import { AccountLayout, Token } from '@solana/spl-token';
|
||||||
|
@ -100,13 +99,10 @@ export async function createAuctionManager(
|
||||||
ParsedAccount<WhitelistedCreator>
|
ParsedAccount<WhitelistedCreator>
|
||||||
>,
|
>,
|
||||||
settings: AuctionManagerSettings,
|
settings: AuctionManagerSettings,
|
||||||
winnerLimit: WinnerLimit,
|
auctionSettings: IPartialCreateAuctionArgs,
|
||||||
endAuctionAt: BN,
|
|
||||||
auctionGap: BN,
|
|
||||||
safetyDepositDrafts: SafetyDepositDraft[],
|
safetyDepositDrafts: SafetyDepositDraft[],
|
||||||
participationSafetyDepositDraft: SafetyDepositDraft | undefined,
|
participationSafetyDepositDraft: SafetyDepositDraft | undefined,
|
||||||
paymentMint: PublicKey,
|
paymentMint: PublicKey,
|
||||||
priceFloor: PriceFloor,
|
|
||||||
): Promise<{
|
): Promise<{
|
||||||
vault: PublicKey;
|
vault: PublicKey;
|
||||||
auction: PublicKey;
|
auction: PublicKey;
|
||||||
|
@ -136,15 +132,7 @@ export async function createAuctionManager(
|
||||||
instructions: makeAuctionInstructions,
|
instructions: makeAuctionInstructions,
|
||||||
signers: makeAuctionSigners,
|
signers: makeAuctionSigners,
|
||||||
auction,
|
auction,
|
||||||
} = await makeAuction(
|
} = await makeAuction(wallet, vault, auctionSettings);
|
||||||
wallet,
|
|
||||||
winnerLimit,
|
|
||||||
vault,
|
|
||||||
endAuctionAt,
|
|
||||||
auctionGap,
|
|
||||||
paymentMint,
|
|
||||||
priceFloor,
|
|
||||||
);
|
|
||||||
|
|
||||||
let safetyDepositConfigsWithPotentiallyUnsetTokens =
|
let safetyDepositConfigsWithPotentiallyUnsetTokens =
|
||||||
await buildSafetyDepositArray(
|
await buildSafetyDepositArray(
|
||||||
|
|
|
@ -2,24 +2,19 @@ import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
import {
|
import {
|
||||||
utils,
|
utils,
|
||||||
actions,
|
actions,
|
||||||
WinnerLimit,
|
|
||||||
PriceFloor,
|
|
||||||
findProgramAddress,
|
findProgramAddress,
|
||||||
|
IPartialCreateAuctionArgs,
|
||||||
|
CreateAuctionArgs,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
|
|
||||||
import BN from 'bn.js';
|
|
||||||
import { METAPLEX_PREFIX } from '../models/metaplex';
|
import { METAPLEX_PREFIX } from '../models/metaplex';
|
||||||
const { AUCTION_PREFIX, createAuction } = actions;
|
const { AUCTION_PREFIX, createAuction } = actions;
|
||||||
|
|
||||||
// This command makes an auction
|
// This command makes an auction
|
||||||
export async function makeAuction(
|
export async function makeAuction(
|
||||||
wallet: any,
|
wallet: any,
|
||||||
winnerLimit: WinnerLimit,
|
|
||||||
vault: PublicKey,
|
vault: PublicKey,
|
||||||
endAuctionAt: BN,
|
auctionSettings: IPartialCreateAuctionArgs,
|
||||||
auctionGap: BN,
|
|
||||||
paymentMint: PublicKey,
|
|
||||||
priceFloor: PriceFloor,
|
|
||||||
): Promise<{
|
): Promise<{
|
||||||
auction: PublicKey;
|
auction: PublicKey;
|
||||||
instructions: TransactionInstruction[];
|
instructions: TransactionInstruction[];
|
||||||
|
@ -47,17 +42,13 @@ export async function makeAuction(
|
||||||
)
|
)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
createAuction(
|
const fullSettings = new CreateAuctionArgs({
|
||||||
winnerLimit,
|
...auctionSettings,
|
||||||
vault,
|
authority: auctionManagerKey,
|
||||||
endAuctionAt,
|
resource: vault,
|
||||||
auctionGap,
|
});
|
||||||
priceFloor,
|
|
||||||
paymentMint,
|
createAuction(fullSettings, wallet.publicKey, instructions);
|
||||||
auctionManagerKey,
|
|
||||||
wallet.publicKey,
|
|
||||||
instructions,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { instructions, signers, auction: auctionKey };
|
return { instructions, signers, auction: auctionKey };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { Col, Button, InputNumber, Spin } from 'antd';
|
import { Col, Button, InputNumber, Spin } from 'antd';
|
||||||
import { MemoryRouter, Route, Redirect, Link } from 'react-router-dom';
|
import { MemoryRouter, Route, Redirect, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ import {
|
||||||
formatTokenAmount,
|
formatTokenAmount,
|
||||||
useMint,
|
useMint,
|
||||||
PriceFloorType,
|
PriceFloorType,
|
||||||
|
AuctionDataExtended,
|
||||||
|
ParsedAccount,
|
||||||
|
getAuctionExtended,
|
||||||
|
programIds,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import { AuctionView, useUserBalance } from '../../hooks';
|
import { AuctionView, useUserBalance } from '../../hooks';
|
||||||
import { sendPlaceBid } from '../../actions/sendPlaceBid';
|
import { sendPlaceBid } from '../../actions/sendPlaceBid';
|
||||||
|
@ -26,9 +30,78 @@ import BN from 'bn.js';
|
||||||
import { Confetti } from '../Confetti';
|
import { Confetti } from '../Confetti';
|
||||||
import { QUOTE_MINT } from '../../constants';
|
import { QUOTE_MINT } from '../../constants';
|
||||||
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||||
|
import { useMeta } from '../../contexts';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
const { useWallet } = contexts.Wallet;
|
const { useWallet } = contexts.Wallet;
|
||||||
|
|
||||||
|
function useGapTickCheck(
|
||||||
|
value: number | undefined,
|
||||||
|
gapTick: number | null,
|
||||||
|
gapTime: number,
|
||||||
|
auctionView: AuctionView,
|
||||||
|
): boolean {
|
||||||
|
return !!useMemo(() => {
|
||||||
|
if (gapTick && value && gapTime && !auctionView.auction.info.ended()) {
|
||||||
|
// so we have a gap tick percentage, and a gap tick time, and a value, and we're not ended - are we within gap time?
|
||||||
|
const now = moment().unix();
|
||||||
|
const endedAt = auctionView.auction.info.endedAt;
|
||||||
|
if (endedAt) {
|
||||||
|
const ended = endedAt.toNumber();
|
||||||
|
if (now > ended) {
|
||||||
|
const toLamportVal = value * LAMPORTS_PER_SOL;
|
||||||
|
// Ok, we are in gap time, since now is greater than ended and we're not actually an ended auction yt.
|
||||||
|
// Check that the bid is at least gapTick % bigger than the next biggest one in the stack.
|
||||||
|
for (
|
||||||
|
let i = auctionView.auction.info.bidState.bids.length - 1;
|
||||||
|
i > -1;
|
||||||
|
i--
|
||||||
|
) {
|
||||||
|
const bid = auctionView.auction.info.bidState.bids[i];
|
||||||
|
const expected = bid.amount.toNumber();
|
||||||
|
if (expected < toLamportVal) {
|
||||||
|
const higherExpectedAmount = expected * ((100 + gapTick) / 100);
|
||||||
|
|
||||||
|
return higherExpectedAmount > toLamportVal;
|
||||||
|
} else if (expected == toLamportVal) {
|
||||||
|
// If gap tick is set, no way you can bid in this case - you must bid higher.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [value, gapTick, gapTime, auctionView]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useAuctionExtended(
|
||||||
|
auctionView: AuctionView,
|
||||||
|
): ParsedAccount<AuctionDataExtended> | undefined {
|
||||||
|
const [auctionExtended, setAuctionExtended] =
|
||||||
|
useState<ParsedAccount<AuctionDataExtended>>();
|
||||||
|
const { auctionDataExtended } = useMeta();
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
const fn = async () => {
|
||||||
|
if (!auctionExtended) {
|
||||||
|
const PROGRAM_IDS = programIds();
|
||||||
|
const extendedKey = await getAuctionExtended({
|
||||||
|
auctionProgramId: PROGRAM_IDS.auction,
|
||||||
|
resource: auctionView.vault.pubkey,
|
||||||
|
});
|
||||||
|
const extendedValue = auctionDataExtended[extendedKey.toBase58()];
|
||||||
|
if (extendedValue) setAuctionExtended(extendedValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fn();
|
||||||
|
}, [auctionDataExtended, auctionExtended, setAuctionExtended]);
|
||||||
|
|
||||||
|
return auctionExtended;
|
||||||
|
}
|
||||||
export const AuctionCard = ({
|
export const AuctionCard = ({
|
||||||
auctionView,
|
auctionView,
|
||||||
style,
|
style,
|
||||||
|
@ -74,9 +147,21 @@ export const AuctionCard = ({
|
||||||
winnerIndex,
|
winnerIndex,
|
||||||
auctionView,
|
auctionView,
|
||||||
);
|
);
|
||||||
|
const auctionExtended = useAuctionExtended(auctionView);
|
||||||
|
|
||||||
const eligibleForAnything = winnerIndex !== null || eligibleForOpenEdition;
|
const eligibleForAnything = winnerIndex !== null || eligibleForOpenEdition;
|
||||||
const gapTime = (auctionView.auction.info.auctionGap?.toNumber() || 0) / 60;
|
const gapTime = (auctionView.auction.info.auctionGap?.toNumber() || 0) / 60;
|
||||||
|
const gapTick = auctionExtended
|
||||||
|
? auctionExtended.info.gapTickSizePercentage
|
||||||
|
: 0;
|
||||||
|
const tickSize = auctionExtended ? auctionExtended.info.tickSize : 0;
|
||||||
|
const tickSizeInvalid = !!(
|
||||||
|
tickSize &&
|
||||||
|
value &&
|
||||||
|
(value * LAMPORTS_PER_SOL) % tickSize.toNumber() != 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const gapBidInvalid = useGapTickCheck(value, gapTick, gapTime, auctionView);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="auction-container" style={style}>
|
<div className="auction-container" style={style}>
|
||||||
|
@ -270,13 +355,32 @@ export const AuctionCard = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Bids placed in the last {gapTime} minutes will extend
|
Bids placed in the last {gapTime} minutes will extend
|
||||||
bidding for another {gapTime} minutes.
|
bidding for another {gapTime} minutes beyond the point in
|
||||||
|
time that bid was made.{' '}
|
||||||
|
{gapTick && (
|
||||||
|
<span>
|
||||||
|
Additionally, once the official auction end time has
|
||||||
|
passed, only bids {gapTick}% larger than an existing
|
||||||
|
bid will be accepted.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
<AuctionNumbers auctionView={auctionView} />
|
<AuctionNumbers auctionView={auctionView} />
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
{tickSizeInvalid && tickSize && (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Tick size is ◎{tickSize.toNumber() / LAMPORTS_PER_SOL}.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{gapBidInvalid && (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Your bid needs to be at least {gapTick}% larger than an
|
||||||
|
existing bid during gap periods to be eligible.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -333,6 +437,8 @@ export const AuctionCard = ({
|
||||||
className="action-btn"
|
className="action-btn"
|
||||||
onClick={placeBid}
|
onClick={placeBid}
|
||||||
disabled={
|
disabled={
|
||||||
|
tickSizeInvalid ||
|
||||||
|
gapBidInvalid ||
|
||||||
!myPayingAccount ||
|
!myPayingAccount ||
|
||||||
value === undefined ||
|
value === undefined ||
|
||||||
value * LAMPORTS_PER_SOL < priceFloor ||
|
value * LAMPORTS_PER_SOL < priceFloor ||
|
||||||
|
|
|
@ -27,6 +27,9 @@ import {
|
||||||
Vault,
|
Vault,
|
||||||
setProgramIds,
|
setProgramIds,
|
||||||
useConnectionConfig,
|
useConnectionConfig,
|
||||||
|
AuctionDataExtended,
|
||||||
|
MAX_AUCTION_DATA_EXTENDED_SIZE,
|
||||||
|
AuctionDataExtendedParser,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import { MintInfo } from '@solana/spl-token';
|
import { MintInfo } from '@solana/spl-token';
|
||||||
import { Connection, PublicKey, PublicKeyAndAccount } from '@solana/web3.js';
|
import { Connection, PublicKey, PublicKeyAndAccount } from '@solana/web3.js';
|
||||||
|
@ -66,6 +69,7 @@ interface MetaState {
|
||||||
masterEditionsByOneTimeAuthMint: Record<string, ParsedAccount<MasterEdition>>;
|
masterEditionsByOneTimeAuthMint: Record<string, ParsedAccount<MasterEdition>>;
|
||||||
auctionManagersByAuction: Record<string, ParsedAccount<AuctionManager>>;
|
auctionManagersByAuction: Record<string, ParsedAccount<AuctionManager>>;
|
||||||
auctions: Record<string, ParsedAccount<AuctionData>>;
|
auctions: Record<string, ParsedAccount<AuctionData>>;
|
||||||
|
auctionDataExtended: Record<string, ParsedAccount<AuctionDataExtended>>;
|
||||||
vaults: Record<string, ParsedAccount<Vault>>;
|
vaults: Record<string, ParsedAccount<Vault>>;
|
||||||
store: ParsedAccount<Store> | null;
|
store: ParsedAccount<Store> | null;
|
||||||
bidderMetadataByAuctionAndBidder: Record<
|
bidderMetadataByAuctionAndBidder: Record<
|
||||||
|
@ -87,29 +91,38 @@ interface MetaState {
|
||||||
|
|
||||||
const { MetadataKey } = actions;
|
const { MetadataKey } = actions;
|
||||||
|
|
||||||
type UpdateStateValueFunc = (prop: keyof MetaState, key: string, value: any) => void;
|
type UpdateStateValueFunc = (
|
||||||
|
prop: keyof MetaState,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
) => void;
|
||||||
export interface MetaContextState extends MetaState {
|
export interface MetaContextState extends MetaState {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMetadataPartOfStore = (m: ParsedAccount<Metadata> , store: ParsedAccount<Store> | null, whitelistedCreatorsByCreator: Record<
|
const isMetadataPartOfStore = (
|
||||||
string,
|
m: ParsedAccount<Metadata>,
|
||||||
ParsedAccount<WhitelistedCreator>
|
store: ParsedAccount<Store> | null,
|
||||||
>) => {
|
whitelistedCreatorsByCreator: Record<
|
||||||
if(!m?.info?.data?.creators) {
|
string,
|
||||||
|
ParsedAccount<WhitelistedCreator>
|
||||||
|
>,
|
||||||
|
) => {
|
||||||
|
if (!m?.info?.data?.creators) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.info.data.creators.findIndex(
|
return (
|
||||||
|
m.info.data.creators.findIndex(
|
||||||
c =>
|
c =>
|
||||||
c.verified &&
|
c.verified &&
|
||||||
store &&
|
store &&
|
||||||
store.info &&
|
store.info &&
|
||||||
(store.info.public ||
|
(store.info.public ||
|
||||||
whitelistedCreatorsByCreator[c.address.toBase58()]?.info
|
whitelistedCreatorsByCreator[c.address.toBase58()]?.info?.activated),
|
||||||
?.activated),
|
) >= 0
|
||||||
) >= 0;
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const MetaContext = React.createContext<MetaContextState>({
|
const MetaContext = React.createContext<MetaContextState>({
|
||||||
metadata: [],
|
metadata: [],
|
||||||
|
@ -121,6 +134,7 @@ const MetaContext = React.createContext<MetaContextState>({
|
||||||
editions: {},
|
editions: {},
|
||||||
auctionManagersByAuction: {},
|
auctionManagersByAuction: {},
|
||||||
auctions: {},
|
auctions: {},
|
||||||
|
auctionDataExtended: {},
|
||||||
vaults: {},
|
vaults: {},
|
||||||
store: null,
|
store: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
@ -140,13 +154,20 @@ export function MetaProvider({ children = null as any }) {
|
||||||
metadata: [] as Array<ParsedAccount<Metadata>>,
|
metadata: [] as Array<ParsedAccount<Metadata>>,
|
||||||
metadataByMint: {} as Record<string, ParsedAccount<Metadata>>,
|
metadataByMint: {} as Record<string, ParsedAccount<Metadata>>,
|
||||||
masterEditions: {} as Record<string, ParsedAccount<MasterEdition>>,
|
masterEditions: {} as Record<string, ParsedAccount<MasterEdition>>,
|
||||||
masterEditionsByPrintingMint: {} as Record<string, ParsedAccount<MasterEdition>>,
|
masterEditionsByPrintingMint: {} as Record<
|
||||||
masterEditionsByOneTimeAuthMint: {} as Record<string, ParsedAccount<MasterEdition>>,
|
string,
|
||||||
|
ParsedAccount<MasterEdition>
|
||||||
|
>,
|
||||||
|
masterEditionsByOneTimeAuthMint: {} as Record<
|
||||||
|
string,
|
||||||
|
ParsedAccount<MasterEdition>
|
||||||
|
>,
|
||||||
metadataByMasterEdition: {} as any,
|
metadataByMasterEdition: {} as any,
|
||||||
editions: {},
|
editions: {},
|
||||||
auctionManagersByAuction: {},
|
auctionManagersByAuction: {},
|
||||||
bidRedemptions: {},
|
bidRedemptions: {},
|
||||||
auctions: {},
|
auctions: {},
|
||||||
|
auctionDataExtended: {},
|
||||||
vaults: {},
|
vaults: {},
|
||||||
payoutTickets: {},
|
payoutTickets: {},
|
||||||
store: null as ParsedAccount<Store> | null,
|
store: null as ParsedAccount<Store> | null,
|
||||||
|
@ -154,7 +175,7 @@ export function MetaProvider({ children = null as any }) {
|
||||||
bidderMetadataByAuctionAndBidder: {},
|
bidderMetadataByAuctionAndBidder: {},
|
||||||
bidderPotsByAuctionAndBidder: {},
|
bidderPotsByAuctionAndBidder: {},
|
||||||
safetyDepositBoxesByVaultAndIndex: {},
|
safetyDepositBoxesByVaultAndIndex: {},
|
||||||
})
|
});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
@ -162,11 +183,11 @@ export function MetaProvider({ children = null as any }) {
|
||||||
async metadataByMint => {
|
async metadataByMint => {
|
||||||
try {
|
try {
|
||||||
const m = await queryExtendedMetadata(connection, metadataByMint);
|
const m = await queryExtendedMetadata(connection, metadataByMint);
|
||||||
setState((current) => ({
|
setState(current => ({
|
||||||
...current,
|
...current,
|
||||||
metadata: m.metadata,
|
metadata: m.metadata,
|
||||||
metadataByMint: m.mintToMetadata,
|
metadataByMint: m.mintToMetadata,
|
||||||
}))
|
}));
|
||||||
} catch (er) {
|
} catch (er) {
|
||||||
console.error(er);
|
console.error(er);
|
||||||
}
|
}
|
||||||
|
@ -202,6 +223,7 @@ export function MetaProvider({ children = null as any }) {
|
||||||
auctionManagersByAuction: {},
|
auctionManagersByAuction: {},
|
||||||
bidRedemptions: {},
|
bidRedemptions: {},
|
||||||
auctions: {},
|
auctions: {},
|
||||||
|
auctionDataExtended: {},
|
||||||
vaults: {},
|
vaults: {},
|
||||||
payoutTickets: {},
|
payoutTickets: {},
|
||||||
store: null,
|
store: null,
|
||||||
|
@ -214,11 +236,11 @@ export function MetaProvider({ children = null as any }) {
|
||||||
const updateTemp = (prop: keyof MetaState, key: string, value: any) => {
|
const updateTemp = (prop: keyof MetaState, key: string, value: any) => {
|
||||||
if (prop === 'store') {
|
if (prop === 'store') {
|
||||||
tempCache[prop] = value;
|
tempCache[prop] = value;
|
||||||
} else if(tempCache[prop]) {
|
} else if (tempCache[prop]) {
|
||||||
const bucket = tempCache[prop] as any;
|
const bucket = tempCache[prop] as any;
|
||||||
bucket[key] = value as any;
|
bucket[key] = value as any;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (let i = 0; i < accounts.length; i++) {
|
for (let i = 0; i < accounts.length; i++) {
|
||||||
let account = accounts[i];
|
let account = accounts[i];
|
||||||
|
@ -226,18 +248,25 @@ export function MetaProvider({ children = null as any }) {
|
||||||
processAuctions(account, updateTemp);
|
processAuctions(account, updateTemp);
|
||||||
processMetaData(account, updateTemp);
|
processMetaData(account, updateTemp);
|
||||||
|
|
||||||
await processMetaplexAccounts(
|
await processMetaplexAccounts(account, updateTemp);
|
||||||
account,
|
|
||||||
updateTemp,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = Object.values(tempCache.metadataByMint) as ParsedAccount<Metadata>[];
|
const values = Object.values(
|
||||||
|
tempCache.metadataByMint,
|
||||||
|
) as ParsedAccount<Metadata>[];
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
const metadata = values[i];
|
const metadata = values[i];
|
||||||
if(isMetadataPartOfStore(metadata, tempCache.store, tempCache.whitelistedCreatorsByCreator)) {
|
if (
|
||||||
|
isMetadataPartOfStore(
|
||||||
|
metadata,
|
||||||
|
tempCache.store,
|
||||||
|
tempCache.whitelistedCreatorsByCreator,
|
||||||
|
)
|
||||||
|
) {
|
||||||
await metadata.info.init();
|
await metadata.info.init();
|
||||||
tempCache.metadataByMasterEdition[metadata.info?.masterEdition?.toBase58() || ''] = metadata;
|
tempCache.metadataByMasterEdition[
|
||||||
|
metadata.info?.masterEdition?.toBase58() || ''
|
||||||
|
] = metadata;
|
||||||
} else {
|
} else {
|
||||||
delete tempCache.metadataByMint[metadata.info.mint.toBase58() || ''];
|
delete tempCache.metadataByMint[metadata.info.mint.toBase58() || ''];
|
||||||
}
|
}
|
||||||
|
@ -247,7 +276,7 @@ export function MetaProvider({ children = null as any }) {
|
||||||
tempCache.metadata = values;
|
tempCache.metadata = values;
|
||||||
setState({
|
setState({
|
||||||
...tempCache,
|
...tempCache,
|
||||||
})
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
console.log('------->set finished');
|
console.log('------->set finished');
|
||||||
|
@ -258,36 +287,34 @@ export function MetaProvider({ children = null as any }) {
|
||||||
return () => {
|
return () => {
|
||||||
dispose();
|
dispose();
|
||||||
};
|
};
|
||||||
}, [
|
}, [connection, setState, updateMints, env]);
|
||||||
connection,
|
|
||||||
setState,
|
|
||||||
updateMints,
|
|
||||||
env,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updateStateValue = useMemo(() => (prop: keyof MetaState, key: string, value: any) => {
|
const updateStateValue = useMemo(
|
||||||
setState((current) => {
|
() => (prop: keyof MetaState, key: string, value: any) => {
|
||||||
if (prop === 'store') {
|
setState(current => {
|
||||||
return {
|
if (prop === 'store') {
|
||||||
...current,
|
return {
|
||||||
[prop]: value,
|
...current,
|
||||||
|
[prop]: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
[prop]: {
|
||||||
|
...current[prop],
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
return ({
|
},
|
||||||
...current,
|
[setState],
|
||||||
[prop]: {
|
);
|
||||||
...current[prop],
|
|
||||||
[key]: value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [setState]);
|
|
||||||
|
|
||||||
const store = state.store;
|
const store = state.store;
|
||||||
const whitelistedCreatorsByCreator = state.whitelistedCreatorsByCreator;
|
const whitelistedCreatorsByCreator = state.whitelistedCreatorsByCreator;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(isLoading) {
|
if (isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +367,10 @@ export function MetaProvider({ children = null as any }) {
|
||||||
updateStateValue,
|
updateStateValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
if(result && isMetadataPartOfStore(result, store, whitelistedCreatorsByCreator)) {
|
if (
|
||||||
|
result &&
|
||||||
|
isMetadataPartOfStore(result, store, whitelistedCreatorsByCreator)
|
||||||
|
) {
|
||||||
await result.info.init();
|
await result.info.init();
|
||||||
setState((data) => ({
|
setState((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
@ -370,7 +400,7 @@ export function MetaProvider({ children = null as any }) {
|
||||||
pubkey,
|
pubkey,
|
||||||
account: info.accountInfo,
|
account: info.accountInfo,
|
||||||
},
|
},
|
||||||
updateStateValue
|
updateStateValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -430,9 +460,12 @@ export function MetaProvider({ children = null as any }) {
|
||||||
masterEditions: state.masterEditions,
|
masterEditions: state.masterEditions,
|
||||||
auctionManagersByAuction: state.auctionManagersByAuction,
|
auctionManagersByAuction: state.auctionManagersByAuction,
|
||||||
auctions: state.auctions,
|
auctions: state.auctions,
|
||||||
|
auctionDataExtended: state.auctionDataExtended,
|
||||||
metadataByMint: state.metadataByMint,
|
metadataByMint: state.metadataByMint,
|
||||||
safetyDepositBoxesByVaultAndIndex: state.safetyDepositBoxesByVaultAndIndex,
|
safetyDepositBoxesByVaultAndIndex:
|
||||||
bidderMetadataByAuctionAndBidder: state.bidderMetadataByAuctionAndBidder,
|
state.safetyDepositBoxesByVaultAndIndex,
|
||||||
|
bidderMetadataByAuctionAndBidder:
|
||||||
|
state.bidderMetadataByAuctionAndBidder,
|
||||||
bidderPotsByAuctionAndBidder: state.bidderPotsByAuctionAndBidder,
|
bidderPotsByAuctionAndBidder: state.bidderPotsByAuctionAndBidder,
|
||||||
vaults: state.vaults,
|
vaults: state.vaults,
|
||||||
bidRedemptions: state.bidRedemptions,
|
bidRedemptions: state.bidRedemptions,
|
||||||
|
@ -525,6 +558,21 @@ const processAuctions = (
|
||||||
// ignore errors
|
// ignore errors
|
||||||
// add type as first byte for easier deserialization
|
// add type as first byte for easier deserialization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (a.account.data.length === MAX_AUCTION_DATA_EXTENDED_SIZE) {
|
||||||
|
const account = cache.add(
|
||||||
|
a.pubkey,
|
||||||
|
a.account,
|
||||||
|
AuctionDataExtendedParser,
|
||||||
|
false,
|
||||||
|
) as ParsedAccount<AuctionDataExtended>;
|
||||||
|
setter('auctionDataExtended', a.pubkey.toBase58(), account);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
|
// add type as first byte for easier deserialization
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (a.account.data.length === BIDDER_METADATA_LEN) {
|
if (a.account.data.length === BIDDER_METADATA_LEN) {
|
||||||
const account = cache.add(
|
const account = cache.add(
|
||||||
|
@ -536,9 +584,10 @@ const processAuctions = (
|
||||||
setter(
|
setter(
|
||||||
'bidderMetadataByAuctionAndBidder',
|
'bidderMetadataByAuctionAndBidder',
|
||||||
account.info.auctionPubkey.toBase58() +
|
account.info.auctionPubkey.toBase58() +
|
||||||
'-' +
|
'-' +
|
||||||
account.info.bidderPubkey.toBase58(),
|
account.info.bidderPubkey.toBase58(),
|
||||||
account);
|
account,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore errors
|
// ignore errors
|
||||||
|
@ -555,9 +604,10 @@ const processAuctions = (
|
||||||
setter(
|
setter(
|
||||||
'bidderPotsByAuctionAndBidder',
|
'bidderPotsByAuctionAndBidder',
|
||||||
account.info.auctionAct.toBase58() +
|
account.info.auctionAct.toBase58() +
|
||||||
'-' +
|
'-' +
|
||||||
account.info.bidderAct.toBase58(),
|
account.info.bidderAct.toBase58(),
|
||||||
account);
|
account,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore errors
|
// ignore errors
|
||||||
|
@ -591,7 +641,11 @@ const processMetaplexAccounts = async (
|
||||||
account: a.account,
|
account: a.account,
|
||||||
info: auctionManager,
|
info: auctionManager,
|
||||||
};
|
};
|
||||||
setter('auctionManagersByAuction', auctionManager.auction.toBase58(), account);
|
setter(
|
||||||
|
'auctionManagersByAuction',
|
||||||
|
auctionManager.auction.toBase58(),
|
||||||
|
account,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (a.account.data[0] === MetaplexKey.BidRedemptionTicketV1) {
|
} else if (a.account.data[0] === MetaplexKey.BidRedemptionTicketV1) {
|
||||||
|
@ -645,7 +699,11 @@ const processMetaplexAccounts = async (
|
||||||
account.info.image = nameInfo.image;
|
account.info.image = nameInfo.image;
|
||||||
account.info.twitter = nameInfo.twitter;
|
account.info.twitter = nameInfo.twitter;
|
||||||
}
|
}
|
||||||
setter('whitelistedCreatorsByCreator', whitelistedCreator.address.toBase58(), account);
|
setter(
|
||||||
|
'whitelistedCreatorsByCreator',
|
||||||
|
whitelistedCreator.address.toBase58(),
|
||||||
|
account,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -658,7 +716,8 @@ const processMetaData = (
|
||||||
meta: PublicKeyAndAccount<Buffer>,
|
meta: PublicKeyAndAccount<Buffer>,
|
||||||
setter: UpdateStateValueFunc,
|
setter: UpdateStateValueFunc,
|
||||||
) => {
|
) => {
|
||||||
if (meta.account.owner.toBase58() !== programIds().metadata.toBase58()) return;
|
if (meta.account.owner.toBase58() !== programIds().metadata.toBase58())
|
||||||
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (meta.account.data[0] === MetadataKey.MetadataV1) {
|
if (meta.account.data[0] === MetadataKey.MetadataV1) {
|
||||||
|
@ -692,8 +751,16 @@ const processMetaData = (
|
||||||
info: masterEdition,
|
info: masterEdition,
|
||||||
};
|
};
|
||||||
setter('masterEditions', meta.pubkey.toBase58(), account);
|
setter('masterEditions', meta.pubkey.toBase58(), account);
|
||||||
setter('masterEditionsByPrintingMint', masterEdition.printingMint.toBase58(), account);
|
setter(
|
||||||
setter('masterEditionsByOneTimeAuthMint', masterEdition.oneTimePrintingAuthorizationMint.toBase58(), account);
|
'masterEditionsByPrintingMint',
|
||||||
|
masterEdition.printingMint.toBase58(),
|
||||||
|
account,
|
||||||
|
);
|
||||||
|
setter(
|
||||||
|
'masterEditionsByOneTimeAuthMint',
|
||||||
|
masterEdition.oneTimePrintingAuthorizationMint.toBase58(),
|
||||||
|
account,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore errors
|
// ignore errors
|
||||||
|
@ -717,7 +784,8 @@ const processVaultData = (
|
||||||
setter(
|
setter(
|
||||||
'safetyDepositBoxesByVaultAndIndex',
|
'safetyDepositBoxesByVaultAndIndex',
|
||||||
safetyDeposit.vault.toBase58() + '-' + safetyDeposit.order,
|
safetyDeposit.vault.toBase58() + '-' + safetyDeposit.order,
|
||||||
account);
|
account,
|
||||||
|
);
|
||||||
} else if (a.account.data[0] === VaultKey.VaultV1) {
|
} else if (a.account.data[0] === VaultKey.VaultV1) {
|
||||||
const vault = decodeVault(a.account.data);
|
const vault = decodeVault(a.account.data);
|
||||||
const account: ParsedAccount<Vault> = {
|
const account: ParsedAccount<Vault> = {
|
||||||
|
@ -726,10 +794,7 @@ const processVaultData = (
|
||||||
info: vault,
|
info: vault,
|
||||||
};
|
};
|
||||||
|
|
||||||
setter(
|
setter('vaults', a.pubkey.toBase58(), account);
|
||||||
'vaults',
|
|
||||||
a.pubkey.toBase58(),
|
|
||||||
account);
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore errors
|
// ignore errors
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
Creator,
|
Creator,
|
||||||
PriceFloor,
|
PriceFloor,
|
||||||
PriceFloorType,
|
PriceFloorType,
|
||||||
|
IPartialCreateAuctionArgs,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
|
@ -305,14 +306,29 @@ export const AuctionCreateView = () => {
|
||||||
console.log('Tiered settings', settings);
|
console.log('Tiered settings', settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auctionSettings: IPartialCreateAuctionArgs = {
|
||||||
|
winners: winnerLimit,
|
||||||
|
endAuctionAt: new BN((attributes.auctionDuration || 0) * 60), // endAuctionAt is actually auction duration, poorly named, in seconds
|
||||||
|
auctionGap: new BN((attributes.gapTime || 0) * 60),
|
||||||
|
priceFloor: new PriceFloor({
|
||||||
|
type: attributes.priceFloor
|
||||||
|
? PriceFloorType.Minimum
|
||||||
|
: PriceFloorType.None,
|
||||||
|
minPrice: new BN((attributes.priceFloor || 0) * LAMPORTS_PER_SOL),
|
||||||
|
}),
|
||||||
|
tokenMint: QUOTE_MINT,
|
||||||
|
gapTickSizePercentage: attributes.tickSizeEndingPhase || null,
|
||||||
|
tickSize: attributes.priceTick
|
||||||
|
? new BN(attributes.priceTick * LAMPORTS_PER_SOL)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
|
||||||
const _auctionObj = await createAuctionManager(
|
const _auctionObj = await createAuctionManager(
|
||||||
connection,
|
connection,
|
||||||
wallet,
|
wallet,
|
||||||
whitelistedCreatorsByCreator,
|
whitelistedCreatorsByCreator,
|
||||||
settings,
|
settings,
|
||||||
winnerLimit,
|
auctionSettings,
|
||||||
new BN((attributes.auctionDuration || 0) * 60), // endAuctionAt is actually auction duration, poorly named, in seconds
|
|
||||||
new BN((attributes.gapTime || 0) * 60),
|
|
||||||
attributes.category === AuctionCategory.Open
|
attributes.category === AuctionCategory.Open
|
||||||
? []
|
? []
|
||||||
: attributes.category !== AuctionCategory.Tiered
|
: attributes.category !== AuctionCategory.Tiered
|
||||||
|
@ -322,12 +338,6 @@ export const AuctionCreateView = () => {
|
||||||
? attributes.items[0]
|
? attributes.items[0]
|
||||||
: attributes.participationNFT,
|
: attributes.participationNFT,
|
||||||
QUOTE_MINT,
|
QUOTE_MINT,
|
||||||
new PriceFloor({
|
|
||||||
type: attributes.priceFloor
|
|
||||||
? PriceFloorType.Minimum
|
|
||||||
: PriceFloorType.None,
|
|
||||||
minPrice: new BN((attributes.priceFloor || 0) * LAMPORTS_PER_SOL),
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
setAuctionObj(_auctionObj);
|
setAuctionObj(_auctionObj);
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -122,6 +122,18 @@ pub enum AuctionError {
|
||||||
/// Data type mismatch
|
/// Data type mismatch
|
||||||
#[error("Data type mismatch")]
|
#[error("Data type mismatch")]
|
||||||
DataTypeMismatch,
|
DataTypeMismatch,
|
||||||
|
|
||||||
|
/// Bid must be multiple of tick size
|
||||||
|
#[error("Bid must be multiple of tick size")]
|
||||||
|
BidMustBeMultipleOfTickSize,
|
||||||
|
|
||||||
|
/// During the gap window, gap between next lowest bid must be of a certain percentage
|
||||||
|
#[error("During the gap window, gap between next lowest bid must be of a certain percentage")]
|
||||||
|
GapBetweenBidsTooSmall,
|
||||||
|
|
||||||
|
/// Gap tick size percentage must be between 0 and 100
|
||||||
|
#[error("Gap tick size percentage must be between 0 and 100")]
|
||||||
|
InvalidGapTickSizePercentage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrintProgramError for AuctionError {
|
impl PrintProgramError for AuctionError {
|
||||||
|
|
|
@ -134,10 +134,10 @@ impl AuctionData {
|
||||||
(Some(end), Some(gap)) => {
|
(Some(end), Some(gap)) => {
|
||||||
// Check if the bid is within the gap between the last bidder.
|
// Check if the bid is within the gap between the last bidder.
|
||||||
if let Some(last) = self.last_bid {
|
if let Some(last) = self.last_bid {
|
||||||
let next_bid_time = match last.checked_add(gap) {
|
let next_bid_time = last
|
||||||
Some(val) => val,
|
.checked_add(gap)
|
||||||
None => return Err(AuctionError::NumericalOverflowError.into()),
|
.ok_or(AuctionError::NumericalOverflowError)?;
|
||||||
};
|
|
||||||
Ok(now > end && now > next_bid_time)
|
Ok(now > end && now > next_bid_time)
|
||||||
} else {
|
} else {
|
||||||
Ok(now > end)
|
Ok(now > end)
|
||||||
|
@ -179,6 +179,28 @@ impl AuctionData {
|
||||||
};
|
};
|
||||||
self.bid_state.winner_at(idx, minimum)
|
self.bid_state.winner_at(idx, minimum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn place_bid(
|
||||||
|
&mut self,
|
||||||
|
bid: Bid,
|
||||||
|
tick_size: Option<u64>,
|
||||||
|
gap_tick_size_percentage: Option<u8>,
|
||||||
|
now: UnixTimestamp,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
let gap_val = match self.ended_at {
|
||||||
|
Some(end) => {
|
||||||
|
// We use the actual gap tick size perc if we're in gap window,
|
||||||
|
// otherwise we pass in none so the logic isnt used
|
||||||
|
if now > end {
|
||||||
|
gap_tick_size_percentage
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
self.bid_state.place_bid(bid, tick_size, gap_val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define valid auction state transitions.
|
/// Define valid auction state transitions.
|
||||||
|
@ -259,59 +281,120 @@ impl BidState {
|
||||||
real_max
|
real_max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_valid_tick_size_bid(bid: &Bid, tick_size: Option<u64>) -> ProgramResult {
|
||||||
|
if let Some(tick) = tick_size {
|
||||||
|
if bid.1.checked_rem(tick) != Some(0) {
|
||||||
|
msg!(
|
||||||
|
"This bid {:?} is not a multiple of tick size {:?}, throw it out.",
|
||||||
|
bid.1,
|
||||||
|
tick_size
|
||||||
|
);
|
||||||
|
return Err(AuctionError::BidMustBeMultipleOfTickSize.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg!("No tick size on this auction")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_valid_gap_insertion(
|
||||||
|
gap_tick: u8,
|
||||||
|
beaten_bid: &Bid,
|
||||||
|
beating_bid: &Bid,
|
||||||
|
) -> ProgramResult {
|
||||||
|
// Use u128 to avoid potential overflow due to temporary mult of 100x since
|
||||||
|
// we haven't divided yet.
|
||||||
|
let mut minimum_bid_amount: u128 = (beaten_bid.1 as u128)
|
||||||
|
.checked_mul((100 + gap_tick) as u128)
|
||||||
|
.ok_or(AuctionError::NumericalOverflowError)?;
|
||||||
|
minimum_bid_amount = minimum_bid_amount
|
||||||
|
.checked_div(100u128)
|
||||||
|
.ok_or(AuctionError::NumericalOverflowError)?;
|
||||||
|
|
||||||
|
if minimum_bid_amount > beating_bid.1 as u128 {
|
||||||
|
msg!("Rejecting inserting this bid due to gap tick size of {:?} which causes min bid of {:?} from {:?} which is the bid it is trying to beat", gap_tick, minimum_bid_amount.to_string(), beaten_bid.1);
|
||||||
|
return Err(AuctionError::GapBetweenBidsTooSmall.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Push a new bid into the state, this succeeds only if the bid is larger than the current top
|
/// Push a new bid into the state, this succeeds only if the bid is larger than the current top
|
||||||
/// winner stored. Crappy list information to start with.
|
/// winner stored. Crappy list information to start with.
|
||||||
pub fn place_bid(&mut self, bid: Bid) -> Result<(), ProgramError> {
|
pub fn place_bid(
|
||||||
|
&mut self,
|
||||||
|
bid: Bid,
|
||||||
|
tick_size: Option<u64>,
|
||||||
|
gap_tick_size_percentage: Option<u8>,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
msg!("Placing bid {:?}", &bid.1.to_string());
|
||||||
|
BidState::assert_valid_tick_size_bid(&bid, tick_size)?;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
// In a capped auction, track the limited number of winners.
|
// In a capped auction, track the limited number of winners.
|
||||||
BidState::EnglishAuction { ref mut bids, max } => match bids.last() {
|
BidState::EnglishAuction { ref mut bids, max } => {
|
||||||
Some(top) => {
|
match bids.last() {
|
||||||
msg!("Looking to go over the loop");
|
Some(top) => {
|
||||||
for i in (0..bids.len()).rev() {
|
msg!("Looking to go over the loop, but check tick size first");
|
||||||
msg!("Comparison of {:?} and {:?} for {:?}", bids[i].1, bid.1, i);
|
|
||||||
if bids[i].1 < bid.1 {
|
for i in (0..bids.len()).rev() {
|
||||||
msg!("Ok we can do an insert");
|
msg!("Comparison of {:?} and {:?} for {:?}", bids[i].1, bid.1, i);
|
||||||
if i + 1 < bids.len() {
|
if bids[i].1 < bid.1 {
|
||||||
msg!("Doing a normal insert");
|
if let Some(gap_tick) = gap_tick_size_percentage {
|
||||||
bids.insert(i + 1, bid);
|
BidState::assert_valid_gap_insertion(gap_tick, &bids[i], &bid)?
|
||||||
} else {
|
}
|
||||||
msg!("Doing an on the end insert");
|
|
||||||
bids.push(bid)
|
msg!("Ok we can do an insert");
|
||||||
}
|
if i + 1 < bids.len() {
|
||||||
break;
|
msg!("Doing a normal insert");
|
||||||
} else if bids[i].1 == bid.1 {
|
bids.insert(i + 1, bid);
|
||||||
msg!("Ok we can do an equivalent insert");
|
} else {
|
||||||
if i == 0 {
|
msg!("Doing an on the end insert");
|
||||||
msg!("Doing a normal insert");
|
bids.push(bid)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if bids[i].1 == bid.1 {
|
||||||
|
if let Some(gap_tick) = gap_tick_size_percentage {
|
||||||
|
if gap_tick > 0 {
|
||||||
|
msg!("Rejecting same-bid insert due to gap tick size of {:?}", gap_tick);
|
||||||
|
return Err(AuctionError::GapBetweenBidsTooSmall.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg!("Ok we can do an equivalent insert");
|
||||||
|
if i == 0 {
|
||||||
|
msg!("Doing a normal insert");
|
||||||
|
bids.insert(0, bid);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if bids[i - 1].1 != bids[i].1 {
|
||||||
|
msg!("Doing an insert just before");
|
||||||
|
bids.insert(i, bid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg!("More duplicates ahead...")
|
||||||
|
}
|
||||||
|
} else if i == 0 {
|
||||||
|
msg!("Inserting at 0");
|
||||||
bids.insert(0, bid);
|
bids.insert(0, bid);
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
if bids[i - 1].1 != bids[i].1 {
|
|
||||||
msg!("Doing an insert just before");
|
|
||||||
bids.insert(i, bid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
msg!("More duplicates ahead...")
|
|
||||||
}
|
}
|
||||||
} else if i == 0 {
|
|
||||||
msg!("Inserting at 0");
|
|
||||||
bids.insert(0, bid);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
let max_size = BidState::max_array_size_for(*max);
|
||||||
let max_size = BidState::max_array_size_for(*max);
|
|
||||||
|
|
||||||
if bids.len() > max_size {
|
if bids.len() > max_size {
|
||||||
bids.remove(0);
|
bids.remove(0);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
msg!("Pushing bid onto stack");
|
||||||
|
bids.push(bid);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
_ => {
|
}
|
||||||
msg!("Pushing bid onto stack");
|
|
||||||
bids.push(bid);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// In an open auction, bidding simply succeeds.
|
// In an open auction, bidding simply succeeds.
|
||||||
BidState::OpenEdition { bids, max } => Ok(()),
|
BidState::OpenEdition { bids, max } => Ok(()),
|
||||||
|
|
|
@ -40,6 +40,10 @@ pub struct CreateAuctionArgs {
|
||||||
pub resource: Pubkey,
|
pub resource: Pubkey,
|
||||||
/// Set a price floor.
|
/// Set a price floor.
|
||||||
pub price_floor: PriceFloor,
|
pub price_floor: PriceFloor,
|
||||||
|
/// Add a tick size increment
|
||||||
|
pub tick_size: Option<u64>,
|
||||||
|
/// Add a minimum percentage increase each bid must meet.
|
||||||
|
pub gap_tick_size_percentage: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Accounts<'a, 'b: 'a> {
|
struct Accounts<'a, 'b: 'a> {
|
||||||
|
@ -98,6 +102,12 @@ pub fn create_auction(
|
||||||
WinnerLimit::Unlimited(_) => BidState::new_open_edition(),
|
WinnerLimit::Unlimited(_) => BidState::new_open_edition(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(gap_tick) = args.gap_tick_size_percentage {
|
||||||
|
if gap_tick > 100 {
|
||||||
|
return Err(AuctionError::InvalidGapTickSizePercentage.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create auction account with enough space for a winner tracking.
|
// Create auction account with enough space for a winner tracking.
|
||||||
create_or_allocate_account_raw(
|
create_or_allocate_account_raw(
|
||||||
*program_id,
|
*program_id,
|
||||||
|
@ -141,6 +151,14 @@ pub fn create_auction(
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Configure extended
|
||||||
|
AuctionDataExtended {
|
||||||
|
total_uncancelled_bids: 0,
|
||||||
|
tick_size: args.tick_size,
|
||||||
|
gap_tick_size_percentage: args.gap_tick_size_percentage,
|
||||||
|
}
|
||||||
|
.serialize(&mut *accounts.auction_extended.data.borrow_mut())?;
|
||||||
|
|
||||||
// Configure Auction.
|
// Configure Auction.
|
||||||
AuctionData {
|
AuctionData {
|
||||||
authority: args.authority,
|
authority: args.authority,
|
||||||
|
|
|
@ -316,9 +316,12 @@ pub fn place_bid<'r, 'b: 'r>(
|
||||||
|
|
||||||
// Serialize new Auction State
|
// Serialize new Auction State
|
||||||
auction.last_bid = Some(clock.unix_timestamp);
|
auction.last_bid = Some(clock.unix_timestamp);
|
||||||
auction
|
auction.place_bid(
|
||||||
.bid_state
|
Bid(*accounts.bidder.key, args.amount),
|
||||||
.place_bid(Bid(*accounts.bidder.key, args.amount))?;
|
auction_extended.tick_size,
|
||||||
|
auction_extended.gap_tick_size_percentage,
|
||||||
|
clock.unix_timestamp,
|
||||||
|
)?;
|
||||||
auction.serialize(&mut *accounts.auction.data.borrow_mut())?;
|
auction.serialize(&mut *accounts.auction.data.borrow_mut())?;
|
||||||
|
|
||||||
// Update latest metadata with results from the bid.
|
// Update latest metadata with results from the bid.
|
||||||
|
|
|
@ -21,12 +21,60 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn set_reservation_list_wrapper<'a>(
|
||||||
|
program_id: &'a Pubkey,
|
||||||
|
master_edition_info: &AccountInfo<'a>,
|
||||||
|
reservation_list_info: &AccountInfo<'a>,
|
||||||
|
auction_manager_info: &AccountInfo<'a>,
|
||||||
|
signer_seeds: &[&[u8]],
|
||||||
|
reservations: Vec<Reservation>,
|
||||||
|
total_reservation_spots: Option<u64>,
|
||||||
|
offset: u64,
|
||||||
|
total_spot_offset: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
|
invoke_signed(
|
||||||
|
&set_reservation_list(
|
||||||
|
*program_id,
|
||||||
|
*master_edition_info.key,
|
||||||
|
*reservation_list_info.key,
|
||||||
|
*auction_manager_info.key,
|
||||||
|
reservations,
|
||||||
|
total_reservation_spots,
|
||||||
|
offset,
|
||||||
|
total_spot_offset,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
master_edition_info.clone(),
|
||||||
|
reservation_list_info.clone(),
|
||||||
|
auction_manager_info.clone(),
|
||||||
|
],
|
||||||
|
&[&signer_seeds],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calc_spots(
|
||||||
|
winning_config_item: &WinningConfigItem,
|
||||||
|
auction_manager: &AuctionManager,
|
||||||
|
n: usize,
|
||||||
|
) -> u64 {
|
||||||
|
auction_manager.settings.winning_configs[n]
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.filter(|i| i.safety_deposit_box_index == winning_config_item.safety_deposit_box_index)
|
||||||
|
.map(|i| i.amount as u64)
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn reserve_list_if_needed<'a>(
|
pub fn reserve_list_if_needed<'a>(
|
||||||
program_id: &'a Pubkey,
|
program_id: &'a Pubkey,
|
||||||
auction_manager: &AuctionManager,
|
auction_manager: &AuctionManager,
|
||||||
auction: &AuctionData,
|
auction: &AuctionData,
|
||||||
winning_config_item: &WinningConfigItem,
|
winning_config_item: &WinningConfigItem,
|
||||||
|
winning_index: usize,
|
||||||
|
bidder_info: &AccountInfo<'a>,
|
||||||
master_edition_info: &AccountInfo<'a>,
|
master_edition_info: &AccountInfo<'a>,
|
||||||
reservation_list_info: &AccountInfo<'a>,
|
reservation_list_info: &AccountInfo<'a>,
|
||||||
auction_manager_info: &AccountInfo<'a>,
|
auction_manager_info: &AccountInfo<'a>,
|
||||||
|
@ -34,57 +82,56 @@ pub fn reserve_list_if_needed<'a>(
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
let reservation_list = get_reservation_list(reservation_list_info)?;
|
let reservation_list = get_reservation_list(reservation_list_info)?;
|
||||||
|
|
||||||
if reservation_list.supply_snapshot().is_none() {
|
let total_reservation_spot_opt: Option<u64>;
|
||||||
let mut reservations: Vec<Reservation> = vec![];
|
|
||||||
|
|
||||||
// Auction specifically does not expose internal state workings as it may change someday,
|
// Auction specifically does not expose internal state workings as it may change someday,
|
||||||
// but it does expose a point get-winner-at-index method. Right now this is just array access
|
// but it does expose a point get-winner-at-index method. Right now this is just array access
|
||||||
// but may be invocation someday. It's inefficient style but better for the interface maintenance
|
// but may be invocation someday. It's inefficient style but better for the interface maintenance
|
||||||
// in the long run if we move to better storage solutions (so that this action doesnt need to change if
|
// in the long run if we move to better storage solutions (so that this action doesnt need to change if
|
||||||
// storage does.)
|
// storage does.)
|
||||||
|
|
||||||
for n in 0..auction_manager.settings.winning_configs.len() {
|
let mut total_reservation_spots: u64 = 0;
|
||||||
match auction.winner_at(n) {
|
let mut total_spot_offset: u64 = 0;
|
||||||
Some(address) => {
|
for n in 0..auction_manager.settings.winning_configs.len() {
|
||||||
let spots: u64 = auction_manager.settings.winning_configs[n]
|
match auction.winner_at(n) {
|
||||||
.items
|
Some(_) => {
|
||||||
.iter()
|
let spots: u64 = calc_spots(winning_config_item, auction_manager, n);
|
||||||
.filter(|i| {
|
total_reservation_spots = total_reservation_spots
|
||||||
i.safety_deposit_box_index
|
.checked_add(spots)
|
||||||
== winning_config_item.safety_deposit_box_index
|
.ok_or(MetaplexError::NumericalOverflowError)?;
|
||||||
})
|
if n < winning_index {
|
||||||
.map(|i| i.amount as u64)
|
total_spot_offset = total_spot_offset
|
||||||
.sum();
|
.checked_add(spots)
|
||||||
reservations.push(Reservation {
|
.ok_or(MetaplexError::NumericalOverflowError)?;
|
||||||
address,
|
|
||||||
// Select all items in a winning config matching the same safety deposit box
|
|
||||||
// as the one being redeemed here (likely only one)
|
|
||||||
// and then sum them to get the total spots to reserve for this winner
|
|
||||||
spots_remaining: spots,
|
|
||||||
total_spots: spots,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
None => break,
|
|
||||||
}
|
}
|
||||||
|
None => break,
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke_signed(
|
|
||||||
&set_reservation_list(
|
|
||||||
*program_id,
|
|
||||||
*master_edition_info.key,
|
|
||||||
*reservation_list_info.key,
|
|
||||||
*auction_manager_info.key,
|
|
||||||
reservations,
|
|
||||||
),
|
|
||||||
&[
|
|
||||||
master_edition_info.clone(),
|
|
||||||
reservation_list_info.clone(),
|
|
||||||
auction_manager_info.clone(),
|
|
||||||
],
|
|
||||||
&[&signer_seeds],
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reservation_list.supply_snapshot().is_none() {
|
||||||
|
total_reservation_spot_opt = Some(total_reservation_spots)
|
||||||
|
} else {
|
||||||
|
total_reservation_spot_opt = None
|
||||||
|
}
|
||||||
|
|
||||||
|
let my_spots: u64 = calc_spots(winning_config_item, auction_manager, winning_index);
|
||||||
|
set_reservation_list_wrapper(
|
||||||
|
program_id,
|
||||||
|
master_edition_info,
|
||||||
|
reservation_list_info,
|
||||||
|
auction_manager_info,
|
||||||
|
signer_seeds,
|
||||||
|
vec![Reservation {
|
||||||
|
address: *bidder_info.key,
|
||||||
|
spots_remaining: my_spots,
|
||||||
|
total_spots: my_spots,
|
||||||
|
}],
|
||||||
|
total_reservation_spot_opt,
|
||||||
|
winning_index as u64,
|
||||||
|
total_spot_offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn process_redeem_bid<'a>(
|
pub fn process_redeem_bid<'a>(
|
||||||
|
@ -186,6 +233,8 @@ pub fn process_redeem_bid<'a>(
|
||||||
&auction_manager,
|
&auction_manager,
|
||||||
&auction,
|
&auction,
|
||||||
&winning_config_item,
|
&winning_config_item,
|
||||||
|
winning_index,
|
||||||
|
bidder_info,
|
||||||
master_edition_info,
|
master_edition_info,
|
||||||
reservation_list_info,
|
reservation_list_info,
|
||||||
auction_manager_info,
|
auction_manager_info,
|
||||||
|
|
|
@ -266,6 +266,18 @@ pub enum MetadataError {
|
||||||
/// Data type mismatch
|
/// Data type mismatch
|
||||||
#[error("Data type mismatch")]
|
#[error("Data type mismatch")]
|
||||||
DataTypeMismatch,
|
DataTypeMismatch,
|
||||||
|
|
||||||
|
/// Beyond alotted address size in reservation!
|
||||||
|
#[error("Beyond alotted address size in reservation!")]
|
||||||
|
BeyondAlottedAddressSize,
|
||||||
|
|
||||||
|
/// The reservation has only been partially alotted
|
||||||
|
#[error("The reservation has only been partially alotted")]
|
||||||
|
ReservationNotComplete,
|
||||||
|
|
||||||
|
/// You cannot splice over an existing reservation!
|
||||||
|
#[error("You cannot splice over an existing reservation!")]
|
||||||
|
TriedToReplaceAnExistingReservation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrintProgramError for MetadataError {
|
impl PrintProgramError for MetadataError {
|
||||||
|
|
|
@ -45,6 +45,15 @@ pub struct MintPrintingTokensViaTokenArgs {
|
||||||
pub struct SetReservationListArgs {
|
pub struct SetReservationListArgs {
|
||||||
/// If set, means that no more than this number of editions can ever be minted. This is immutable.
|
/// If set, means that no more than this number of editions can ever be minted. This is immutable.
|
||||||
pub reservations: Vec<Reservation>,
|
pub reservations: Vec<Reservation>,
|
||||||
|
/// should only be present on the very first call to set reservation list.
|
||||||
|
pub total_reservation_spots: Option<u64>,
|
||||||
|
/// Where in the reservation list you want to insert this slice of reservations
|
||||||
|
pub offset: u64,
|
||||||
|
/// What the total spot offset is in the reservation list from the beginning to your slice of reservations.
|
||||||
|
/// So if is going to be 4 total editions eventually reserved between your slice and the beginning of the array,
|
||||||
|
/// split between 2 reservation entries, the offset variable above would be "2" since you start at entry 2 in 0 indexed array
|
||||||
|
/// (first 2 taking 0 and 1) and because they each have 2 spots taken, this variable would be 4.
|
||||||
|
pub total_spot_offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instructions supported by the Metadata program.
|
/// Instructions supported by the Metadata program.
|
||||||
|
@ -124,9 +133,13 @@ pub enum MetadataInstruction {
|
||||||
/// with the pda that was created by that first bidder - the token metadata can then cross reference
|
/// with the pda that was created by that first bidder - the token metadata can then cross reference
|
||||||
/// these people with the list and see that bidder A gets edition #2, so on and so forth.
|
/// these people with the list and see that bidder A gets edition #2, so on and so forth.
|
||||||
///
|
///
|
||||||
|
/// NOTE: If you have more than 20 addresses in a reservation list, this may be called multiple times to build up the list,
|
||||||
|
/// otherwise, it simply wont fit in one transaction. Only provide a total_reservation argument on the first call, which will
|
||||||
|
/// allocate the edition space, and in follow up calls this will specifically be unnecessary (and indeed will error.)
|
||||||
|
///
|
||||||
/// 0. `[writable]` Master Edition key (pda of ['metadata', program id, mint id, 'edition'])
|
/// 0. `[writable]` Master Edition key (pda of ['metadata', program id, mint id, 'edition'])
|
||||||
/// 1. `[writable]` PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key]
|
/// 1. `[writable]` PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key]
|
||||||
/// 3. `[signer]` The resource you tied the reservation list too
|
/// 2. `[signer]` The resource you tied the reservation list too
|
||||||
SetReservationList(SetReservationListArgs),
|
SetReservationList(SetReservationListArgs),
|
||||||
|
|
||||||
/// Create an empty reservation list for a resource who can come back later as a signer and fill the reservation list
|
/// Create an empty reservation list for a resource who can come back later as a signer and fill the reservation list
|
||||||
|
@ -363,6 +376,9 @@ pub fn set_reservation_list(
|
||||||
reservation_list: Pubkey,
|
reservation_list: Pubkey,
|
||||||
resource: Pubkey,
|
resource: Pubkey,
|
||||||
reservations: Vec<Reservation>,
|
reservations: Vec<Reservation>,
|
||||||
|
total_reservation_spots: Option<u64>,
|
||||||
|
offset: u64,
|
||||||
|
total_spot_offset: u64,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
Instruction {
|
Instruction {
|
||||||
program_id,
|
program_id,
|
||||||
|
@ -371,9 +387,14 @@ pub fn set_reservation_list(
|
||||||
AccountMeta::new(reservation_list, false),
|
AccountMeta::new(reservation_list, false),
|
||||||
AccountMeta::new_readonly(resource, true),
|
AccountMeta::new_readonly(resource, true),
|
||||||
],
|
],
|
||||||
data: MetadataInstruction::SetReservationList(SetReservationListArgs { reservations })
|
data: MetadataInstruction::SetReservationList(SetReservationListArgs {
|
||||||
.try_to_vec()
|
reservations,
|
||||||
.unwrap(),
|
total_reservation_spots,
|
||||||
|
offset,
|
||||||
|
total_spot_offset,
|
||||||
|
})
|
||||||
|
.try_to_vec()
|
||||||
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,14 @@ pub fn process_instruction(
|
||||||
}
|
}
|
||||||
MetadataInstruction::SetReservationList(args) => {
|
MetadataInstruction::SetReservationList(args) => {
|
||||||
msg!("Instruction: Set Reservation List");
|
msg!("Instruction: Set Reservation List");
|
||||||
process_set_reservation_list(program_id, accounts, args.reservations)
|
process_set_reservation_list(
|
||||||
|
program_id,
|
||||||
|
accounts,
|
||||||
|
args.reservations,
|
||||||
|
args.total_reservation_spots,
|
||||||
|
args.offset,
|
||||||
|
args.total_spot_offset,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
MetadataInstruction::CreateReservationList => {
|
MetadataInstruction::CreateReservationList => {
|
||||||
msg!("Instruction: Create Reservation List");
|
msg!("Instruction: Create Reservation List");
|
||||||
|
@ -541,6 +548,9 @@ pub fn process_set_reservation_list(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
reservations: Vec<Reservation>,
|
reservations: Vec<Reservation>,
|
||||||
|
total_reservation_spots: Option<u64>,
|
||||||
|
offset: u64,
|
||||||
|
total_spot_offset: u64,
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
let account_info_iter = &mut accounts.iter();
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
@ -558,10 +568,6 @@ pub fn process_set_reservation_list(
|
||||||
return Err(MetadataError::ReservationDoesNotExist.into());
|
return Err(MetadataError::ReservationDoesNotExist.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if reservations.len() > MAX_RESERVATIONS {
|
|
||||||
return Err(MetadataError::BeyondMaxAddressSize.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_derivation(
|
assert_derivation(
|
||||||
program_id,
|
program_id,
|
||||||
reservation_list_info,
|
reservation_list_info,
|
||||||
|
@ -576,12 +582,12 @@ pub fn process_set_reservation_list(
|
||||||
|
|
||||||
let mut reservation_list = get_reservation_list(reservation_list_info)?;
|
let mut reservation_list = get_reservation_list(reservation_list_info)?;
|
||||||
|
|
||||||
if reservation_list.supply_snapshot().is_some() {
|
if reservation_list.supply_snapshot().is_some() && total_reservation_spots.is_some() {
|
||||||
return Err(MetadataError::ReservationAlreadyMade.into());
|
return Err(MetadataError::ReservationAlreadyMade.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut total_len: u64 = 0;
|
let mut total_len: u64 = reservation_list.current_reservation_spots();
|
||||||
let mut total_len_check: u64 = 0;
|
let mut total_len_check: u64 = reservation_list.current_reservation_spots();
|
||||||
|
|
||||||
for reservation in &reservations {
|
for reservation in &reservations {
|
||||||
total_len = total_len
|
total_len = total_len
|
||||||
|
@ -596,28 +602,39 @@ pub fn process_set_reservation_list(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reservation_list.set_current_reservation_spots(total_len);
|
||||||
|
|
||||||
|
reservation_list.add_reservations(reservations, offset, total_spot_offset)?;
|
||||||
|
|
||||||
|
if let Some(total) = total_reservation_spots {
|
||||||
|
reservation_list.set_supply_snapshot(Some(master_edition.supply));
|
||||||
|
reservation_list.set_total_reservation_spots(total);
|
||||||
|
master_edition.supply = master_edition
|
||||||
|
.supply
|
||||||
|
.checked_add(total as u64)
|
||||||
|
.ok_or(MetadataError::NumericalOverflowError)?;
|
||||||
|
|
||||||
|
if let Some(max_supply) = master_edition.max_supply {
|
||||||
|
if master_edition.supply > max_supply {
|
||||||
|
return Err(MetadataError::ReservationBreachesMaximumSupply.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?;
|
||||||
|
}
|
||||||
|
|
||||||
if total_len_check != total_len {
|
if total_len_check != total_len {
|
||||||
return Err(MetadataError::SpotMismatch.into());
|
return Err(MetadataError::SpotMismatch.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
reservation_list.set_supply_snapshot(Some(master_edition.supply));
|
if total_len > reservation_list.total_reservation_spots() {
|
||||||
reservation_list.set_reservations(reservations);
|
return Err(MetadataError::BeyondAlottedAddressSize.into());
|
||||||
msg!("Master edition {:?}", master_edition);
|
};
|
||||||
msg!("Total new spots {:?}", total_len);
|
|
||||||
master_edition.supply = master_edition
|
|
||||||
.supply
|
|
||||||
.checked_add(total_len as u64)
|
|
||||||
.ok_or(MetadataError::NumericalOverflowError)?;
|
|
||||||
|
|
||||||
if let Some(max_supply) = master_edition.max_supply {
|
if reservation_list.reservations().len() > MAX_RESERVATIONS {
|
||||||
if master_edition.supply > max_supply {
|
return Err(MetadataError::BeyondMaxAddressSize.into());
|
||||||
return Err(MetadataError::ReservationBreachesMaximumSupply.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reservation_list.save(reservation_list_info)?;
|
reservation_list.save(reservation_list_info)?;
|
||||||
master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub const MAX_RESERVATIONS: usize = 200;
|
||||||
pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100;
|
pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100;
|
||||||
|
|
||||||
// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec
|
// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec
|
||||||
pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 100;
|
pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 8 + 84;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||||
|
@ -170,9 +170,19 @@ pub trait ReservationList {
|
||||||
fn master_edition(&self) -> Pubkey;
|
fn master_edition(&self) -> Pubkey;
|
||||||
fn supply_snapshot(&self) -> Option<u64>;
|
fn supply_snapshot(&self) -> Option<u64>;
|
||||||
fn reservations(&self) -> Vec<Reservation>;
|
fn reservations(&self) -> Vec<Reservation>;
|
||||||
|
fn total_reservation_spots(&self) -> u64;
|
||||||
|
fn current_reservation_spots(&self) -> u64;
|
||||||
fn set_master_edition(&mut self, key: Pubkey);
|
fn set_master_edition(&mut self, key: Pubkey);
|
||||||
fn set_supply_snapshot(&mut self, supply: Option<u64>);
|
fn set_supply_snapshot(&mut self, supply: Option<u64>);
|
||||||
fn set_reservations(&mut self, reservations: Vec<Reservation>);
|
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult;
|
||||||
|
fn add_reservations(
|
||||||
|
&mut self,
|
||||||
|
reservations: Vec<Reservation>,
|
||||||
|
offset: u64,
|
||||||
|
total_spot_offset: u64,
|
||||||
|
) -> ProgramResult;
|
||||||
|
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64);
|
||||||
|
fn set_current_reservation_spots(&mut self, current_reservation_spots: u64);
|
||||||
fn save(&self, account: &AccountInfo) -> ProgramResult;
|
fn save(&self, account: &AccountInfo) -> ProgramResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +209,10 @@ pub struct ReservationListV2 {
|
||||||
/// What supply counter was on master_edition when this reservation was created.
|
/// What supply counter was on master_edition when this reservation was created.
|
||||||
pub supply_snapshot: Option<u64>,
|
pub supply_snapshot: Option<u64>,
|
||||||
pub reservations: Vec<Reservation>,
|
pub reservations: Vec<Reservation>,
|
||||||
|
/// How many reservations there are going to be, given on first set_reservation call
|
||||||
|
pub total_reservation_spots: u64,
|
||||||
|
/// Cached count of reservation spots in the reservation vec to save on CPU.
|
||||||
|
pub current_reservation_spots: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReservationList for ReservationListV2 {
|
impl ReservationList for ReservationListV2 {
|
||||||
|
@ -222,14 +236,74 @@ impl ReservationList for ReservationListV2 {
|
||||||
self.supply_snapshot = supply;
|
self.supply_snapshot = supply;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
|
fn add_reservations(
|
||||||
self.reservations = reservations
|
&mut self,
|
||||||
|
mut reservations: Vec<Reservation>,
|
||||||
|
offset: u64,
|
||||||
|
total_spot_offset: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let usize_offset = offset as usize;
|
||||||
|
while self.reservations.len() < usize_offset {
|
||||||
|
self.reservations.push(Reservation {
|
||||||
|
address: solana_program::system_program::id(),
|
||||||
|
spots_remaining: 0,
|
||||||
|
total_spots: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if self.reservations.len() > usize_offset {
|
||||||
|
let removed_elements: Vec<Reservation> = self
|
||||||
|
.reservations
|
||||||
|
.splice(
|
||||||
|
usize_offset..usize_offset + reservations.len(),
|
||||||
|
reservations,
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
let existing_res = removed_elements
|
||||||
|
.iter()
|
||||||
|
.find(|r| r.address != solana_program::system_program::id());
|
||||||
|
if existing_res.is_some() {
|
||||||
|
return Err(MetadataError::TriedToReplaceAnExistingReservation.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.reservations.append(&mut reservations)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usize_offset != 0
|
||||||
|
&& self.reservations[usize_offset - 1].address == solana_program::system_program::id()
|
||||||
|
{
|
||||||
|
// This becomes an anchor then for calculations... put total spot offset in here.
|
||||||
|
self.reservations[usize_offset - 1].spots_remaining = total_spot_offset;
|
||||||
|
self.reservations[usize_offset - 1].total_spots = total_spot_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
|
||||||
|
self.reservations = reservations;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self, account: &AccountInfo) -> ProgramResult {
|
fn save(&self, account: &AccountInfo) -> ProgramResult {
|
||||||
self.serialize(&mut *account.data.borrow_mut())?;
|
self.serialize(&mut *account.data.borrow_mut())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn total_reservation_spots(&self) -> u64 {
|
||||||
|
self.total_reservation_spots
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64) {
|
||||||
|
self.total_reservation_spots = total_reservation_spots;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_reservation_spots(&self) -> u64 {
|
||||||
|
self.current_reservation_spots
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_reservation_spots(&mut self, current_reservation_spots: u64) {
|
||||||
|
self.current_reservation_spots = current_reservation_spots;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReservationListV2 {
|
impl ReservationListV2 {
|
||||||
|
@ -293,7 +367,12 @@ impl ReservationList for ReservationListV1 {
|
||||||
self.supply_snapshot = supply;
|
self.supply_snapshot = supply;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
|
fn add_reservations(
|
||||||
|
&mut self,
|
||||||
|
reservations: Vec<Reservation>,
|
||||||
|
_: u64,
|
||||||
|
_: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
self.reservations = reservations
|
self.reservations = reservations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| ReservationV1 {
|
.map(|r| ReservationV1 {
|
||||||
|
@ -302,12 +381,31 @@ impl ReservationList for ReservationListV1 {
|
||||||
total_spots: r.total_spots as u8,
|
total_spots: r.total_spots as u8,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
|
||||||
|
self.add_reservations(reservations, 0, 0)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self, account: &AccountInfo) -> ProgramResult {
|
fn save(&self, account: &AccountInfo) -> ProgramResult {
|
||||||
self.serialize(&mut *account.data.borrow_mut())?;
|
self.serialize(&mut *account.data.borrow_mut())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn total_reservation_spots(&self) -> u64 {
|
||||||
|
self.reservations.iter().map(|r| r.total_spots as u64).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_total_reservation_spots(&mut self, _: u64) {}
|
||||||
|
|
||||||
|
fn current_reservation_spots(&self) -> u64 {
|
||||||
|
self.reservations.iter().map(|r| r.total_spots as u64).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_reservation_spots(&mut self, _: u64) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReservationListV1 {
|
impl ReservationListV1 {
|
||||||
|
|
|
@ -424,6 +424,7 @@ pub fn mint_limited_edition<'a>(
|
||||||
let mut reservations = reservation_list.reservations();
|
let mut reservations = reservation_list.reservations();
|
||||||
for i in 0..reservations.len() {
|
for i in 0..reservations.len() {
|
||||||
let mut reservation = &mut reservations[i];
|
let mut reservation = &mut reservations[i];
|
||||||
|
|
||||||
if reservation.address == *mint_authority_info.key {
|
if reservation.address == *mint_authority_info.key {
|
||||||
offset = Some(
|
offset = Some(
|
||||||
prev_total_offsets
|
prev_total_offsets
|
||||||
|
@ -436,13 +437,21 @@ pub fn mint_limited_edition<'a>(
|
||||||
.checked_sub(1)
|
.checked_sub(1)
|
||||||
.ok_or(MetadataError::NumericalOverflowError)?;
|
.ok_or(MetadataError::NumericalOverflowError)?;
|
||||||
|
|
||||||
reservation_list.set_reservations(reservations);
|
reservation_list.set_reservations(reservations)?;
|
||||||
reservation_list.save(account)?;
|
reservation_list.save(account)?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
prev_total_offsets = prev_total_offsets
|
|
||||||
.checked_add(reservation.total_spots)
|
if reservation.address == solana_program::system_program::id() {
|
||||||
.ok_or(MetadataError::NumericalOverflowError)?;
|
// This is an anchor point in the array...it means we reset our math to
|
||||||
|
// this offset because we may be missing information in between this point and
|
||||||
|
// the points before it.
|
||||||
|
prev_total_offsets = reservation.total_spots;
|
||||||
|
} else {
|
||||||
|
prev_total_offsets = prev_total_offsets
|
||||||
|
.checked_add(reservation.total_spots)
|
||||||
|
.ok_or(MetadataError::NumericalOverflowError)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match offset {
|
match offset {
|
||||||
|
|
|
@ -5,7 +5,9 @@ use {
|
||||||
input_validators::{is_url, is_valid_pubkey, is_valid_signer},
|
input_validators::{is_url, is_valid_pubkey, is_valid_signer},
|
||||||
},
|
},
|
||||||
solana_client::rpc_client::RpcClient,
|
solana_client::rpc_client::RpcClient,
|
||||||
solana_program::{borsh::try_from_slice_unchecked, program_pack::Pack},
|
solana_program::{
|
||||||
|
account_info::AccountInfo, borsh::try_from_slice_unchecked, program_pack::Pack,
|
||||||
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{read_keypair_file, Keypair, Signer},
|
signature::{read_keypair_file, Keypair, Signer},
|
||||||
|
@ -22,7 +24,9 @@ use {
|
||||||
mint_new_edition_from_master_edition_via_token, mint_printing_tokens,
|
mint_new_edition_from_master_edition_via_token, mint_printing_tokens,
|
||||||
update_metadata_accounts,
|
update_metadata_accounts,
|
||||||
},
|
},
|
||||||
state::{Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX},
|
state::{
|
||||||
|
get_reservation_list, Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::str::FromStr,
|
std::str::FromStr,
|
||||||
};
|
};
|
||||||
|
@ -79,6 +83,30 @@ fn mint_coins(app_matches: &ArgMatches, payer: Keypair, client: RpcClient) {
|
||||||
|
|
||||||
println!("Minted {:?} tokens to {:?}.", amount, destination_key);
|
println!("Minted {:?} tokens to {:?}.", amount, destination_key);
|
||||||
}
|
}
|
||||||
|
fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {
|
||||||
|
let key = pubkey_of(app_matches, "key").unwrap();
|
||||||
|
let mut res_data = client.get_account(&key).unwrap();
|
||||||
|
let mut lamports = 0;
|
||||||
|
let account_info = AccountInfo::new(
|
||||||
|
&key,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
&mut lamports,
|
||||||
|
&mut res_data.data,
|
||||||
|
&res_data.owner,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res_list = get_reservation_list(&account_info).unwrap();
|
||||||
|
println!("Res list {:?}", res_list.reservations());
|
||||||
|
println!(
|
||||||
|
"current res spots: {:?}",
|
||||||
|
res_list.current_reservation_spots()
|
||||||
|
);
|
||||||
|
println!("total res spots: {:?}", res_list.total_reservation_spots());
|
||||||
|
println!("supply snapshot: {:?}", res_list.supply_snapshot());
|
||||||
|
}
|
||||||
|
|
||||||
fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {
|
fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {
|
||||||
let program_key = spl_token_metadata::id();
|
let program_key = spl_token_metadata::id();
|
||||||
|
@ -735,6 +763,18 @@ fn main() {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Metadata mint"),
|
.help("Metadata mint"),
|
||||||
)
|
)
|
||||||
|
).subcommand(
|
||||||
|
SubCommand::with_name("show_reservation_list")
|
||||||
|
.about("Show Reservation List")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("key")
|
||||||
|
.long("key")
|
||||||
|
.value_name("KEY")
|
||||||
|
.required(true)
|
||||||
|
.validator(is_valid_pubkey)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Account key of reservation list"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("create_master_edition")
|
SubCommand::with_name("create_master_edition")
|
||||||
|
@ -843,6 +883,9 @@ fn main() {
|
||||||
("show", Some(arg_matches)) => {
|
("show", Some(arg_matches)) => {
|
||||||
show(arg_matches, payer, client);
|
show(arg_matches, payer, client);
|
||||||
}
|
}
|
||||||
|
("show_reservation_list", Some(arg_matches)) => {
|
||||||
|
show_reservation_list(arg_matches, payer, client);
|
||||||
|
}
|
||||||
("mint_coins", Some(arg_matches)) => {
|
("mint_coins", Some(arg_matches)) => {
|
||||||
mint_coins(arg_matches, payer, client);
|
mint_coins(arg_matches, payer, client);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue