import {
AddressLookupTableAccount,
Connection,
Keypair,
PublicKey,
TransactionInstruction,
TransactionMessage,
VersionedTransaction,
} from "@solana/web3.js";
import BN from "bn.js";
import { getFlashBorrowIx, getFlashPaybackIx } from "@jup-ag/lend/flashloan";
import { getOperateIx } from "@jup-ag/lend/borrow";
import fs from "fs";
import path from "path";
const KEYPAIR_PATH = "/path/to/your/keypair.json";
const RPC_URL = "https://api.mainnet-beta.solana.com";
const LITE_API = "https://lite-api.jup.ag/swap/v1";
const VAULT_ID = 1;
const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const INITIAL_COLLATERAL = new BN(1_000_000_000); // 1 SOL @ 9 decimals
const BORROW_USDC = new BN(100_000_000); // 100 USDC @ 6 decimals
function loadKeypair(p: string): Keypair {
return Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync(path.resolve(p), "utf8"))));
}
function jupIxToTransactionInstruction(ix: {
programId: string;
accounts: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>;
data: string;
}): TransactionInstruction {
return new TransactionInstruction({
programId: new PublicKey(ix.programId),
keys: ix.accounts.map((a) => ({
pubkey: new PublicKey(a.pubkey),
isSigner: a.isSigner,
isWritable: a.isWritable,
})),
data: Buffer.from(ix.data, "base64"),
});
}
async function getAddressLookupTableAccounts(
connection: Connection,
keys: string[]
): Promise<AddressLookupTableAccount[]> {
if (!keys?.length) return [];
const infos = await connection.getMultipleAccountsInfo(keys.map((k) => new PublicKey(k)));
return infos.reduce((acc, info, i) => {
if (info) {
acc.push(
new AddressLookupTableAccount({
key: new PublicKey(keys[i]),
state: AddressLookupTableAccount.deserialize(info.data),
})
);
}
return acc;
}, [] as AddressLookupTableAccount[]);
}
async function main() {
const userKeypair = loadKeypair(KEYPAIR_PATH);
const connection = new Connection(RPC_URL, { commitment: "confirmed" });
const signer = userKeypair.publicKey;
const flashParams = { connection, signer, asset: USDC_MINT, amount: BORROW_USDC };
const [flashBorrowIx, flashPaybackIx, quoteResponse, latestBlockhash] = await Promise.all([
getFlashBorrowIx(flashParams),
getFlashPaybackIx(flashParams),
fetch(
`${LITE_API}/quote?inputMint=${USDC_MINT.toBase58()}&outputMint=${SOL_MINT.toBase58()}&amount=${BORROW_USDC.toString()}&slippageBps=100`
).then((r) => r.json()),
connection.getLatestBlockhash("confirmed"),
]);
if (quoteResponse.error || !quoteResponse.routePlan) {
throw new Error(quoteResponse.error ?? "No quote from Jupiter Lite");
}
const swapOutputAmount = new BN(quoteResponse.outAmount);
const supplyAmount = INITIAL_COLLATERAL.add(swapOutputAmount);
const { ixs: operateIxs, addressLookupTableAccounts } = await getOperateIx({
vaultId: VAULT_ID,
positionId: 0,
colAmount: supplyAmount,
debtAmount: BORROW_USDC,
connection,
signer,
});
const swapRes = await fetch(`${LITE_API}/swap-instructions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ quoteResponse, userPublicKey: signer.toBase58() }),
}).then((r) => r.json());
if (swapRes.error) {
throw new Error(swapRes.error ?? "No swap instructions from Jupiter Lite");
}
const swapIx = jupIxToTransactionInstruction(swapRes.swapInstruction);
const swapAlts = await getAddressLookupTableAccounts(connection, swapRes.addressLookupTableAddresses ?? []);
const allAlts = [...(addressLookupTableAccounts ?? []), ...swapAlts];
const message = new TransactionMessage({
payerKey: signer,
recentBlockhash: latestBlockhash.blockhash,
instructions: [flashBorrowIx, swapIx, ...operateIxs, flashPaybackIx],
}).compileToV0Message(allAlts);
const tx = new VersionedTransaction(message);
tx.sign([userKeypair]);
const signature = await connection.sendTransaction(tx, {
skipPreflight: false,
maxRetries: 3,
preflightCommitment: "confirmed",
});
await connection.confirmTransaction(
{ signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight },
"confirmed"
);
console.log("Multiply successful:", signature);
}
main().catch(console.error);