UNPKG

@deserialize/auction-sdk

Version:

An SDK for Auction on SVMs

533 lines (448 loc) 27.7 kB
import { PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; import ProgramLoader from "./ProgramLoader"; import { BidReceiptState, BidReceipt as BidReceiptType, ReceiptInfo } from "../types/bid-receipt"; import Listing from "./Listing"; import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { checkEnumState } from "../helper/check-enum-status.helper"; import config from "../config"; /** * BidReceipt * * @description A class for managing bid receipts * * - create a bid receipt * - get a bid receipt * - fetch many bid receipts * - get all bid receipts * - get bid receipt by state * - get user bid receipt on a listing * - cancel a bid receipt * - refund a bid * - increase bid */ export default class BidReceipt { public constructor(protected readonly programLoader: ProgramLoader) { } private getListingProgram(): Listing { return new Listing(this.programLoader); } /** * @description Create a bid receipt * * @param {PublicKey} listingPubkey - The public key of the listing * @param {PublicKey} bidder - The public key of the bidder * @param {number} price - The price of the bid * @returns {TransactionInstruction[]} */ public async create(listingPubkey: PublicKey, bidder: PublicKey, price: number, tokenMintProgramId: PublicKey,): Promise<TransactionInstruction[]> { try { var listingProgram = this.getListingProgram(); const listingData = await listingProgram.get(listingPubkey); // 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) 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); 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); console.log("[*] Listing: ", listing); console.log("[*] Listing bump: ", listingBump); const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())]; const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId); console.log("[*] Bid receipt: ", bidReceipt); console.log("[*] Bid receipt bump: ", bidReceiptBump); const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")]; const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId); console.log("[*] Userdata: ", userdata); console.log("[*] Userdata bump: ", userdataBump); const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true); const proceedsWallet = await getAssociatedTokenAddress(listingData.tokenMint, listingData.authority, true, tokenMintProgramId); const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId); const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true); const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId); const feeTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, appState.feeAccount, true, tokenMintProgramId); console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount); console.log("[*] BIDDER NFT account: ", bidderNftAccount); console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow); console.log("[*] FEE TOKEN account: ", feeTokenAccount); const txns: TransactionInstruction[] = []; const bidderTokenAccountInfo = await this.programLoader.connection.getAccountInfo(bidderTokenAccount); const bidderNftAccountInfo = await this.programLoader.connection.getAccountInfo(bidderNftAccount); const tokenEscrowInfo = await this.programLoader.connection.getAccountInfo(tokenEscrow); const feeTokenAccountInfo = await this.programLoader.connection.getAccountInfo(feeTokenAccount); if (!feeTokenAccountInfo) { const inx = createAssociatedTokenAccountInstruction(bidder, feeTokenAccount, appState.feeAccount, listingData.tokenMint, tokenMintProgramId); txns.push(inx); } if (!bidderTokenAccountInfo) { const inx = createAssociatedTokenAccountInstruction(bidder, bidderTokenAccount, bidder, listingData.tokenMint, tokenMintProgramId); txns.push(inx); } if (!bidderNftAccountInfo) { const inx = createAssociatedTokenAccountInstruction(bidder, bidderNftAccount, bidder, listingData.nft); txns.push(inx); } if (!tokenEscrowInfo) { const inx = createAssociatedTokenAccountInstruction(bidder, tokenEscrow, auctionManager, listingData.tokenMint, tokenMintProgramId); txns.push(inx); } const submittedAt = Math.floor(Date.now() / 1000); const inx = await this.programLoader.program.methods .bidOnListing(new anchor.BN(price), new anchor.BN(submittedAt)) .accounts({ applicationState: this.programLoader.applicationState, userdata, bidReceipt, user: bidder, listing, nft: listingData.nft, auctionManager, proceedsWallet, nftEscrow, bidderNftAccount, bidderAccount: bidderTokenAccount, tokenEscrow, tokenMint: listingData.tokenMint, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, feeAccount: appState.feeAccount, feeTokenAccount, }) .instruction() txns.push(inx); return txns; } catch (e: any) { console.error(e); throw new Error(`Failed to create bid receipt: ${e.message}`); } } /** * @description Create a transaction for creating a bid receipt * * @param {PublicKey} listingPubkey - The public key of the listing * @param {PublicKey} bidder - The public key of the bidder * @param {number} price - The price of the bid * @returns {Transaction} */ public async createTransaction(listingPubkey: PublicKey, bidder: PublicKey, price: number, tokenMintProgramId: PublicKey): Promise<Transaction> { try { let txnIns = await this.create(listingPubkey, bidder, price, tokenMintProgramId); let txn: Transaction = new Transaction(); for (let tx of txnIns) { txn.add(tx); } txn.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash; txn.feePayer = bidder; return txn; } catch (e: any) { console.error(e); throw new Error(`Failed to create bid receipt: ${e.message}`); } } /** * @description Get a bid receipt * * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt * @returns {BidReceiptType} */ public async get(bidReceiptPubkey: PublicKey): Promise<BidReceiptType> { try { const bidReceipt = await this.programLoader.program.account.bidReceipt.fetch(bidReceiptPubkey); return bidReceipt as unknown as BidReceiptType; } catch (e: any) { console.error(e); throw new Error(`Failed to get bid receipt: ${e.message}`); } } /** * @description Fetch many bid receipts * * @param {PublicKey[]} bidReceiptPubkeys - The public keys of the bid receipts * @returns {BidReceiptType[]} */ public async fetchMany(bidReceiptPubkeys: PublicKey[]): Promise<BidReceiptType[]> { try { const bidReceipts = await this.programLoader.program.account.bidReceipt.fetchMultiple(bidReceiptPubkeys); console.log("[*] Bid receipts: ", JSON.stringify(bidReceipts, null, 4)); return bidReceipts as unknown as BidReceiptType[]; } catch (e: any) { console.error(e); throw new Error(`Failed to fetch many bid receipts: ${e.message}`); } } /** * @description Get all bid receipts * * @returns {BidReceiptType[]} */ public async getAll(): Promise<BidReceiptType[]> { try { const bidReceipts = await this.programLoader.program.account.bidReceipt.all(); return bidReceipts as unknown as BidReceiptType[]; } catch (e: any) { console.error(e); throw new Error(`Failed to get all bid receipts: ${e.message}`); } } /** * @description Get bid receipts by state * * @param {BidReceiptState} state - The state of the bid receipts * @returns {BidReceiptType[]} */ public async getByState(state: BidReceiptState): Promise<BidReceiptType[]> { try { const bidReceipts = await this.getAll(); return bidReceipts.filter((bidReceipt) => bidReceipt.state === state); } catch (e: any) { console.error(e); throw new Error(`Failed to get bid receipts by state: ${e.message}`); } } /** * @description Get a user's bid on a listing * * @param {PublicKey} listingPubkey - The public key of the listing * @param {PublicKey} user - The public key of the user * @returns {BidReceiptType|null} */ public async getUserBidOnListing(listingPubkey: PublicKey, user: PublicKey): Promise<BidReceiptType | null> { try { var listingProgram = this.getListingProgram(); let listing = await listingProgram.get(listingPubkey); let bids = await this.fetchMany(listing.bidReceipts); let userBid = bids.find(bid => bid.bidder.equals(user)); return userBid; } catch (e: any) { console.error(e); throw new Error(`Failed to get user bid on listing: ${e.message}`); } } /** * @description Cancel a bid receipt * * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt * @param {PublicKey} bidder - The public key of the bidder * @returns {Transaction} */ public async cancel(bidReceiptPubkey: PublicKey, bidder: PublicKey, tokenMintProgramId: PublicKey): Promise<Transaction> { try { const bidReceiptData = await this.get(bidReceiptPubkey); var listingProgram = this.getListingProgram(); const listingData = await listingProgram.get(bidReceiptData.listing); if (!bidReceiptData.bidder.equals(bidder)) { throw new Error("You cannot cancel another user's bid"); } // 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) 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); 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); console.log("[*] Listing: ", listing); console.log("[*] Listing bump: ", listingBump); const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())]; const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId); console.log("[*] Bid receipt: ", bidReceipt); console.log("[*] Bid receipt bump: ", bidReceiptBump); const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")]; const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId); console.log("[*] Userdata: ", userdata); console.log("[*] Userdata bump: ", userdataBump); const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true); const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true); console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow); console.log("[*] LISTER NFT owner: ", nftOwner); const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId); const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true); const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId); console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount); console.log("[*] BIDDER NFT account: ", bidderNftAccount); console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow); let data: ReceiptInfo[] = await Promise.all(listingData.bidReceipts.map(async key => { let bidReceipt = await this.get(key); return { amount: bidReceipt.amount, key }; })) const submittedAt = Math.floor(Date.now() / 1000); const tx = await this.programLoader.program.methods .cancelBid(data, new anchor.BN(submittedAt)) .accounts({ applicationState: this.programLoader.applicationState, userdata, bidReceipt, user: bidder, listing, nft: listingData.nft, auctionManager, cancellerTokenAccount: bidderTokenAccount, tokenEscrow, tokenMint: listingData.tokenMint, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, feeAccount: appState.feeAccount }) .transaction(); tx.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash; tx.feePayer = bidder; return tx; } catch (e: any) { console.error(e); throw new Error(`Failed to cancel bid receipt: ${e.message}`); } } /** * @description Refund a bid receipt * * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt * @param {PublicKey} bidder - The public key of the bidder * @returns {TransactionInstruction} */ public async refund(bidReceiptPubkey: PublicKey, bidder: PublicKey, tokenMintProgramId: PublicKey): Promise<TransactionInstruction> { try { const bidReceiptData = await this.get(bidReceiptPubkey); console.log("[*] Bid receipt data: ", JSON.stringify(bidReceiptData, null, 4)); var listingProgram = this.getListingProgram(); const listingData = await listingProgram.get(bidReceiptData.listing); console.log("[*] Listing data: ", JSON.stringify(listingData, null, 4)); if (!bidReceiptData.bidder.equals(bidder)) { throw new Error("You cannot cancel another user's bid"); } if (!checkEnumState(bidReceiptData.state as any, BidReceiptState.Active)) { throw new Error("You cannot refund a bid that is not active"); } // 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)); 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); console.log("[*] AUCTION MANAGER", auctionManager); 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); console.log("[*] Listing: ", listing); console.log("[*] Listing bump: ", listingBump); const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())]; const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId); console.log("[*] Bid receipt: ", bidReceipt); console.log("[*] Bid receipt bump: ", bidReceiptBump); const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")]; const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId); console.log("[*] Userdata: ", userdata); console.log("[*] Userdata bump: ", userdataBump); const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true); const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true); console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow); console.log("[*] LISTER NFT owner: ", nftOwner); const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId); const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true); const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId); console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount); console.log("[*] BIDDER NFT account: ", bidderNftAccount); console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow); const refundedAt = Math.floor(Date.now() / 1000); let inx = await this.programLoader.program.methods .refundBid(new anchor.BN(refundedAt)) .accounts({ applicationState: this.programLoader.applicationState, user: bidder, auctionManager, listing, nft: listingData.nft, userdata, userRefundAccount: bidderTokenAccount, refundBidReceipt: bidReceipt, tokenEscrow, tokenMint: listingData.tokenMint, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId }) .instruction() return inx; } catch (e: any) { console.error(e); throw new Error(`Failed to refund bid receipt: ${e.message}`); } } /** * @description Increase a bid * * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt * @param {PublicKey} bidder - The public key of the bidder * @param {number} newAmount - The new bid amount * @returns {Transaction} */ public async increase(bidReceiptPubkey: PublicKey, bidder: PublicKey, newAmount: number, tokenMintProgramId: PublicKey): Promise<Transaction> { try { const bidReceiptData = await this.get(bidReceiptPubkey); var listingProgram = this.getListingProgram(); const listingData = await listingProgram.get(bidReceiptData.listing); if (!bidReceiptData.bidder.equals(bidder)) { throw new Error("You cannot increase another user's bid"); } if (bidReceiptData.state !== BidReceiptState.Active) { throw new Error("You cannot increase a bid that is not active"); } // 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) 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); 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); console.log("[*] Listing: ", listing); console.log("[*] Listing bump: ", listingBump); const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())]; const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId); console.log("[*] Bid receipt: ", bidReceipt); console.log("[*] Bid receipt bump: ", bidReceiptBump); const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")]; const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId); console.log("[*] Userdata: ", userdata); console.log("[*] Userdata bump: ", userdataBump); const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true); const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true); console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow); console.log("[*] LISTER NFT owner: ", nftOwner); const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId); const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true); const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId); console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount); console.log("[*] BIDDER NFT account: ", bidderNftAccount); console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow); const updatedAt = Math.floor(Date.now() / 1000); const tx = await this.programLoader.program.methods .increaseBid(new anchor.BN(newAmount), new anchor.BN(updatedAt)) .accounts({ applicationState: this.programLoader.applicationState, userdata, bidReceipt, user: bidder, listing, nft: listingData.nft, auctionManager, bidderAccount: bidderTokenAccount, tokenEscrow: tokenEscrow, tokenMint: listingData.tokenMint, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, feeAccount: appState.feeAccount }) .transaction(); tx.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash; tx.feePayer = bidder; return tx; } catch (e: any) { console.error(e); throw new Error(`Failed to increase bid receipt: ${e.message}`); } } }