import * as anchor from "@coral-xyz/anchor"; import { TOKEN_PROGRAM_ID, createAssociatedTokenAccount, mintTo, getAccount, } from "@solana/spl-token"; import { Transaction } from "@solana/web3.js"; import { expect } from "chai"; import fetch from "node-fetch"; import { getTestContext, setTestContext } from "../setup"; describe("Deposit USDC (API)", () => { it("Setup: Mint USDC to Company Admins", async () => { const ctx = getTestContext(); const { connection, globalAdmin, usdcMint, company1Admin, company2Admin, } = ctx; console.log("Creating USDC ATAs for company admins..."); const company1AdminUsdcAta = await createAssociatedTokenAccount( connection, globalAdmin, usdcMint, company1Admin.publicKey ); const company2AdminUsdcAta = await createAssociatedTokenAccount( connection, globalAdmin, usdcMint, company2Admin.publicKey ); console.log(`Company 1 Admin USDC ATA: ${company1AdminUsdcAta.toBase58()}`); console.log(`Company 2 Admin USDC ATA: ${company2AdminUsdcAta.toBase58()}`); // Mint USDC to company admins (simulating they bought USDC) await mintTo( connection, globalAdmin, usdcMint, company1AdminUsdcAta, globalAdmin, 2_000_000_000 // 2000 USDC (with 6 decimals) ); await mintTo( connection, globalAdmin, usdcMint, company2AdminUsdcAta, globalAdmin, 2_000_000_000 // 2000 USDC ); // Verify balances const company1Balance = await getAccount(connection, company1AdminUsdcAta); const company2Balance = await getAccount(connection, company2AdminUsdcAta); expect(company1Balance.amount.toString()).to.equal("2000000000"); expect(company2Balance.amount.toString()).to.equal("2000000000"); // Store in context setTestContext({ company1AdminUsdcAta, company2AdminUsdcAta, }); console.log("✓ USDC minted to company admins"); console.log(` Company 1 Admin: 2000 USDC`); console.log(` Company 2 Admin: 2000 USDC`); }); it("Company 1 Deposits 500 USDC to Vault via API", async () => { const ctx = getTestContext(); const { connection, program, axumBaseUrl, company1Pda, company1UsdcVault, company1AdminUsdcAta, company1Admin, db, } = ctx; const depositAmount = 500_000_000; // 500 USDC (6 decimals) // ============================================================ // STEP 1: Check DB State Before // ============================================================ console.log("Checking database state before deposit..."); const companyBefore = await db.getCompany(company1Pda.toBase58()); const usdcLogsBefore = await db.getUsdcLogs(company1Pda.toBase58()); const totalDepositedBefore = await db.getTotalUsdcDeposited(company1Pda.toBase58()); console.log(`✓ Company 1 USDC balance before: ${companyBefore.usdc_balance}`); console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`); console.log(`✓ Total deposited before: ${totalDepositedBefore}`); // ============================================================ // STEP 2: Create API request payload // ============================================================ const request = { company: company1Pda.toBase58(), company_admin_usdc_ata: company1AdminUsdcAta.toBase58(), company_usdc_vault: company1UsdcVault.toBase58(), company_admin: company1Admin.publicKey.toBase58(), token_program: TOKEN_PROGRAM_ID.toBase58(), amount: depositAmount, }; console.log("Calling API to create deposit USDC transaction for Company 1..."); // ============================================================ // STEP 3: Call Axum API to get unsigned transaction // ============================================================ const createTxResponse = await fetch( `${axumBaseUrl}/api/secondary-market/deposit-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}`); } const { transaction_base64, message } = await createTxResponse.json(); 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(company1Admin); console.log("Transaction signed locally by Company 1 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 vaultAccount = await getAccount(connection, company1UsdcVault); expect(vaultAccount.amount.toString()).to.equal("500000000"); const usdcDivisor = 1_000_000; console.log("✓ Company 1 deposited USDC"); console.log(` Vault balance: ${Number(vaultAccount.amount) / usdcDivisor} USDC`); const adminAtaAccount = await getAccount(connection, company1AdminUsdcAta); expect(adminAtaAccount.amount.toString()).to.equal("1500000000"); // 2000 - 500 = 1500 console.log(` Admin ATA balance: ${Number(adminAtaAccount.amount) / usdcDivisor} USDC`); // ============================================================ // STEP 7: Verify DB State After (Wait for Event Handler) // ============================================================ console.log("Verifying database state after deposit..."); // Wait for USDC log to be created const logCreated = await db.waitForUsdcLog( company1Pda.toBase58(), 'Deposit', BigInt(depositAmount), 5000 ); expect(logCreated).to.be.true; // Wait for company balance to be updated const balanceUpdated = await db.waitForCompanyUsdcBalance( company1Pda.toBase58(), depositAmount.toString(), 5000 ); expect(balanceUpdated).to.be.true; // Verify USDC log details const usdcLogsAfter = await db.getUsdcLogs(company1Pda.toBase58()); expect(usdcLogsAfter.length).to.be.greaterThan(usdcLogsBefore.length); const latestLog = usdcLogsAfter[0]; expect(latestLog.company_pubkey).to.equal(company1Pda.toBase58()); expect(latestLog.operation_type).to.equal('Deposit'); expect(latestLog.amount).to.equal(depositAmount.toString()); expect(latestLog.new_vault_balance).to.equal(depositAmount.toString()); console.log(`✓ USDC deposit log verified (Log ID: ${latestLog.id})`); // Verify company balance in DB const companyAfter = await db.getCompany(company1Pda.toBase58()); expect(companyAfter.usdc_balance).to.equal(depositAmount.toString()); console.log(`✓ Company 1 USDC balance in DB: ${companyAfter.usdc_balance}`); // Verify total deposited const totalDepositedAfter = await db.getTotalUsdcDeposited(company1Pda.toBase58()); expect(totalDepositedAfter).to.equal(BigInt(depositAmount)); console.log(`✓ Total USDC deposited: ${totalDepositedAfter}`); console.log("✓ Database state matches on-chain state"); }); it("Company 2 Deposits 800 USDC to Vault via API", async () => { const ctx = getTestContext(); const { connection, program, axumBaseUrl, company2Pda, company2UsdcVault, company2AdminUsdcAta, company2Admin, db, } = ctx; const depositAmount = 800_000_000; // 800 USDC (6 decimals) // ============================================================ // STEP 1: Check DB State Before // ============================================================ console.log("Checking database state before deposit..."); const companyBefore = await db.getCompany(company2Pda.toBase58()); const usdcLogsBefore = await db.getUsdcLogs(company2Pda.toBase58()); const totalDepositedBefore = await db.getTotalUsdcDeposited(company2Pda.toBase58()); console.log(`✓ Company 2 USDC balance before: ${companyBefore.usdc_balance}`); console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`); console.log(`✓ Total deposited before: ${totalDepositedBefore}`); // ============================================================ // 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: depositAmount, }; console.log("Calling API to create deposit USDC transaction for Company 2..."); // ============================================================ // STEP 3: Call Axum API to get unsigned transaction // ============================================================ const createTxResponse = await fetch( `${axumBaseUrl}/api/secondary-market/deposit-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}`); } const { transaction_base64, message } = await createTxResponse.json(); 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 vaultAccount = await getAccount(connection, company2UsdcVault); expect(vaultAccount.amount.toString()).to.equal("800000000"); const usdcDivisor = 1_000_000; console.log("✓ Company 2 deposited USDC"); console.log(` Vault balance: ${Number(vaultAccount.amount) / usdcDivisor} USDC`); const adminAtaAccount = await getAccount(connection, company2AdminUsdcAta); expect(adminAtaAccount.amount.toString()).to.equal("1200000000"); // 2000 - 800 = 1200 console.log(` Admin ATA balance: ${Number(adminAtaAccount.amount) / usdcDivisor} USDC`); // ============================================================ // STEP 7: Verify DB State After (Wait for Event Handler) // ============================================================ console.log("Verifying database state after deposit..."); // Wait for USDC log to be created const logCreated = await db.waitForUsdcLog( company2Pda.toBase58(), 'Deposit', BigInt(depositAmount), 5000 ); expect(logCreated).to.be.true; // Wait for company balance to be updated const balanceUpdated = await db.waitForCompanyUsdcBalance( company2Pda.toBase58(), depositAmount.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]; expect(latestLog.company_pubkey).to.equal(company2Pda.toBase58()); expect(latestLog.operation_type).to.equal('Deposit'); expect(latestLog.amount).to.equal(depositAmount.toString()); expect(latestLog.new_vault_balance).to.equal(depositAmount.toString()); console.log(`✓ USDC deposit log verified (Log ID: ${latestLog.id})`); // Verify company balance in DB const companyAfter = await db.getCompany(company2Pda.toBase58()); expect(companyAfter.usdc_balance).to.equal(depositAmount.toString()); console.log(`✓ Company 2 USDC balance in DB: ${companyAfter.usdc_balance}`); // Verify total deposited const totalDepositedAfter = await db.getTotalUsdcDeposited(company2Pda.toBase58()); expect(totalDepositedAfter).to.equal(BigInt(depositAmount)); console.log(`✓ Total USDC deposited: ${totalDepositedAfter}`); console.log("✓ Database state matches on-chain state"); }); it("Verify Final Balances", async () => { const ctx = getTestContext(); const { connection, company1Pda, company2Pda, company1UsdcVault, company2UsdcVault, company1AdminUsdcAta, company2AdminUsdcAta, db, } = ctx; // On-chain verification const company1Vault = await getAccount(connection, company1UsdcVault); const company2Vault = await getAccount(connection, company2UsdcVault); const company1AdminAta = await getAccount(connection, company1AdminUsdcAta); const company2AdminAta = await getAccount(connection, company2AdminUsdcAta); const usdcDivisor = 1_000_000; console.log("\n=== Final On-Chain USDC Balances ==="); console.log(`Company 1 Vault: ${Number(company1Vault.amount) / usdcDivisor} USDC`); console.log(`Company 1 Admin ATA: ${Number(company1AdminAta.amount) / usdcDivisor} USDC`); console.log(`Company 2 Vault: ${Number(company2Vault.amount) / usdcDivisor} USDC`); console.log(`Company 2 Admin ATA: ${Number(company2AdminAta.amount) / usdcDivisor} USDC`); expect(company1Vault.amount.toString()).to.equal("500000000"); expect(company2Vault.amount.toString()).to.equal("800000000"); expect(company1AdminAta.amount.toString()).to.equal("1500000000"); expect(company2AdminAta.amount.toString()).to.equal("1200000000"); // Database verification const company1Db = await db.getCompany(company1Pda.toBase58()); const company2Db = await db.getCompany(company2Pda.toBase58()); console.log("\n=== Final Database USDC Balances ==="); console.log(`Company 1 DB Balance: ${Number(company1Db.usdc_balance) / usdcDivisor} USDC`); console.log(`Company 2 DB Balance: ${Number(company2Db.usdc_balance) / usdcDivisor} USDC`); expect(company1Db.usdc_balance).to.equal("500000000"); expect(company2Db.usdc_balance).to.equal("800000000"); // Verify deposit logs const company1Logs = await db.getUsdcLogsByType(company1Pda.toBase58(), 'Deposit'); const company2Logs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Deposit'); console.log(`\nCompany 1 Deposit Logs: ${company1Logs.length}`); console.log(`Company 2 Deposit Logs: ${company2Logs.length}`); expect(company1Logs.length).to.equal(1); expect(company2Logs.length).to.equal(1); console.log("✓ All balances verified successfully"); console.log("✓ Database state matches on-chain state"); }); });