diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9821cd510..b9cde6a3b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,8 +32,3 @@ jobs: - name: Lint test run: yarn run lint:test - - - name: Commit potential changes - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Fix styling diff --git a/packages/js/src/plugins/auctionHouseModule/operations/directBuy.ts b/packages/js/src/plugins/auctionHouseModule/operations/directBuy.ts index 27875162d..bf0220f17 100644 --- a/packages/js/src/plugins/auctionHouseModule/operations/directBuy.ts +++ b/packages/js/src/plugins/auctionHouseModule/operations/directBuy.ts @@ -5,6 +5,8 @@ import { AuctionHouse, Bid, Listing, Purchase } from '../models'; import { ExecuteSaleBuilderContext } from './executeSale'; import { TransactionBuilder, TransactionBuilderOptions } from '@/utils'; import { + amount, + lamports, now, Operation, OperationHandler, @@ -99,6 +101,12 @@ export type DirectBuyInput = { */ price?: SolAmount | SplTokenAmount; + /** + * The amount of tokens to buy. + * + */ + tokens?: SplTokenAmount; + /** * The Auctioneer authority key. * It is required when Auction House has Auctioneer enabled. @@ -199,7 +207,7 @@ export const directBuyBuilder = async ( auctionHouse, auctioneerAuthority, listing, - price = listing.price, + price, buyer = metaplex.identity(), authority = auctionHouse.authorityAddress, bookkeeper = metaplex.identity(), @@ -207,7 +215,20 @@ export const directBuyBuilder = async ( executeSaleInstructionKey, } = params; - const { tokens, asset, sellerAddress, receiptAddress } = listing; + + const { asset, sellerAddress, receiptAddress } = listing; + const tokens = params.tokens ?? listing.tokens; + + let finalPrice = price; + + if(!finalPrice) { + const listingPricePerToken = listing.price.basisPoints.div(listing.tokens.basisPoints); + const finalPriceBasisPoints = listingPricePerToken.mul(tokens.basisPoints); + + finalPrice = auctionHouse.isNative + ? lamports(finalPriceBasisPoints) + : amount(finalPriceBasisPoints, auctionHouse.treasuryMint.currency) + } const printReceipt = (params.printReceipt ?? true) && Boolean(receiptAddress); @@ -221,7 +242,7 @@ export const directBuyBuilder = async ( auctioneerAuthority, authority, tokens, - price, + price: finalPrice, mintAccount: asset.mint.address, seller: sellerAddress, buyer, @@ -243,7 +264,7 @@ export const directBuyBuilder = async ( buyerAddress: buyer.publicKey, receiptAddress: receipt, purchaseReceiptAddress: null, - price, + price: finalPrice, tokens, canceledAt: null, createdAt: now(), diff --git a/packages/js/test/plugins/auctionHouseModule/directBuy.test.ts b/packages/js/test/plugins/auctionHouseModule/directBuy.test.ts index bcd104093..79653371e 100644 --- a/packages/js/test/plugins/auctionHouseModule/directBuy.test.ts +++ b/packages/js/test/plugins/auctionHouseModule/directBuy.test.ts @@ -3,15 +3,14 @@ import spok, { Specifications } from 'spok'; import { Keypair } from '@solana/web3.js'; import { createNft, + createSft, createWallet, killStuckProcess, - metaplex, - spokSameAmount, - spokSamePubkey, + metaplex, spokSameAmount, spokSamePubkey, } from '../../helpers'; import { createAuctionHouse } from './helpers'; import { sol, token } from '@/types'; -import { Purchase } from '@/index'; +import { Purchase } from '@/plugins'; killStuckProcess(); @@ -64,6 +63,89 @@ test('[auctionHouseModule] buy on an Auction House with minimum input', async (t } as unknown as Specifications); }); +test('[auctionHouseModule] partial buy on an Auction House', async (t: Test) => { + // Given we have an Auction House and an SFT. + const mx = await metaplex(); + const seller = await createWallet(mx); + const auctionHouse = await createAuctionHouse(mx); + const sft = await createSft(mx, { + tokenOwner: seller.publicKey, + tokenAmount: token(10) + }); + + // And we listed that 5 SFTs for 1 SOL each. + const { listing } = await mx.auctionHouse().list({ + auctionHouse, + seller, + mintAccount: sft.address, + price: sol(5), + tokens: token(5), + }); + + // When we execute direct buy with the given listing. + const { purchase } = await mx.auctionHouse().buy({ + auctionHouse, + listing, + tokens: token(3) + }); + + // Then the user must receive 3 Tokens. + const buyerTokens = await mx + .nfts() + .findByToken({ token: purchase.asset.token.address }); + + t.equal(buyerTokens.token.amount.basisPoints.toNumber(), 3); + + // And then the seller must have 2 Tokens on sale left. + const sellerTokens = await mx + .nfts() + .findByToken({ token: listing.asset.token.address }); + + t.equal(sellerTokens.token.delegateAmount.basisPoints.toNumber(), 2); +}); + +test('[auctionHouseModule] partial buy on an Auction House with exact price', async (t: Test) => { + // Given we have an Auction House and an SFT. + const mx = await metaplex(); + const seller = await createWallet(mx); + const auctionHouse = await createAuctionHouse(mx); + const sft = await createSft(mx, { + tokenOwner: seller.publicKey, + tokenAmount: token(10) + }); + + // And we listed that 5 SFTs for 1 SOL each. + const { listing } = await mx.auctionHouse().list({ + auctionHouse, + seller, + mintAccount: sft.address, + price: sol(5), + tokens: token(5), + }); + + // When we execute direct buy with the given listing with 3 SOL. + const { purchase } = await mx.auctionHouse().buy({ + auctionHouse, + listing, + tokens: token(3), + price: sol(3) + }); + + // Then the user must receive 3 Tokens. + const buyerTokens = await mx + .nfts() + .findByToken({ token: purchase.asset.token.address }); + + t.equal(buyerTokens.token.amount.basisPoints.toNumber(), 3); + + // And then the seller must have 2 Tokens on sale left. + const sellerTokens = await mx + .nfts() + .findByToken({ token: listing.asset.token.address }); + + t.equal(sellerTokens.token.delegateAmount.basisPoints.toNumber(), 2); +}); + test('[auctionHouseModule] buy on an Auction House with auctioneer with auctioneer', async (t: Test) => { // Given we have an Auction House and an NFT. const mx = await metaplex();