import * as anchor from "@coral-xyz/anchor"; import { TOKEN_PROGRAM_ID, getAccount } from "@solana/spl-token"; import { expect } from "chai"; import { getTestContext } from "../setup"; import { Transaction } from "@solana/web3.js"; describe("Withdraw USDC (API)", () => { it("Total Withdraws USDC from Vault", async () => { const ctx = getTestContext(); const { connection, axumBaseUrl, company2Pda, company2UsdcVault, company2AdminUsdcAta, company2Admin, db, } = ctx; const withdrawAmount = 50_000_000; // 50 USDC // ============================================================ // STEP 1: Check DB State Before // ============================================================ console.log("Checking database state before withdrawal..."); const companyBefore = await db.getCompany(company2Pda.toBase58()); const usdcLogsBefore = await db.getUsdcLogs(company2Pda.toBase58()); const totalWithdrawnBefore = await db.getTotalUsdcWithdrawn(company2Pda.toBase58()); const vaultBefore = await getAccount(connection, company2UsdcVault); const ataBefore = await getAccount(connection, company2AdminUsdcAta); console.log(`✓ Company 2 USDC balance before: ${companyBefore.usdc_balance}`); console.log(`✓ Vault balance before: ${vaultBefore.amount}`); console.log(`✓ Admin ATA balance before: ${ataBefore.amount}`); console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`); console.log(`✓ Total withdrawn before: ${totalWithdrawnBefore}`); // ============================================================ // STEP 2: Create API request payload // ============================================================ const request = { company: company2Pda.toBase58(), company_admin_usdc_ata: company2AdminUsdcAta.toBase58(), company_usdc_vault: company2UsdcVault.toBase58(), company_admin: company2Admin.publicKey.toBase58(), token_program: TOKEN_PROGRAM_ID.toBase58(), amount: withdrawAmount, }; // ============================================================ // STEP 3: Call Axum API to get unsigned transaction // ============================================================ console.log("Calling API to create withdraw USDC transaction..."); const createTxResponse = await fetch( `${axumBaseUrl}/api/secondary-market/withdraw-usdc`, { 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}`); } type WithdrawUsdcResponse = { transaction_base64: string; message: string; }; const data = (await createTxResponse.json()) as WithdrawUsdcResponse; const { transaction_base64, message } = data; console.log(`API Response: ${message}`); // ============================================================ // STEP 4: Deserialize and sign transaction // ============================================================ const txBuffer = Buffer.from(transaction_base64, "base64"); const transaction = Transaction.from(txBuffer); transaction.sign(company2Admin); console.log("Transaction signed locally by Company 2 Admin"); // ============================================================ // STEP 5: 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 6: Verify On-Chain State // ============================================================ console.log("Verifying on-chain state..."); const vaultAfter = await getAccount(connection, company2UsdcVault); const ataAfter = await getAccount(connection, company2AdminUsdcAta); const expectedVaultBalance = Number(vaultBefore.amount) - withdrawAmount; const expectedAtaBalance = Number(ataBefore.amount) + withdrawAmount; expect(Number(vaultAfter.amount)).to.equal(expectedVaultBalance); expect(Number(ataAfter.amount)).to.equal(expectedAtaBalance); const usdcDivisor = 1_000_000; console.log(`✓ Vault balance after: ${Number(vaultAfter.amount) / usdcDivisor} USDC`); console.log(`✓ Admin ATA balance after: ${Number(ataAfter.amount) / usdcDivisor} USDC`); console.log(`✓ Withdrawn: ${withdrawAmount / usdcDivisor} USDC`); // ============================================================ // STEP 7: Verify DB State After (Wait for Event Handler) // ============================================================ console.log("Verifying database state after withdrawal..."); // Wait for USDC log to be created const logCreated = await db.waitForUsdcLog( company2Pda.toBase58(), 'Withdraw', BigInt(withdrawAmount), 5000 ); expect(logCreated).to.be.true; // Calculate expected new balance in DB const expectedDbBalance = Number(companyBefore.usdc_balance) - withdrawAmount; // Wait for company balance to be updated const balanceUpdated = await db.waitForCompanyUsdcBalance( company2Pda.toBase58(), expectedDbBalance.toString(), 5000 ); expect(balanceUpdated).to.be.true; // Verify USDC log details const usdcLogsAfter = await db.getUsdcLogs(company2Pda.toBase58()); expect(usdcLogsAfter.length).to.be.greaterThan(usdcLogsBefore.length); const latestLog = usdcLogsAfter[0]; // Most recent log expect(latestLog.company_pubkey).to.equal(company2Pda.toBase58()); expect(latestLog.operation_type).to.equal('Withdraw'); expect(latestLog.amount).to.equal(withdrawAmount.toString()); expect(latestLog.new_vault_balance).to.equal(expectedVaultBalance.toString()); console.log(`✓ USDC withdrawal log verified (Log ID: ${latestLog.id})`); // Verify company balance in DB const companyAfter = await db.getCompany(company2Pda.toBase58()); expect(companyAfter.usdc_balance).to.equal(expectedDbBalance.toString()); console.log(`✓ Company 2 USDC balance in DB: ${companyAfter.usdc_balance}`); // Verify total withdrawn const totalWithdrawnAfter = await db.getTotalUsdcWithdrawn(company2Pda.toBase58()); expect(totalWithdrawnAfter).to.equal(BigInt(withdrawAmount)); console.log(`✓ Total USDC withdrawn: ${totalWithdrawnAfter}`); // Verify all logs for Company 2 const depositLogs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Deposit'); const withdrawLogs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Withdraw'); console.log(`✓ Total deposit logs: ${depositLogs.length}`); console.log(`✓ Total withdraw logs: ${withdrawLogs.length}`); console.log("✓ Total withdrew 50 USDC"); console.log("✓ Database state matches on-chain state"); }); });