import * as anchor from "@coral-xyz/anchor"; import { SystemProgram, Transaction } from "@solana/web3.js"; import { expect } from "chai"; import fetch from "node-fetch"; import { getTestContext } from "../setup"; describe("Publish Auction (API)", () => { it("Local Admin Germany Publishes Auction with 1000 EUA", async () => { const ctx = getTestContext(); const { connection, program, axumBaseUrl, localAdminGermany, localAdminGermanyPda, localAdminGermanyEuaVault } = ctx; // Derive primary market PDA const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("primary_market")], program.programId ); // Set auction times: start in 5 seconds, close in 1 hour const currentTime = Math.floor(Date.now() / 1000); const startTime = currentTime + 5; const closeTime = startTime + 3600; // Step 1: Create API request payload (NO auction PDA - backend will calculate it) const request = { primary_market: primaryMarketPda.toBase58(), local_admin: localAdminGermanyPda.toBase58(), local_admin_eua_vault: localAdminGermanyEuaVault.toBase58(), local_admin_pubkey: localAdminGermany.publicKey.toBase58(), eua_volume: 1000, // Must be divisible by 500 start_time: startTime, close_time: closeTime, }; console.log("Calling API to create publish auction transaction..."); console.log("Note: Backend will calculate the auction PDA using on-chain total_auctions"); // Step 2: Call Axum API to get unsigned transaction const createTxResponse = await fetch( `${axumBaseUrl}/api/primary-market/publish-auction`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), } ); if (!createTxResponse.ok) { const error = await createTxResponse.text(); throw new Error(`Failed to create transaction: ${error}`); } const { transaction_base64, message, auction_pda } = await createTxResponse.json(); console.log(`API Response: ${message}`); console.log(`Auction PDA (calculated by backend): ${auction_pda}`); const auctionPda = new anchor.web3.PublicKey(auction_pda); // Step 3: Deserialize and sign transaction const txBuffer = Buffer.from(transaction_base64, "base64"); const transaction = Transaction.from(txBuffer); console.log("Transaction signer (expected):"); console.log(" Local Admin Germany:", localAdminGermany.publicKey.toBase58()); // Sign with localAdminGermany (the only required signer) transaction.sign(localAdminGermany); console.log("Transaction signed locally"); // Step 4: Submit signed transaction to backend const signedTxBase64 = transaction.serialize().toString("base64"); const submitTxResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ transaction_base64: signedTxBase64 }), }); if (!submitTxResponse.ok) { const error = await submitTxResponse.text(); throw new Error(`Failed to submit transaction: ${error}`); } const { signature } = await submitTxResponse.json(); console.log(`✓ Transaction signature: ${signature}`); // Step 5: Verify on-chain state - AUCTION CREATED const auction = await program.account.auction.fetch(auctionPda); expect(auction.localAdmin.equals(localAdminGermanyPda)).to.be.true; expect(auction.euaVolumeOffered.toNumber()).to.equal(1000); expect(auction.startTime.toNumber()).to.equal(startTime); expect(auction.closeTime.toNumber()).to.equal(closeTime); expect(auction.status).to.deep.equal({ published: {} }); expect(auction.totalBids).to.equal(0); console.log("✓ Auction account state verified"); console.log(` - Auction ID: ${auction.auctionId.toNumber()}`); console.log(` - EUA Volume Offered: ${auction.euaVolumeOffered.toNumber()}`); console.log(` - Lot Size: ${auction.lotSize}`); console.log(` - Start Time: ${new Date(startTime * 1000).toISOString()}`); console.log(` - Close Time: ${new Date(closeTime * 1000).toISOString()}`); console.log(` - Status: Published`); console.log(` - Total Bids: ${auction.totalBids}`); // Step 6: Verify local admin state updated const localAdmin = await program.account.localAdmin.fetch(localAdminGermanyPda); expect(localAdmin.activeAuction).to.not.be.null; expect(localAdmin.activeAuction.equals(auctionPda)).to.be.true; console.log(`✓ Local Admin updated:`); console.log(` - Active Auction: ${localAdmin.activeAuction.toBase58()}`); console.log(` - Total Auctions: ${localAdmin.totalAuctions.toNumber()}`); // Step 7: Verify events/logs const txDetails = await connection.getTransaction(signature, { commitment: "confirmed", maxSupportedTransactionVersion: 0, }); if (txDetails?.meta?.logMessages) { const logs = txDetails.meta.logMessages; const hasPublishLog = logs.some((log) => log.includes("Auction published") || log.includes("AuctionPublished") ); if (hasPublishLog) { console.log("✓ Auction published event found in logs"); } } console.log("✓ Auction published successfully via API"); // Store auction PDA for subsequent tests const { setTestContext } = require("../setup"); setTestContext({ auctionPda, auctionId: auction.auctionId.toNumber(), }); }); // it("Should Reject Publishing Auction with Invalid EUA Volume", async () => { // const ctx = getTestContext(); // const { // axumBaseUrl, // localAdminGermany, // localAdminGermanyPda, // localAdminGermanyEuaVault, // } = ctx; // // const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync( // [Buffer.from("primary_market")], // ctx.program.programId // ); // // const currentTime = Math.floor(Date.now() / 1000); // // const request = { // primary_market: primaryMarketPda.toBase58(), // local_admin: localAdminGermanyPda.toBase58(), // local_admin_eua_vault: localAdminGermanyEuaVault.toBase58(), // local_admin_pubkey: localAdminGermany.publicKey.toBase58(), // eua_volume: 333, // Not divisible by 500 // start_time: currentTime + 5, // close_time: currentTime + 3605, // }; // // const createTxResponse = await fetch( // `${axumBaseUrl}/api/primary-market/publish-auction`, // { // method: "POST", // headers: { "Content-Type": "application/json" }, // body: JSON.stringify(request), // } // ); // // expect(createTxResponse.ok).to.be.false; // const errorText = await createTxResponse.text(); // console.log("✓ Correctly rejected invalid EUA volume:", errorText); // expect(errorText).to.include("divisible by 500"); // }); // // it("Should Reject Publishing Auction with Past Start Time", async () => { // const ctx = getTestContext(); // const { // axumBaseUrl, // localAdminGermany, // localAdminGermanyPda, // localAdminGermanyEuaVault, // } = ctx; // // const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync( // [Buffer.from("primary_market")], // ctx.program.programId // ); // // const currentTime = Math.floor(Date.now() / 1000); // // const request = { // primary_market: primaryMarketPda.toBase58(), // local_admin: localAdminGermanyPda.toBase58(), // local_admin_eua_vault: localAdminGermanyEuaVault.toBase58(), // local_admin_pubkey: localAdminGermany.publicKey.toBase58(), // eua_volume: 1000, // start_time: currentTime - 100, // Past time // close_time: currentTime + 3600, // }; // // const createTxResponse = await fetch( // `${axumBaseUrl}/api/primary-market/publish-auction`, // { // method: "POST", // headers: { "Content-Type": "application/json" }, // body: JSON.stringify(request), // } // ); // // expect(createTxResponse.ok).to.be.false; // const errorText = await createTxResponse.text(); // console.log("✓ Correctly rejected past start time:", errorText); // expect(errorText).to.include("future"); // }); // // it("Should Reject Publishing Auction When Local Admin Already Has Active Auction", async () => { // const ctx = getTestContext(); // const { // axumBaseUrl, // localAdminGermany, // localAdminGermanyPda, // localAdminGermanyEuaVault, // } = ctx; // // const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync( // [Buffer.from("primary_market")], // ctx.program.programId // ); // // const currentTime = Math.floor(Date.now() / 1000); // // const request = { // primary_market: primaryMarketPda.toBase58(), // local_admin: localAdminGermanyPda.toBase58(), // local_admin_eua_vault: localAdminGermanyEuaVault.toBase58(), // local_admin_pubkey: localAdminGermany.publicKey.toBase58(), // eua_volume: 500, // start_time: currentTime + 5, // close_time: currentTime + 3605, // }; // // const createTxResponse = await fetch( // `${axumBaseUrl}/api/primary-market/publish-auction`, // { // method: "POST", // headers: { "Content-Type": "application/json" }, // body: JSON.stringify(request), // } // ); // // expect(createTxResponse.ok).to.be.false; // const errorText = await createTxResponse.text(); // console.log("✓ Correctly rejected duplicate active auction:", errorText); // expect(errorText).to.include("active auction"); // }); // // it("Should Reject Publishing Auction with Insufficient Balance", async () => { // const ctx = getTestContext(); // const { // axumBaseUrl, // localAdminGermany, // localAdminGermanyPda, // localAdminGermanyEuaVault, // } = ctx; // // const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync( // [Buffer.from("primary_market")], // ctx.program.programId // ); // // const currentTime = Math.floor(Date.now() / 1000); // // const request = { // primary_market: primaryMarketPda.toBase58(), // local_admin: localAdminGermanyPda.toBase58(), // local_admin_eua_vault: localAdminGermanyEuaVault.toBase58(), // local_admin_pubkey: localAdminGermany.publicKey.toBase58(), // eua_volume: 5000, // bigger than 3000 // start_time: currentTime + 5, // close_time: currentTime + 3605, // }; // // const createTxResponse = await fetch( // `${axumBaseUrl}/api/primary-market/publish-auction`, // { // method: "POST", // headers: { "Content-Type": "application/json" }, // body: JSON.stringify(request), // } // ); // // expect(createTxResponse.ok).to.be.false; // const errorText = await createTxResponse.text(); // console.log("✓ Correctly rejected insufficient balance:", errorText); // expect(errorText).to.include("insufficient"); // }); });