@deserialize/auction-sdk
Version:
An SDK for Auction on SVMs
215 lines (179 loc) • 10.2 kB
text/typescript
import { PublicKey, SystemProgram, Transaction, TransactionSignature } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
import ProgramLoader from "./ProgramLoader";
import { ApplicationState as ApplicationStateType } from "../types/application-state";
import Listing from "./Listing";
import BidReceipt from "./BidReciept";
import { ListingState } from "../types/listing";
import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { checkEnumState } from "../helper/check-enum-status.helper";
/**
* ApplicationState
*
* @description A class for managing the application state
* - get the application state by pubkey
* - update application state
* - execute sale ?
*/
export default class ApplicationState {
private listingProgram: Listing;
private bidReceiptProgram: BidReceipt;
public constructor(protected readonly programLoader: ProgramLoader) {
this.listingProgram = new Listing(programLoader);
this.bidReceiptProgram = new BidReceipt(programLoader);
}
public async create(feeAccount: string): Promise<ApplicationStateType> {
try {
const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
const [applicationState, _] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
console.log("[*] NEW APPLICATION STATE:- " + applicationState);
let txSignature = await this.programLoader.program.methods
.initialize()
.accounts({
applicationState,
user: this.programLoader.wallet.publicKey,
feeAccount,
systemProgram: SystemProgram.programId
})
.signers([this.programLoader.wallet])
.rpc();
console.log("[*] Transaction signature:- ", txSignature);
let appState = await this.programLoader.program.account.applicationState.fetch(applicationState);
return appState as unknown as ApplicationStateType;
} catch (e: any) {
console.error(e);
throw new Error(`Failed to create application state: ${e.message}`);
}
}
/**
* @description Get all application states
* @returns {ApplicationStateType[]} The application states
*/
public async getAll(): Promise<ApplicationStateType[]> {
try {
const applicationStates = await this.programLoader.program.account.applicationState.all();
console.log("[*] Application states: ", applicationStates);
return applicationStates as unknown as ApplicationStateType[];
} catch (e: any) {
console.error(e);
throw new Error(`Failed to get all application states: ${e.message}`);
}
}
/**
* @description Get the application state
*
* @param {PublicKey} appKey - The application state pubkey
* @returns {ApplicationStateType} The application state
*/
public async get(appKey: PublicKey): Promise<ApplicationStateType> {
try {
const applicationState = await this.programLoader.program.account.applicationState.fetch(appKey);
return applicationState as any;
} catch (e: any) {
console.error(e);
throw new Error(`Failed to get application state: ${e.message}`);
}
}
/**
* @description Update the application state
*
* @param {PublicKey} appKey - The application state pubkey
* @param {PublicKey} feeAccount - The fee account pubkey
* @returns {ApplicationStateType} The application state
*/
public async update(appKey: PublicKey, feeAccount: PublicKey): Promise<ApplicationStateType> {
try {
await this.programLoader.program.methods
.updateApplicationState(feeAccount)
.accounts({
applicationState: appKey,
user: this.programLoader.wallet.publicKey,
})
.signers([this.programLoader.wallet])
.rpc();
let state = await this.get(appKey);
return state;
} catch (e: any) {
console.error(e);
throw new Error(`Failed to update application state: ${e.message}`);
}
}
/**
* @description Execute a sale
* @param listingPubkey
* @param executor
* @returns
*/
public async executeSale(listingPubkey: PublicKey, executor: PublicKey, tokenMintProgramId: PublicKey, nftMintProgramId: PublicKey): Promise<Transaction> {
try {
const listingData = await this.listingProgram.get(listingPubkey);
console.log("[*] Listing data: ", JSON.stringify(listingData, null, 4));
// const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
// const [applicationState, applicationStateBump] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
let appState = await this.programLoader.program.account.applicationState.fetch(this.programLoader.applicationState);
console.log("[*] Application state: ", JSON.stringify(appState, null, 4));
// if(!executor.equals(listingData.authority) || !executor.equals(appState.listingAuthority)) throw new Error('The user has no access to execute sale');
const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer())];
const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);
var auctionManagerState = await this.programLoader.program.account.auctionManager.fetch(auctionManager);
console.log("[*] Auction manager state: ", JSON.stringify(auctionManagerState, null, 4));
const seeds3 = [anchor.utils.bytes.utf8.encode("listing"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer()), Uint8Array.from(auctionManager.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
const [listing, listingBump] = PublicKey.findProgramAddressSync(seeds3, this.programLoader.program.programId);
var listingState = await this.programLoader.program.account.listing.fetch(listing);
console.log("[*] Listing: ", JSON.stringify(listingState, null, 4));
if (!listingPubkey.equals(listing)) throw new Error("Invalid Listing");
console.log("[*] Listing state: ", listingData.state);
if (!checkEnumState(listingData.state as any, ListingState.Active)) throw new Error("Listing is not active");
let highestBidInfo = await this.listingProgram.getHighestBidInfo(listingPubkey);
console.log("[*] Highest bid info: ", JSON.stringify(highestBidInfo, null, 4));
let bidsInfoToRefund = await this.listingProgram.getBidsInfoToRefund(listingPubkey);
console.log("[*] Bids info to refund: ", JSON.stringify(bidsInfoToRefund, null, 4));
let txn: Transaction = new Transaction();
//create refund bid transactions
for await (let bid of bidsInfoToRefund) {
let inx = await this.bidReceiptProgram.refund(bid.receipt, bid.bidder, tokenMintProgramId);
txn.add(inx);
}
const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true);
// const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true);
const winnerNftAccount = await getAssociatedTokenAddress(listingData.nft, highestBidInfo.bidder, true, nftMintProgramId);
const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId);
const feeTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, appState.feeAccount, true, tokenMintProgramId);
const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(highestBidInfo.bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")];
const [userdata, _] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId);
console.log("[*] WINNER NFT account: ", winnerNftAccount);
console.log("[*] AUCTION MANAGER Token escrow: ", tokenEscrow);
//create transaction to execute sale
const submittedAt = Math.floor(Date.now() / 1000);
const inx = await this.programLoader.program.methods
.executeSale(new anchor.BN(submittedAt))
.accounts({
applicationState: this.programLoader.applicationState,
userdata,
bidReceipt: highestBidInfo.receipt,
user: executor,
listing,
nft: listingData.nft,
auctionManager,
nftEscrow,
winnerNftAccount,
proceedsWallet: listingData.auctionProceedsWallet,
tokenEscrow,
tokenMint: listingData.tokenMint,
tokenProgram: tokenMintProgramId,
nftTokenProgram: nftMintProgramId,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
feeTokenAccount
})
.instruction();
txn.add(inx);
txn.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash;
txn.feePayer = executor;
return txn;
} catch (e: any) {
console.error(e);
throw new Error(`Failed to execute sale: ${e.message}`);
}
}
}