Working on instant sale

This commit is contained in:
Jordan Prince 2021-08-13 20:09:34 -05:00
parent b4f5b4b29b
commit bf22676c2c
8 changed files with 246 additions and 69 deletions

View File

@ -373,10 +373,14 @@ export interface IPartialCreateAuctionArgs {
tickSize: BN | null;
gapTickSizePercentage: number | null;
instantSalePrice: BN | null;
name: number[] | null;
}
export class CreateAuctionArgs implements IPartialCreateAuctionArgs {
instruction: number = 1;
instruction: number = 7;
/// 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.
@ -396,6 +400,10 @@ export class CreateAuctionArgs implements IPartialCreateAuctionArgs {
gapTickSizePercentage: number | null;
instantSalePrice: BN | null;
name: number[] | null;
constructor(args: {
winners: WinnerLimit;
endAuctionAt: BN | null;
@ -406,6 +414,8 @@ export class CreateAuctionArgs implements IPartialCreateAuctionArgs {
priceFloor: PriceFloor;
tickSize: BN | null;
gapTickSizePercentage: number | null;
name: number[] | null;
instantSalePrice: BN | null;
}) {
this.winners = args.winners;
this.endAuctionAt = args.endAuctionAt;
@ -416,6 +426,8 @@ export class CreateAuctionArgs implements IPartialCreateAuctionArgs {
this.priceFloor = args.priceFloor;
this.tickSize = args.tickSize;
this.gapTickSizePercentage = args.gapTickSizePercentage;
this.name = args.name;
this.instantSalePrice = args.instantSalePrice;
}
}
@ -468,6 +480,8 @@ export const AUCTION_SCHEMA = new Map<any, any>([
['priceFloor', PriceFloor],
['tickSize', { kind: 'option', type: 'u64' }],
['gapTickSizePercentage', { kind: 'option', type: 'u8' }],
['instantSalePrice', { kind: 'option', type: 'u64' }],
['name', { kind: 'option', type: [32] }],
],
},
],
@ -545,6 +559,8 @@ export const AUCTION_SCHEMA = new Map<any, any>([
['totalUncancelledBids', 'u64'],
['tickSize', { kind: 'option', type: 'u64' }],
['gapTickSizePercentage', { kind: 'option', type: 'u8' }],
['instantSalePrice', { kind: 'option', type: 'u64' }],
['name', { kind: 'option', type: [32] }],
],
},
],

View File

@ -237,7 +237,20 @@ export class Metadata {
}
public async init() {
const edition = await getEdition(this.mint);
const edition: PublicKey;
if (this.info.editionNonce != null) {
edition = await PublicKey.createProgramAddress(
[
Buffer.from(METADATA_PREFIX),
METADATA_PROGRAM_ID.toBuffer(),
tempCache.metadata[i].info.mint.toBuffer(),
new Uint8Array([tempCache.metadata[i].info.editionNonce || 0]),
],
METADATA_PROGRAM_ID,
);
} else {
edition = await getEdition(tempCache.metadata[i].info.mint);
}
this.edition = edition;
this.masterEdition = edition;
}

View File

@ -0,0 +1,75 @@
import { getAuctionExtended, programIds } from '@oyster/common';
import {
PublicKey,
SYSVAR_CLOCK_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { serialize } from 'borsh';
import { EndAuctionArgs, getAuctionKeys, SCHEMA } from '.';
export async function endAuction(
vault: PublicKey,
auctionManagerAuthority: PublicKey,
instructions: TransactionInstruction[],
) {
const PROGRAM_IDS = programIds();
const store = PROGRAM_IDS.store;
if (!store) {
throw new Error('Store not initialized');
}
const { auctionKey, auctionManagerKey } = await getAuctionKeys(vault);
const auctionExtended = await getAuctionExtended({
auctionProgramId: PROGRAM_IDS.auction,
resource: vault,
});
const value = new EndAuctionArgs({ reveal: null });
const data = Buffer.from(serialize(SCHEMA, value));
const keys = [
{
pubkey: auctionManagerKey,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionExtended,
isSigner: false,
isWritable: false,
},
{
pubkey: auctionManagerAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: store,
isSigner: false,
isWritable: false,
},
{
pubkey: PROGRAM_IDS.auction,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_CLOCK_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: PROGRAM_IDS.metaplex,
data,
}),
);
}

View File

@ -320,6 +320,15 @@ export class RedeemFullRightsTransferBidArgs {
export class StartAuctionArgs {
instruction = 5;
}
export class EndAuctionArgs {
instruction = 21;
reveal: BN[] | null;
constructor(args: { reveal: BN[] | null }) {
this.reveal = args.reveal;
}
}
export class ClaimBidArgs {
instruction = 6;
}
@ -953,6 +962,16 @@ export const SCHEMA = new Map<any, any>([
fields: [['instruction', 'u8']],
},
],
[
EndAuctionArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['reveal', { kind: 'option', type: [BN] }],
],
},
],
[
ClaimBidArgs,
{

View File

@ -1,4 +1,9 @@
import { findProgramAddress, programIds, VAULT_PREFIX } from '@oyster/common';
import {
findProgramAddress,
getAuctionExtended,
programIds,
VAULT_PREFIX,
} from '@oyster/common';
import {
PublicKey,
SystemProgram,
@ -63,6 +68,11 @@ export async function redeemBid(
safetyDeposit,
);
const auctionExtended = await getAuctionExtended({
auctionProgramId: PROGRAM_IDS.auction,
resource: vault,
});
const value =
auctioneerReclaimIndex !== undefined
? new RedeemUnusedWinningConfigItemsAsAuctioneerArgs({
@ -167,6 +177,11 @@ export async function redeemBid(
isSigner: false,
isWritable: false,
},
{
pubkey: auctionExtended,
isSigner: false,
isWritable: false,
},
];
if (isPrintingType && masterEdition && reservationList) {

View File

@ -1,4 +1,9 @@
import { programIds, VAULT_PREFIX, findProgramAddress } from '@oyster/common';
import {
programIds,
VAULT_PREFIX,
findProgramAddress,
getAuctionExtended,
} from '@oyster/common';
import {
PublicKey,
SystemProgram,
@ -62,6 +67,11 @@ export async function redeemFullRightsTransferBid(
safetyDeposit,
);
const auctionExtended = await getAuctionExtended({
auctionProgramId: PROGRAM_IDS.auction,
resource: vault,
});
const value =
auctioneerReclaimIndex !== undefined
? new RedeemUnusedWinningConfigItemsAsAuctioneerArgs({
@ -176,6 +186,12 @@ export async function redeemFullRightsTransferBid(
isSigner: false,
isWritable: false,
},
{
pubkey: auctionExtended,
isSigner: false,
isWritable: false,
},
];
instructions.push(

View File

@ -36,14 +36,7 @@ import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { MintLayout } from '@solana/spl-token';
import { useHistory, useParams } from 'react-router-dom';
import { capitalize } from 'lodash';
import {
WinningConfigType,
NonWinningConstraint,
WinningConstraint,
ParticipationConfigV2,
SafetyDepositConfig,
AmountRange,
} from '../../models/metaplex';
import { WinningConfigType, AmountRange } from '../../models/metaplex';
import moment from 'moment';
import {
createAuctionManager,
@ -124,6 +117,8 @@ export interface AuctionState {
tiers?: Array<Tier>;
winnersCount: number;
instantSalePrice?: number;
}
export const AuctionCreateView = () => {
@ -413,6 +408,10 @@ export const AuctionCreateView = () => {
tickSize: attributes.priceTick
? new BN(attributes.priceTick * LAMPORTS_PER_SOL)
: null,
instantSalePrice: attributes.instantSalePrice
? new BN((attributes.instantSalePrice || 0) * LAMPORTS_PER_SOL)
: null,
name: null,
};
const _auctionObj = await createAuctionManager(
@ -856,6 +855,22 @@ const SaleTypeStep = (props: {
Allow bidding on your NFT(s).
</div>
</Radio.Group>
<Radio.Group
defaultValue={props.attributes.saleType}
onChange={info =>
props.setAttributes({
...props.attributes,
saleType: info.target.value,
})
}
>
<Radio className="radio-field" value="auction">
Instant Sale
</Radio>
<div className="radio-subtitle">
Instant purchase and redemption of your NFT.
</div>
</Radio.Group>
</label>
</Col>
</Row>
@ -898,13 +913,13 @@ const PriceSale = (props: {
<>
<Row className="call-to-action">
<h2>Price</h2>
<p>Set the price for your auction.</p>
<p>Set the fixed price for your instant sale.</p>
</Row>
<Row className="content-action">
<label className="action-field">
<span className="field-title">Sale price</span>
<span className="field-info">
This is the starting bid price for your auction.
This is the price of purchasing the item(s).
</span>
<Input
type="number"
@ -917,7 +932,8 @@ const PriceSale = (props: {
onChange={info =>
props.setAttributes({
...props.attributes,
price: parseFloat(info.target.value) || undefined,
priceFloor: parseFloat(info.target.value),
instantSalePrice: parseFloat(info.target.value),
})
}
/>
@ -1228,7 +1244,10 @@ const EndingPhaseAuction = (props: {
<Row className="content-action">
<Col className="section" xl={24}>
<div className="action-field">
<span className="field-title">Auction Duration</span>
<span className="field-title">
{props.attributes.saleType == 'auction' ? 'Auction' : 'Sale'}{' '}
Duration
</span>
<span className="field-info">
This is how long the auction will last for.
</span>
@ -1261,61 +1280,65 @@ const EndingPhaseAuction = (props: {
/>
</div>
<div className="action-field">
<span className="field-title">Gap Time</span>
<span className="field-info">
The final phase of the auction will begin when there is this much
time left on the countdown. Any bids placed during the final phase
will extend the end time by this same duration.
</span>
<Input
addonAfter={
<Select
defaultValue={props.attributes.gapTimeType}
onChange={value =>
{props.attributes.saleType == 'auction' && (
<>
<div className="action-field">
<span className="field-title">Gap Time</span>
<span className="field-info">
The final phase of the auction will begin when there is this
much time left on the countdown. Any bids placed during the
final phase will extend the end time by this same duration.
</span>
<Input
addonAfter={
<Select
defaultValue={props.attributes.gapTimeType}
onChange={value =>
props.setAttributes({
...props.attributes,
gapTimeType: value,
})
}
>
<Option value="minutes">Minutes</Option>
<Option value="hours">Hours</Option>
<Option value="days">Days</Option>
</Select>
}
type="number"
className="input"
placeholder="Set the gap time"
onChange={info =>
props.setAttributes({
...props.attributes,
gapTimeType: value,
gapTime: parseInt(info.target.value),
})
}
>
<Option value="minutes">Minutes</Option>
<Option value="hours">Hours</Option>
<Option value="days">Days</Option>
</Select>
}
type="number"
className="input"
placeholder="Set the gap time"
onChange={info =>
props.setAttributes({
...props.attributes,
gapTime: parseInt(info.target.value),
})
}
/>
</div>
/>
</div>
<label className="action-field">
<span className="field-title">Tick Size for Ending Phase</span>
<span className="field-info">
In order for winners to move up in the auction, they must place a
bid thats at least this percentage higher than the next highest
bid.
</span>
<Input
type="number"
className="input"
placeholder="Percentage"
suffix="%"
onChange={info =>
props.setAttributes({
...props.attributes,
tickSizeEndingPhase: parseInt(info.target.value),
})
}
/>
</label>
<label className="action-field">
<span className="field-title">Tick Size for Ending Phase</span>
<span className="field-info">
In order for winners to move up in the auction, they must
place a bid thats at least this percentage higher than the
next highest bid.
</span>
<Input
type="number"
className="input"
placeholder="Percentage"
suffix="%"
onChange={info =>
props.setAttributes({
...props.attributes,
tickSizeEndingPhase: parseInt(info.target.value),
})
}
/>
</label>
</>
)}
</Col>
</Row>
<Row>

View File

@ -45,10 +45,10 @@ pub struct CreateAuctionArgsV2 {
pub tick_size: Option<u64>,
/// Add a minimum percentage increase each bid must meet.
pub gap_tick_size_percentage: Option<u8>,
/// Auction name
pub name: AuctionName,
/// Add a instant sale price.
pub instant_sale_price: Option<u64>,
/// Auction name
pub name: Option<AuctionName>,
}
struct Accounts<'a, 'b: 'a> {
@ -94,6 +94,6 @@ pub fn create_auction_v2(
gap_tick_size_percentage: args.gap_tick_size_percentage,
},
args.instant_sale_price,
Some(args.name),
args.name,
)
}