// mint-and-redeem.ts
import {
Connection, Keypair, PublicKey, Transaction,
} from "@solana/web3.js";
import { AnchorProvider, Program, Wallet, BN } from "@coral-xyz/anchor";
import {
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID,
getAssociatedTokenAddressSync,
createAssociatedTokenAccountIdempotentInstruction,
} from "@solana/spl-token";
import fs from "fs";
import path from "path";
import mintRedeemIdl from "./idl/mint_redeem.json";
import faucetIdl from "./idl/faucet.json";
// ─────────────────────────────────────────────────────────────────
const RPC_URL = "https://api.devnet.solana.com";
const SYMBOL = "QQQ";
const CUSDC_MINT = new PublicKey("B1c5xBYkp7AAemYhcu4VuH4CU4sPJDDuG2iuv6ts38uE");
const MINT_AMT = new BN(100_000_000); // 100 cUSDC (6 decimals)
// ─────────────────────────────────────────────────────────────────
async function main() {
const conn = new Connection(RPC_URL, "confirmed");
const kp = Keypair.fromSecretKey(new Uint8Array(JSON.parse(
fs.readFileSync(path.join(process.env.HOME!, ".config/solana/id.json"), "utf8"),
)));
const wallet = new Wallet(kp);
const provider = new AnchorProvider(conn, wallet, { commitment: "confirmed" });
const mr = new Program(mintRedeemIdl as any, provider);
const faucet = new Program(faucetIdl as any, provider);
// 1. Derive market and load it
const [marketPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("market"), Buffer.from(SYMBOL)], mr.programId,
);
const market = await mr.account.market.fetch(marketPDA);
console.log(`✓ Market loaded: ${SYMBOL} risk=${Object.keys(market.riskState)[0]}`);
// 2. ATAs
const userCusdc = getAssociatedTokenAddressSync(CUSDC_MINT, kp.publicKey);
const userLong = getAssociatedTokenAddressSync(market.longMint, kp.publicKey);
const userShort = getAssociatedTokenAddressSync(market.shortMint, kp.publicKey);
const devCusdc = getAssociatedTokenAddressSync(CUSDC_MINT, market.feeRecipient);
// 3. Drip cUSDC if balance < 100
const balance = await conn.getTokenAccountBalance(userCusdc).catch(() => null);
const lamports = balance ? Number(balance.value.amount) : 0;
if (lamports < 100_000_000) {
console.log("⌛ Dripping cUSDC from faucet…");
const [faucetState] = PublicKey.findProgramAddressSync(
[Buffer.from("faucet"), CUSDC_MINT.toBuffer()], faucet.programId,
);
const [dripRecord] = PublicKey.findProgramAddressSync(
[Buffer.from("drip"), CUSDC_MINT.toBuffer(), kp.publicKey.toBuffer()], faucet.programId,
);
const dripIx = await faucet.methods
.drip()
.accounts({
faucetState, dripRecord,
mint: CUSDC_MINT, recipient: kp.publicKey, recipientAta: userCusdc,
tokenProgram: TOKEN_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
})
.instruction();
const tx = new Transaction().add(
createAssociatedTokenAccountIdempotentInstruction(
kp.publicKey, userCusdc, kp.publicKey, CUSDC_MINT,
),
dripIx,
);
const sig = await provider.sendAndConfirm(tx);
console.log(` drip tx: ${sig}`);
}
// 4. Pre-create L and S ATAs idempotently
const preIxs = [
createAssociatedTokenAccountIdempotentInstruction(kp.publicKey, userLong, kp.publicKey, market.longMint),
createAssociatedTokenAccountIdempotentInstruction(kp.publicKey, userShort, kp.publicKey, market.shortMint),
createAssociatedTokenAccountIdempotentInstruction(kp.publicKey, devCusdc, market.feeRecipient, CUSDC_MINT),
];
// 5. Mint paired
const mintIx = await mr.methods
.mintPaired(MINT_AMT)
.accounts({
market: marketPDA,
user: kp.publicKey,
userCollateral: userCusdc,
userLong, userShort,
longMint: market.longMint, shortMint: market.shortMint,
collateralVault: market.collateralVault,
devTokenAccount: devCusdc,
oracleAddress: market.oracleAddress,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction();
const sig1 = await provider.sendAndConfirm(new Transaction().add(...preIxs, mintIx));
console.log(`✓ mint_paired: ${sig1}`);
const longBal = await conn.getTokenAccountBalance(userLong);
const shortBal = await conn.getTokenAccountBalance(userShort);
console.log(` +${longBal.value.uiAmountString} ${SYMBOL}L`);
console.log(` +${shortBal.value.uiAmountString} ${SYMBOL}S`);
// 6. Wait one block, then redeem the full position
await new Promise(r => setTimeout(r, 2000));
const lToBurn = new BN(longBal.value.amount);
const sToBurn = new BN(shortBal.value.amount);
const redeemIx = await mr.methods
.redeemPaired(lToBurn, sToBurn)
.accounts({
market: marketPDA,
user: kp.publicKey,
userCollateral: userCusdc,
userLong, userShort,
longMint: market.longMint, shortMint: market.shortMint,
collateralVault: market.collateralVault,
devTokenAccount: devCusdc,
oracleAddress: market.oracleAddress,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction();
const sig2 = await provider.sendAndConfirm(new Transaction().add(redeemIx));
console.log(`✓ redeem_paired: ${sig2}`);
const finalCusdc = await conn.getTokenAccountBalance(userCusdc);
console.log(` cUSDC balance: ${finalCusdc.value.uiAmountString}`);
}
main().catch(e => { console.error(e); process.exit(1); });