Finish auction actions out

This commit is contained in:
Jordan Prince 2021-04-24 19:41:02 -05:00
parent 87bb0870c6
commit 41b7393d00
7 changed files with 682 additions and 27 deletions

View File

@ -0,0 +1,538 @@
import {
PublicKey,
SystemProgram,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { programIds } from '../utils/ids';
import { deserializeBorsh } from './../utils/borsh';
import { serialize } from 'borsh';
import BN from 'bn.js';
export const AUCTION_PREFIX = 'auction';
export enum AuctionState {
Created = 0,
Started,
Ended,
}
export enum BidStateType {
EnglishAuction = 0,
OpenEdition = 1,
}
export class Bid {
key: PublicKey;
amount: BN;
constructor(args: { key: PublicKey; amount: BN }) {
this.key = args.key;
this.amount = args.amount;
}
}
export class BidState {
type: BidStateType;
bids?: Bid[];
max?: BN;
constructor(args: { type: BidStateType; bids?: Bid[]; max?: BN }) {
this.type = args.type;
this.bids = args.bids;
this.max = args.max;
}
}
export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9;
export class AuctionData {
/// Pubkey of the authority with permission to modify this auction.
authority: PublicKey;
/// Pubkey of the resource being bid on.
resource: PublicKey;
/// Token mint for the SPL token being used to bid
tokenMint: PublicKey;
/// The state the auction is in, whether it has started or ended.
state: AuctionState;
/// Auction Bids, each user may have one bid open at a time.
bidState: BidState;
/// The time the last bid was placed, used to keep track of auction timing.
lastBid?: BN;
/// Slot time the auction was officially ended by.
endedAt?: BN;
/// End time is the cut-off point that the auction is forced to end by.
endAuctionAt?: BN;
/// Gap time is the amount of time in slots after the previous bid at which the auction ends.
endAuctionGap?: BN;
constructor(args: {
authority: PublicKey;
resource: PublicKey;
tokenMint: PublicKey;
state: AuctionState;
bidState: BidState;
lastBid?: BN;
endedAt?: BN;
endAuctionAt?: BN;
endAuctionGap?: BN;
}) {
this.authority = args.authority;
this.resource = args.resource;
this.tokenMint = args.tokenMint;
this.state = args.state;
this.bidState = args.bidState;
this.lastBid = args.lastBid;
this.endedAt = args.endedAt;
this.endAuctionAt = args.endAuctionAt;
this.endAuctionGap = args.endAuctionGap;
}
}
export class BidderMetadata {
// Relationship with the bidder who's metadata this covers.
bidderPubkey: PublicKey;
// Relationship with the auction this bid was placed on.
auctionPubkey: PublicKey;
// Amount that the user bid.
lastBid: BN;
// Tracks the last time this user bid.
lastBidTimestamp: BN;
// Whether the last bid the user made was cancelled. This should also be enough to know if the
// user is a winner, as if cancelled it implies previous bids were also cancelled.
cancelled: boolean;
constructor(args: {
bidderPubkey: PublicKey;
auctionPubkey: PublicKey;
lastBid: BN;
lastBidTimestamp: BN;
cancelled: boolean;
}) {
this.bidderPubkey = args.bidderPubkey;
this.auctionPubkey = args.auctionPubkey;
this.lastBid = args.lastBid;
this.lastBidTimestamp = args.lastBidTimestamp;
this.cancelled = args.cancelled;
}
}
export class BidderPot {
/// Points at actual pot that is a token account
bidderPot: PublicKey;
constructor(args: { bidderPot: PublicKey }) {
this.bidderPot = args.bidderPot;
}
}
export enum WinnerLimitType {
Unlimited = 0,
Capped = 1,
}
export class WinnerLimit {
type: WinnerLimitType;
usize?: BN;
constructor(args: { type: WinnerLimitType; usize?: BN }) {
this.type = args.type;
this.usize = args.usize;
}
}
class CreateAuctionArgs {
instruction: number = 0;
/// How many winners are allowed for this auction. See AuctionData.
winners: WinnerLimit;
/// The resource being auctioned. See AuctionData.
resource: PublicKey;
/// End time is the cut-off point that the auction is forced to end by. See AuctionData.
endAuctionAt?: BN;
/// Gap time is how much time after the previous bid where the auction ends. See AuctionData.
endAuctionGap?: BN;
/// Token mint for the SPL token used for bidding.
tokenMint: PublicKey;
/// Authority
authority: PublicKey;
constructor(args: {
winners: WinnerLimit;
resource: PublicKey;
endAuctionAt?: BN;
endAuctionGap?: BN;
tokenMint: PublicKey;
authority: PublicKey;
}) {
this.winners = args.winners;
this.resource = args.resource;
this.endAuctionAt = args.endAuctionAt;
this.endAuctionGap = args.endAuctionGap;
this.tokenMint = args.tokenMint;
this.authority = args.authority;
}
}
class StartAuctionArgs {
instruction: number = 1;
resource: PublicKey;
constructor(args: { resource: PublicKey }) {
this.resource = args.resource;
}
}
class PlaceBidArgs {
instruction: number = 2;
resource: PublicKey;
amount: BN;
constructor(args: { resource: PublicKey; amount: BN }) {
this.resource = args.resource;
this.amount = args.amount;
}
}
export const AUCTION_SCHEMA = new Map<any, any>([
[
CreateAuctionArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['winnerLimitType', 'u8'],
['usize', { kind: 'option', type: 'u64' }],
['resource', 'pubkey'],
['endAuctionAt', { kind: 'option', type: 'u64' }],
['endAuctionGap', { kind: 'option', type: 'u64' }],
['tokenMint', 'pubkey'],
['authority', 'pubkey'],
],
},
],
[
StartAuctionArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['resource', 'pubkey'],
],
},
],
[
PlaceBidArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['resource', 'pubkey'],
['amount', 'u64'],
],
},
],
[
AuctionData,
{
kind: 'struct',
fields: [
['authority', 'pubkey'],
['resource', 'pubkey'],
['tokenMint', 'pubkey'],
['state', 'u8'],
['bidState', 'BidState'],
['lastBid', { kind: 'option', type: 'u64' }],
['endedAt', { kind: 'option', type: 'u64' }],
['endAuctionAt', { kind: 'option', type: 'u64' }],
['endAuctionGap', { kind: 'option', type: 'u64' }],
],
},
],
[
BidState,
{
kind: 'struct',
fields: [
['type', 'u8'],
['bids', { kind: 'option', type: { kind: 'vec', type: 'Bid' } }],
['max', { kind: 'option', type: 'u64' }],
],
},
],
[
Bid,
{
kind: 'struct',
fields: [
['key', 'pubkey'],
['amount', 'u64'],
],
},
],
[
BidderMetadata,
{
kind: 'struct',
fields: [
['bidderPubkey', 'pubkey'],
['auctionPubkey', 'pubkey'],
['lastBid', 'u64'],
['lastBidTimestamp', 'u64'],
['cancelled', 'u8'],
],
},
],
[
BidderPot,
{
kind: 'struct',
fields: [['bidderPot', 'pubkey']],
},
],
]);
export const decodeAuctionData = (buffer: Buffer) => {
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
};
export async function createAuction(
winners: WinnerLimit,
resource: PublicKey,
endAuctionAt: BN | undefined,
endAuctionGap: BN | undefined,
tokenMint: PublicKey,
authority: PublicKey,
creator: PublicKey,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new CreateAuctionArgs({
winners,
resource,
endAuctionAt,
endAuctionGap,
tokenMint,
authority,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: creator,
isSigner: true,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}
export async function startAuction(
resource: PublicKey,
creator: PublicKey,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new StartAuctionArgs({
resource,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: creator,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: SYSVAR_CLOCK_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}
export async function placeBid(
bidderPubkey: PublicKey,
bidderPotTokenPubkey: PublicKey,
tokenMintPubkey: PublicKey,
transferAuthority: PublicKey,
payer: PublicKey,
resource: PublicKey,
amount: BN,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new PlaceBidArgs({
resource,
amount,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const bidderPotKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
auctionKey.toBuffer(),
bidderPubkey.toBuffer(),
],
auctionProgramId,
)
)[0];
const bidderMetaKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
auctionKey.toBuffer(),
bidderPubkey.toBuffer(),
Buffer.from('metadata'),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: bidderPubkey,
isSigner: true,
isWritable: true,
},
{
pubkey: bidderPotKey,
isSigner: false,
isWritable: true,
},
{
pubkey: bidderPotTokenPubkey,
isSigner: false,
isWritable: true,
},
{
pubkey: bidderMetaKey,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: tokenMintPubkey,
isSigner: false,
isWritable: true,
},
{
pubkey: transferAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: payer,
isSigner: true,
isWritable: false,
},
{
pubkey: SYSVAR_CLOCK_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}

View File

@ -1,3 +1,4 @@
export * from './account'; export * from './account';
export * from './metadata'; export * from './metadata';
export * from './vault'; export * from './vault';
export * from './auction';

View File

@ -30,6 +30,14 @@ export const VAULT_ID = new PublicKey(
'94wRaYAQdC2gYF76AUTYSugNJ3rAC4EimjAMPwM7uYry', '94wRaYAQdC2gYF76AUTYSugNJ3rAC4EimjAMPwM7uYry',
); );
export const AUCTION_ID = new PublicKey(
'C9nHkL6BfGx9M9MyYrJqAD5hPsGJd1fHpp1uAJA6vTCn',
);
export const METAPLEX_ID = new PublicKey(
'EPtpKdKW8qciGVd1UFyGjgbBHTbSAyvbY61h9uQGVgeu',
);
export let SYSTEM = new PublicKey('11111111111111111111111111111111'); export let SYSTEM = new PublicKey('11111111111111111111111111111111');
let WORMHOLE_BRIDGE: { let WORMHOLE_BRIDGE: {
@ -171,5 +179,7 @@ export const programIds = () => {
metadata: METADATA_PROGRAM_ID, metadata: METADATA_PROGRAM_ID,
memo: MEMO_ID, memo: MEMO_ID,
vault: VAULT_ID, vault: VAULT_ID,
auction: AUCTION_ID,
metaplex: METAPLEX_ID,
}; };
}; };

View File

@ -4,27 +4,20 @@ import {
PublicKey, PublicKey,
TransactionInstruction, TransactionInstruction,
} from '@solana/web3.js'; } from '@solana/web3.js';
import { contexts, utils, actions, models } from '@oyster/common'; import { utils, actions, models } from '@oyster/common';
import { AccountLayout } from '@solana/spl-token'; import { AccountLayout } from '@solana/spl-token';
import BN from 'bn.js'; import BN from 'bn.js';
const { const { createTokenAccount, addTokenToInactiveVault, VAULT_PREFIX } = actions;
createTokenAccount,
activateVault,
addTokenToInactiveVault,
VAULT_PREFIX,
} = actions;
const { approve } = models; const { approve } = models;
const BATCH_SIZE = 4; const BATCH_SIZE = 4;
// This command batches out adding tokens to a vault using a prefilled payer account, and then activates // This command batches out adding tokens to a vault using a prefilled payer account, and then activates and combines
// the vault for use. It issues a series of transaction instructions and signers for the sendTransactions batch. // the vault for use. It issues a series of transaction instructions and signers for the sendTransactions batch.
export async function addTokensToVault( export async function addTokensToVault(
connection: Connection, connection: Connection,
wallet: any, wallet: any,
vault: PublicKey, vault: PublicKey,
fractionMint: PublicKey,
fractionTreasury: PublicKey,
nfts: { tokenAccount: PublicKey; tokenMint: PublicKey; amount: BN }[], nfts: { tokenAccount: PublicKey; tokenMint: PublicKey; amount: BN }[],
): Promise<{ ): Promise<{
instructions: Array<TransactionInstruction[]>; instructions: Array<TransactionInstruction[]>;
@ -50,7 +43,8 @@ export async function addTokensToVault(
let currSigners: Account[] = []; let currSigners: Account[] = [];
let currInstructions: TransactionInstruction[] = []; let currInstructions: TransactionInstruction[] = [];
nfts.forEach(nft => { for (let i = 0; i < nfts.length; i++) {
let nft = nfts[i];
const newStoreAccount = createTokenAccount( const newStoreAccount = createTokenAccount(
currInstructions, currInstructions,
wallet.publicKey, wallet.publicKey,
@ -70,13 +64,13 @@ export async function addTokensToVault(
currSigners.push(transferAuthority); currSigners.push(transferAuthority);
addTokenToInactiveVault( await addTokenToInactiveVault(
nft.amount, nft.amount,
nft.tokenMint, nft.tokenMint,
nft.tokenAccount, nft.tokenAccount,
newStoreAccount, newStoreAccount,
vault, vault,
vaultAuthority, wallet.publicKey,
wallet.publicKey, wallet.publicKey,
transferAuthority.publicKey, transferAuthority.publicKey,
currInstructions, currInstructions,
@ -89,19 +83,7 @@ export async function addTokensToVault(
currSigners = []; currSigners = [];
currInstructions = []; currInstructions = [];
} }
}); }
currSigners = [];
currInstructions = [];
activateVault(
new BN(0),
vault,
fractionMint,
fractionTreasury,
vaultAuthority,
currInstructions,
);
signers.push(currSigners); signers.push(currSigners);
instructions.push(currInstructions); instructions.push(currInstructions);

View File

@ -0,0 +1,122 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { utils, actions, models } from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import BN from 'bn.js';
import { METAPLEX_PREFIX } from '../models/metaplex';
const {
createTokenAccount,
activateVault,
combineVault,
VAULT_PREFIX,
AUCTION_PREFIX,
} = actions;
const { approve } = models;
// This command "closes" the vault, by activating & combining it in one go, handing it over to the auction manager
// authority (that may or may not exist yet.)
export async function closeVault(
connection: Connection,
wallet: any,
vault: PublicKey,
fractionMint: PublicKey,
fractionTreasury: PublicKey,
redeemTreasury: PublicKey,
priceMint: PublicKey,
externalPriceAccount: PublicKey,
): Promise<{
instructions: TransactionInstruction[];
signers: Account[];
}> {
const PROGRAM_IDS = utils.programIds();
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
const vaultAuthority = (
await PublicKey.findProgramAddress(
[Buffer.from(VAULT_PREFIX), PROGRAM_IDS.vault.toBuffer()],
PROGRAM_IDS.vault,
)
)[0];
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
PROGRAM_IDS.auction.toBuffer(),
vault.toBuffer(),
],
PROGRAM_IDS.auction,
)
)[0];
const auctionManagerKey: PublicKey = (
await PublicKey.findProgramAddress(
[Buffer.from(METAPLEX_PREFIX), auctionKey.toBuffer()],
PROGRAM_IDS.metaplex,
)
)[0];
await activateVault(
new BN(0),
vault,
fractionMint,
fractionTreasury,
vaultAuthority,
instructions,
);
const outstandingShareAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt,
fractionMint,
wallet.publicKey,
signers,
);
const payingTokenAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt,
priceMint,
wallet.publicKey,
signers,
);
// Shouldn't need to pay anything since we activated vault with 0 shares, but we still
// need this setup anyway.
const transferAuthority = approve(
instructions,
[],
payingTokenAccount,
wallet.publicKey,
0,
);
signers.push(transferAuthority);
await combineVault(
vault,
outstandingShareAccount,
payingTokenAccount,
fractionMint,
fractionTreasury,
redeemTreasury,
auctionManagerKey,
wallet.publicKey,
transferAuthority.publicKey,
externalPriceAccount,
instructions,
);
return { instructions, signers };
}

View File

@ -1,3 +1,3 @@
export * from './nft'; export * from './nft';
export * from './createVault'; export * from './createVault';
export * from './auction'; export * from './createAuction';

View File

@ -9,6 +9,8 @@ export * from './redeemMasterEditionBid';
export * from './redeemOpenEditionBid'; export * from './redeemOpenEditionBid';
export * from './startAuction'; export * from './startAuction';
export * from './validateSafetyDepositBox'; export * from './validateSafetyDepositBox';
export const METAPLEX_PREFIX = 'metaplex';
export class AuctionManager { export class AuctionManager {
key: number = 0; key: number = 0;
authority?: PublicKey; authority?: PublicKey;