auction-house.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import {
  2. AnchorProvider,
  3. Program,
  4. Wallet,
  5. BN,
  6. getProvider,
  7. setProvider,
  8. } from "@coral-xyz/anchor";
  9. import {
  10. Transaction,
  11. Keypair,
  12. PublicKey,
  13. SystemProgram,
  14. } from "@solana/web3.js";
  15. import { u64, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
  16. import * as metaplex from "@metaplex/js";
  17. import * as assert from "assert";
  18. import { AuctionHouse } from "../target/types/auction_house";
  19. const IDL = require("../target/idl/auction_house.json");
  20. const MetadataDataData = metaplex.programs.metadata.MetadataDataData;
  21. const CreateMetadata = metaplex.programs.metadata.CreateMetadata;
  22. const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
  23. "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
  24. );
  25. const AUCTION_HOUSE_PROGRAM_ID = new PublicKey(
  26. "hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk"
  27. );
  28. // Mint address for native SOL token accounts.
  29. //
  30. // The program uses this when one wants to pay with native SOL vs an SPL token.
  31. const NATIVE_SOL_MINT = new PublicKey(
  32. "So11111111111111111111111111111111111111112"
  33. );
  34. describe("auction-house", () => {
  35. setProvider(AnchorProvider.env());
  36. // @ts-ignore
  37. const wallet = getProvider().wallet as Wallet;
  38. // Clients.
  39. let authorityClient: Program<AuctionHouse>; // Represents the exchange authority.
  40. let sellerClient: Program<AuctionHouse>; // Represents the seller.
  41. let buyerClient: Program<AuctionHouse>; // Represents the buyer.
  42. let nftMintClient: Token; // Represents the NFT to be traded.
  43. // Seeds constants.
  44. const PREFIX = Buffer.from("auction_house");
  45. const FEE_PAYER = Buffer.from("fee_payer");
  46. const TREASURY = Buffer.from("treasury");
  47. // Constant accounts.
  48. const authorityKeypair = wallet.payer;
  49. const authority = wallet.publicKey;
  50. const feeWithdrawalDestination = authority;
  51. const treasuryWithdrawalDestination = authority;
  52. const treasuryWithdrawalDestinationOwner = authority;
  53. const treasuryMint = NATIVE_SOL_MINT;
  54. const tokenProgram = TOKEN_PROGRAM_ID;
  55. // Uninitialized constant accounts.
  56. let metadata: PublicKey;
  57. let auctionHouse: PublicKey;
  58. let auctionHouseFeeAccount: PublicKey;
  59. // Buyer specific vars.
  60. const buyerWallet = Keypair.generate();
  61. let buyerTokenAccount: PublicKey;
  62. // Seller specific vars.
  63. const sellerWallet = Keypair.generate();
  64. let sellerTokenAccount: PublicKey;
  65. it("Creates an NFT mint", async () => {
  66. // Create the mint.
  67. nftMintClient = await Token.createMint(
  68. getProvider().connection,
  69. authorityKeypair,
  70. authority,
  71. null,
  72. 6,
  73. tokenProgram
  74. );
  75. // Create the metadata.
  76. const [_metadata] = await PublicKey.findProgramAddress(
  77. [
  78. Buffer.from("metadata"),
  79. TOKEN_METADATA_PROGRAM_ID.toBuffer(),
  80. nftMintClient.publicKey.toBuffer(),
  81. ],
  82. TOKEN_METADATA_PROGRAM_ID
  83. );
  84. metadata = _metadata;
  85. const tx = new CreateMetadata(
  86. { feePayer: authority },
  87. {
  88. metadata,
  89. metadataData: new MetadataDataData({
  90. name: "test-nft",
  91. symbol: "TEST",
  92. uri: "https://nothing.com",
  93. sellerFeeBasisPoints: 1,
  94. creators: null,
  95. }),
  96. updateAuthority: authority,
  97. mint: nftMintClient.publicKey,
  98. mintAuthority: authority,
  99. }
  100. );
  101. await getProvider().sendAndConfirm(tx);
  102. });
  103. it("Creates token accounts for the NFT", async () => {
  104. // Create token accounts for the mint.
  105. buyerTokenAccount = await nftMintClient.createAssociatedTokenAccount(
  106. buyerWallet.publicKey
  107. );
  108. sellerTokenAccount = await nftMintClient.createAssociatedTokenAccount(
  109. sellerWallet.publicKey
  110. );
  111. // Initialize the seller's account with a single token.
  112. await nftMintClient.mintTo(sellerTokenAccount, authority, [], 1);
  113. });
  114. it("Creates auction house program clients representing the buyer and seller", async () => {
  115. authorityClient = new Program<AuctionHouse>(IDL, getProvider());
  116. sellerClient = new Program<AuctionHouse>(
  117. IDL,
  118. new AnchorProvider(
  119. getProvider().connection,
  120. new Wallet(sellerWallet),
  121. AnchorProvider.defaultOptions()
  122. )
  123. );
  124. buyerClient = new Program<AuctionHouse>(
  125. IDL,
  126. new AnchorProvider(
  127. getProvider().connection,
  128. new Wallet(buyerWallet),
  129. AnchorProvider.defaultOptions()
  130. )
  131. );
  132. });
  133. it("Initializes constants", async () => {
  134. const [_auctionHouse] = await PublicKey.findProgramAddress(
  135. [PREFIX, authority.toBuffer(), treasuryMint.toBuffer()],
  136. AUCTION_HOUSE_PROGRAM_ID
  137. );
  138. const [_auctionHouseFeeAccount] = await PublicKey.findProgramAddress(
  139. [PREFIX, _auctionHouse.toBuffer(), FEE_PAYER],
  140. AUCTION_HOUSE_PROGRAM_ID
  141. );
  142. auctionHouse = _auctionHouse;
  143. auctionHouseFeeAccount = _auctionHouseFeeAccount;
  144. });
  145. it("Funds the buyer with lamports so that it can bid", async () => {
  146. const tx = new Transaction();
  147. tx.add(
  148. SystemProgram.transfer({
  149. fromPubkey: authority,
  150. toPubkey: buyerWallet.publicKey,
  151. lamports: 20 * 10 ** 9,
  152. })
  153. );
  154. tx.add(
  155. SystemProgram.transfer({
  156. fromPubkey: authority,
  157. toPubkey: sellerWallet.publicKey,
  158. lamports: 20 * 10 ** 9,
  159. })
  160. );
  161. tx.add(
  162. SystemProgram.transfer({
  163. fromPubkey: authority,
  164. toPubkey: auctionHouseFeeAccount,
  165. lamports: 100 * 10 ** 9,
  166. })
  167. );
  168. const txSig = await getProvider().sendAndConfirm(tx);
  169. console.log("fund buyer:", txSig);
  170. });
  171. it("Creates an auction house", async () => {
  172. const sellerFeeBasisPoints = 1;
  173. const requiresSignOff = true;
  174. const canChangeSalePrice = true;
  175. const txSig = await authorityClient.methods
  176. .createAuctionHouse(
  177. sellerFeeBasisPoints,
  178. requiresSignOff,
  179. canChangeSalePrice
  180. )
  181. .accounts({
  182. treasuryMint,
  183. authority,
  184. feeWithdrawalDestination,
  185. treasuryWithdrawalDestination,
  186. treasuryWithdrawalDestinationOwner,
  187. })
  188. .rpc();
  189. console.log("createAuctionHouse:", txSig);
  190. });
  191. it("Deposits into an escrow account", async () => {
  192. const amount = new BN(10 * 10 ** 9);
  193. const txSig = await buyerClient.methods
  194. .deposit(amount)
  195. .accounts({
  196. paymentAccount: buyerWallet.publicKey,
  197. transferAuthority: buyerWallet.publicKey,
  198. treasuryMint,
  199. authority,
  200. })
  201. .signers([authorityKeypair])
  202. .rpc();
  203. console.log("deposit:", txSig);
  204. });
  205. it("Withdraws from an escrow account", async () => {
  206. const amount = new BN(10 * 10 ** 9);
  207. const txSig = await buyerClient.methods
  208. .withdraw(amount)
  209. .accounts({
  210. wallet: buyerWallet.publicKey,
  211. receiptAccount: buyerWallet.publicKey,
  212. treasuryMint,
  213. authority,
  214. })
  215. .signers([authorityKeypair])
  216. .rpc();
  217. console.log("withdraw:", txSig);
  218. });
  219. it("Posts an offer", async () => {
  220. const buyerPrice = new u64(2 * 10 ** 9);
  221. const tokenSize = new u64(1);
  222. const txSig = await sellerClient.methods
  223. .sell(buyerPrice, tokenSize)
  224. .accounts({
  225. wallet: sellerWallet.publicKey,
  226. tokenAccount: sellerTokenAccount,
  227. metadata,
  228. authority,
  229. treasuryMint,
  230. })
  231. .signers([authorityKeypair])
  232. .rpc();
  233. console.log("sell:", txSig);
  234. });
  235. it("Cancels an offer", async () => {
  236. const buyerPrice = new u64(2 * 10 ** 9);
  237. const tokenSize = new u64(1);
  238. const txSig = await sellerClient.methods
  239. .cancel(buyerPrice, tokenSize)
  240. .accounts({
  241. wallet: sellerWallet.publicKey,
  242. tokenAccount: sellerTokenAccount,
  243. authority,
  244. treasuryMint,
  245. })
  246. .signers([authorityKeypair])
  247. .rpc();
  248. console.log("cancel:", txSig);
  249. });
  250. it("Posts an offer (again)", async () => {
  251. const buyerPrice = new u64(2 * 10 ** 9);
  252. const tokenSize = new u64(1);
  253. const txSig = await sellerClient.methods
  254. .sell(buyerPrice, tokenSize)
  255. .accounts({
  256. wallet: sellerWallet.publicKey,
  257. tokenAccount: sellerTokenAccount,
  258. metadata,
  259. authority,
  260. treasuryMint,
  261. })
  262. .signers([authorityKeypair])
  263. .rpc();
  264. console.log("sell:", txSig);
  265. });
  266. it("Posts a bid", async () => {
  267. const buyerPrice = new u64(2 * 10 ** 9);
  268. const tokenSize = new u64(1);
  269. const txSig = await buyerClient.methods
  270. .buy(buyerPrice, tokenSize)
  271. .accounts({
  272. wallet: buyerWallet.publicKey,
  273. paymentAccount: buyerWallet.publicKey,
  274. transferAuthority: buyerWallet.publicKey,
  275. treasuryMint,
  276. tokenAccount: sellerTokenAccount,
  277. metadata,
  278. authority,
  279. })
  280. .signers([authorityKeypair])
  281. .rpc();
  282. console.log("buy:", txSig);
  283. });
  284. it("Executes a trade", async () => {
  285. const [buyerEscrow] = await PublicKey.findProgramAddress(
  286. [PREFIX, auctionHouse.toBuffer(), buyerWallet.publicKey.toBuffer()],
  287. AUCTION_HOUSE_PROGRAM_ID
  288. );
  289. const [auctionHouseTreasury] = await PublicKey.findProgramAddress(
  290. [PREFIX, auctionHouse.toBuffer(), TREASURY],
  291. AUCTION_HOUSE_PROGRAM_ID
  292. );
  293. const airdropSig = await authorityClient.provider.connection.requestAirdrop(
  294. auctionHouseTreasury,
  295. 890880
  296. );
  297. await authorityClient.provider.connection.confirmTransaction(airdropSig);
  298. // Before state.
  299. const beforeEscrowState =
  300. await authorityClient.provider.connection.getAccountInfo(buyerEscrow);
  301. const beforeSeller =
  302. await authorityClient.provider.connection.getAccountInfo(
  303. sellerWallet.publicKey
  304. );
  305. // Execute trade.
  306. const buyerPrice = new u64(2 * 10 ** 9);
  307. const tokenSize = new u64(1);
  308. const txSig = await authorityClient.methods
  309. .executeSale(buyerPrice, tokenSize)
  310. .accounts({
  311. buyer: buyerWallet.publicKey,
  312. seller: sellerWallet.publicKey,
  313. tokenAccount: sellerTokenAccount,
  314. tokenMint: nftMintClient.publicKey,
  315. metadata,
  316. treasuryMint,
  317. escrowPaymentAccount: buyerEscrow,
  318. sellerPaymentReceiptAccount: sellerWallet.publicKey,
  319. buyerReceiptTokenAccount: buyerTokenAccount,
  320. authority,
  321. })
  322. .rpc();
  323. console.log("executeSale:", txSig);
  324. // After state.
  325. const afterEscrowState =
  326. await authorityClient.provider.connection.getAccountInfo(buyerEscrow);
  327. const afterSeller =
  328. await authorityClient.provider.connection.getAccountInfo(
  329. sellerWallet.publicKey
  330. );
  331. // Assertions.
  332. assert.ok(afterEscrowState === null);
  333. assert.ok(beforeEscrowState.lamports === 2 * 10 ** 9);
  334. assert.ok(1999800000.0 === afterSeller.lamports - beforeSeller.lamports); // 1bp fee.
  335. });
  336. it("Withdraws from the fee account", async () => {
  337. const txSig = await authorityClient.methods
  338. .withdrawFromFee(new u64(1))
  339. .accounts({
  340. authority,
  341. treasuryMint,
  342. feeWithdrawalDestination,
  343. })
  344. .rpc();
  345. console.log("withdrawFromFee:", txSig);
  346. });
  347. it("Withdraws from the treasury account", async () => {
  348. const txSig = await authorityClient.methods
  349. .withdrawFromTreasury(new u64(1))
  350. .accounts({
  351. treasuryMint,
  352. authority,
  353. treasuryWithdrawalDestination,
  354. })
  355. .rpc();
  356. console.log("txSig:", txSig);
  357. });
  358. it("Updates an auction house", async () => {
  359. const sellerFeeBasisPoints = 2;
  360. const requiresSignOff = true;
  361. const canChangeSalePrice = null;
  362. const tx = new Transaction();
  363. tx.add(
  364. await authorityClient.methods
  365. .updateAuctionHouse(
  366. sellerFeeBasisPoints,
  367. requiresSignOff,
  368. canChangeSalePrice
  369. )
  370. .accounts({
  371. treasuryMint,
  372. payer: authority,
  373. authority,
  374. newAuthority: authority,
  375. feeWithdrawalDestination,
  376. treasuryWithdrawalDestination,
  377. treasuryWithdrawalDestinationOwner,
  378. })
  379. .instruction()
  380. );
  381. const txSig = await authorityClient.provider.sendAndConfirm(tx);
  382. console.log("updateAuctionHouse:", txSig);
  383. const newAh = await authorityClient.account.auctionHouse.fetch(
  384. auctionHouse
  385. );
  386. assert.ok(newAh.sellerFeeBasisPoints === 2);
  387. });
  388. });