Not a member of gistpad yet?
Sign Up,
it unlocks many cool features!
- // executor.ts
- import {
- Connection,
- Keypair,
- PublicKey,
- AddressLookupTableProgram,
- VersionedTransaction,
- Transaction,
- TransactionInstruction,
- TransactionMessage,
- SystemProgram,
- ComputeBudgetProgram,
- LAMPORTS_PER_SOL,
- AddressLookupTableAccount,
- } from '@solana/web3.js';
- import { getAssociatedTokenAddress } from '@solana/spl-token';
- import bs58 from 'bs58';
- import https from 'https';
- import * as dotenv from 'dotenv';
- dotenv.config();
- import Decimal from 'decimal.js';
- // Kamino SDK
- import {
- KaminoMarket,
- getFlashLoanInstructions,
- PROGRAM_ID as KAMINO_PROGRAM_ID,
- } from '@kamino-finance/klend-sdk';
- import BN from 'bn.js';
- // ---------------------- CONFIG ----------------------
- const SOLANA_RPC = process.env.SOLANA_RPC || 'https://api.mainnet-beta.solana.com';
- const JUPITER_SWAP_INSTRUCTIONS_API = 'https://api.jup.ag/swap/v1/swap-instructions';
- const JITO_API = process.env.JITO_API || 'https://mainnet.block-engine.jito.wtf/api/v1/bundles';
- const KAMINO_MARKET_PUBKEY = new PublicKey(
- process.env.KAMINO_MARKET ?? '7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF'
- );
- const WSOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
- const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
- const JITO_TIP_ACCOUNT = new PublicKey(
- process.env.JITO_TIP_ACCOUNT ?? '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNs3Rj2Z'
- );
- const JITO_TIP_AMOUNT = Math.floor(
- (parseFloat(process.env.JITO_TIP_SOL ?? '0.005') || 0.005) * LAMPORTS_PER_SOL
- );
- // ----------------------------------------------------
- function loadKeypairFromEnv(): Keypair {
- const pk = process.env.PRIVATE_KEY;
- if (!pk) throw new Error('PRIVATE_KEY missing in .env');
- try {
- if (pk.trim().startsWith('[')) {
- const arr = JSON.parse(pk);
- return Keypair.fromSecretKey(Uint8Array.from(arr));
- } else {
- const secret = bs58.decode(pk);
- return Keypair.fromSecretKey(secret);
- }
- } catch (e) {
- throw new Error('Failed to parse PRIVATE_KEY: ' + String(e));
- }
- }
- interface SwapInstructionsResponse {
- tokenLedgerInstruction?: any;
- computeBudgetInstructions?: any[];
- setupInstructions?: any[];
- swapInstruction: any;
- cleanupInstruction?: any;
- addressLookupTableAddresses?: string[];
- error?: string;
- }
- /**
- * Get swap instructions from Jupiter v6 /swap-instructions endpoint
- */
- async function getJupiterSwapInstructions(
- quote: any,
- userPubkey: string
- ): Promise<SwapInstructionsResponse | null> {
- const payload = {
- quoteResponse: quote,
- userPublicKey: userPubkey,
- wrapAndUnwrapSol: false, // We handle WSOL manually in flash loan
- dynamicComputeUnitLimit: true,
- prioritizationFeeLamports: 'auto',
- };
- const data = JSON.stringify(payload);
- console.log('π Jupiter Swap Instructions Request:', {
- url: JUPITER_SWAP_INSTRUCTIONS_API,
- inputMint: quote.inputMint,
- outputMint: quote.outputMint,
- inAmount: quote.inAmount,
- });
- return new Promise((resolve, reject) => {
- const req = https.request(
- JUPITER_SWAP_INSTRUCTIONS_API,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-api-key': process.env.JUPITER_API_KEY || '',
- 'Content-Length': data.length,
- },
- timeout: 12_000,
- },
- (res) => {
- let body = '';
- res.on('data', (c) => (body += c));
- res.on('end', () => {
- console.log('π‘ Jupiter Response:', {
- status: res.statusCode,
- statusMessage: res.statusMessage,
- });
- if (res.statusCode !== 200) {
- console.error(`β Jupiter API Error: ${res.statusCode} ${res.statusMessage}`);
- console.error(`Response: ${body}`);
- return resolve(null);
- }
- try {
- const parsed = JSON.parse(body);
- if (parsed.error) {
- console.error('β Jupiter returned error:', parsed.error);
- return resolve(null);
- }
- console.log('β Jupiter Instructions received successfully');
- return resolve(parsed as SwapInstructionsResponse);
- } catch (e) {
- console.error('β Error parsing Jupiter response:', e);
- return resolve(null);
- }
- });
- }
- );
- req.on('error', (err) => {
- console.error('β Jupiter Request Error:', err.message);
- reject(err);
- });
- req.on('timeout', () => {
- console.error('β° Jupiter Request Timeout');
- req.destroy();
- resolve(null);
- });
- req.write(data);
- req.end();
- });
- }
- /**
- * Convert instruction payload to TransactionInstruction
- */
- function deserializeInstruction(instruction: any): TransactionInstruction {
- return new TransactionInstruction({
- programId: new PublicKey(instruction.programId),
- keys: instruction.accounts.map((key: any) => ({
- pubkey: new PublicKey(key.pubkey),
- isSigner: key.isSigner,
- isWritable: key.isWritable,
- })),
- data: Buffer.from(instruction.data, 'base64'),
- });
- }
- /**
- * Fetch address lookup table accounts
- */
- async function getAddressLookupTableAccounts(
- connection: Connection,
- keys: string[]
- ): Promise<AddressLookupTableAccount[]> {
- const addressLookupTableAccountInfos = await connection.getMultipleAccountsInfo(
- keys.map((key) => new PublicKey(key))
- );
- return addressLookupTableAccountInfos.reduce((acc, accountInfo, index) => {
- const addressLookupTableAddress = keys[index];
- if (accountInfo) {
- const addressLookupTableAccount = new AddressLookupTableAccount({
- key: new PublicKey(addressLookupTableAddress),
- state: AddressLookupTableAccount.deserialize(accountInfo.data),
- });
- acc.push(addressLookupTableAccount);
- }
- return acc;
- }, [] as AddressLookupTableAccount[]);
- }
- function safeDeserializeIx(
- ix: any | undefined,
- debugName: string
- ): TransactionInstruction {
- if (!ix || typeof ix !== 'object') {
- throw new Error(`Jupiter returned empty instruction for "${debugName}"`);
- }
- if (!ix.programId) {
- console.error('Raw instruction that failed:', JSON.stringify(ix, null, 2));
- throw new Error(`Instruction "${debugName}" is missing programId`);
- }
- return deserializeInstruction(ix);
- }
- // ALT: new helper ----------------------------------------------------------
- function sleep(ms: number): Promise<void> {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- async function ensureAlt(
- connection: Connection,
- payer: Keypair,
- addresses: PublicKey[]
- ): Promise<AddressLookupTableAccount> {
- // 0οΈβ£ Hard validation
- const uniq = [
- ...new Set(
- addresses
- .filter((k): k is PublicKey => k instanceof PublicKey)
- .map(k => k.toBase58())
- ),
- ].map(k => new PublicKey(k));
- if (uniq.length === 0) {
- throw new Error('ALT requested with empty or invalid address list');
- }
- // 1οΈβ£ Create + extend ALT
- const recentSlot = await connection.getSlot('finalized');
- const [createIx, altAddress] =
- AddressLookupTableProgram.createLookupTable({
- authority: payer.publicKey,
- payer: payer.publicKey,
- recentSlot,
- });
- const extendIx = AddressLookupTableProgram.extendLookupTable({
- lookupTable: altAddress,
- authority: payer.publicKey,
- payer: payer.publicKey,
- addresses: uniq,
- });
- const { blockhash, lastValidBlockHeight } =
- await connection.getLatestBlockhash('finalized');
- const tx = new VersionedTransaction(
- new TransactionMessage({
- payerKey: payer.publicKey,
- recentBlockhash: blockhash,
- instructions: [createIx, extendIx],
- }).compileToV0Message()
- );
- tx.sign([payer]);
- // 2οΈβ£ Send ONCE
- const sig = await connection.sendTransaction(tx, {
- skipPreflight: true,
- });
- // 3οΈβ£ Wait for FINALIZATION
- await connection.confirmTransaction(
- { signature: sig, blockhash, lastValidBlockHeight },
- 'finalized'
- );
- // 4οΈβ£ Retry fetch until ALT is live
- let acc = null;
- for (let i = 0; i < 10; i++) {
- acc = await connection.getAccountInfo(altAddress, 'finalized');
- if (acc?.owner.equals(AddressLookupTableProgram.programId)) break;
- await sleep(300);
- }
- if (!acc) {
- throw new Error('ALT account not found after creation');
- }
- if (!acc.owner.equals(AddressLookupTableProgram.programId)) {
- throw new Error(
- `Account exists but is not ALT (owner=${acc.owner.toBase58()})`
- );
- }
- // 5οΈβ£ Safe deserialize
- return new AddressLookupTableAccount({
- key: altAddress,
- state: AddressLookupTableAccount.deserialize(acc.data),
- });
- }
- let cachedAlt: AddressLookupTableAccount | null = null;
- async function getAlt(
- connection: Connection,
- payer: Keypair,
- addresses: PublicKey[]
- ) {
- if (cachedAlt) return cachedAlt;
- cachedAlt = await ensureAlt(connection, payer, addresses);
- return cachedAlt;
- }
- /** Patch Kamino SDK bug: replace undefined pubkey with a dummy key */
- function fixKaminoInstruction(ix: TransactionInstruction): TransactionInstruction {
- const dummy = SystemProgram.programId;
- ix.keys = ix.keys.map(k => {
- // if pubkey is missing or invalid, replace with dummy and make sure it is not a signer
- if (!k.pubkey || typeof k.pubkey.toBase58 !== 'function') {
- return {
- ...k,
- pubkey: dummy,
- isSigner: false,
- isWritable: Boolean(k.isWritable), // preserve writable if desired
- };
- }
- // otherwise, ensure the pubkey is a PublicKey instance (some libs return strings)
- if (typeof k.pubkey === 'string') {
- return { ...k, pubkey: new PublicKey(k.pubkey) };
- }
- return k;
- });
- return ix;
- }
- /** Build a *writable* transfer instruction so Jito accepts the bundle */
- const HELIUS_TIP_ACCOUNTS = [
- 'wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF',
- '3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT',
- '2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ',
- '5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn',
- '4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey',
- '4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE',
- '9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta',
- 'D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ',
- '4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or',
- '2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD',
- 'D1Mc6j9xQWgR1o1Z7yU5nVVXFQiAYx7FG9AW1aVfwrUM'
- ];
- const TIP_LAMPORTS = 200_000; // 0.002 SOL minimum
- /**
- * Build an atomic VersionedTransaction that wraps:
- * [ flashBorrowIxn, ...computeBudget, ...setup, swap1, swap2, cleanup, flashRepayIxn, jito tip ]
- */
- async function buildAtomicFlashloanTransaction(
- connection: Connection,
- wallet: Keypair,
- market: any,
- reserve: any,
- flashLoanAmountLamports: bigint,
- swapInstructions1: SwapInstructionsResponse,
- swapInstructions2: SwapInstructionsResponse
- ): Promise<VersionedTransaction> {
- // 1) Derive destination ATA for borrowed mint (WSOL)
- const destinationAta = await getAssociatedTokenAddress(WSOL_MINT, wallet.publicKey, false);
- // 2) Get lending market authority
- const lendingMarketAuthority = await market.getLendingMarketAuthority();
- // 3) Build flash borrow & repay instructions
- const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({
- borrowIxIndex: 0,
- userTransferAuthority: wallet.publicKey,
- lendingMarketAuthority,
- lendingMarketAddress: market.getAddress(),
- reserve,
- amountLamports: flashLoanAmountLamports,
- destinationAta,
- referrerAccount: undefined,
- referrerTokenState: undefined,
- programId: KAMINO_PROGRAM_ID,
- });
- // right after getFlashLoanInstructions(...) and the destructure
- console.log('--- flashBorrowIxn keys ---');
- flashBorrowIxn.keys.forEach((k, i) =>
- console.log(i, k.pubkey?.toBase58?.() ?? String(k.pubkey), 'isSigner=', k.isSigner, 'isWritable=', k.isWritable)
- );
- console.log('--- flashRepayIxn keys ---');
- flashRepayIxn.keys.forEach((k, i) =>
- console.log(i, k.pubkey?.toBase58?.() ?? String(k.pubkey), 'isSigner=', k.isSigner, 'isWritable=', k.isWritable)
- );
- // Insert after the getFlashLoanInstructions(...) call and before pushing instructions
- // 1) Dump raw keys shape (useful for debugging)
- console.log('RAW flashBorrowIxn.keys (inspect):', JSON.stringify(flashBorrowIxn.keys, null, 2));
- console.log('RAW flashRepayIxn.keys (inspect):', JSON.stringify(flashRepayIxn.keys, null, 2));
- // 2) Sanitizer: ensure every AccountMeta has a PublicKey instance
- function sanitizeKaminoIxn(ix: TransactionInstruction, walletPubkey: PublicKey): TransactionInstruction {
- if (!ix || !Array.isArray(ix.keys)) throw new Error('Bad instruction returned by Kamino SDK');
- const sanitizedKeys = ix.keys.map((k: any, i: number) => {
- // Keep isSigner/isWritable flags robust
- const isSigner = Boolean(k?.isSigner);
- const isWritable = Boolean(k?.isWritable);
- // Determine a safe pubkey value:
- let pub = k?.pubkey;
- // If it's already a PublicKey instance, great
- if (pub && typeof pub.toBase58 === 'function') {
- return { pubkey: pub, isSigner, isWritable };
- }
- // If it's a string (some libs return base58 strings), convert it
- if (typeof pub === 'string' && pub.length > 0) {
- try {
- return { pubkey: new PublicKey(pub), isSigner, isWritable };
- } catch (e) {
- /* fallthrough to fallback */
- }
- }
- // If missing and this entry requires a signer, assume it's the userTransferAuthority
- if (isSigner) {
- console.warn(`sanitize: instruction key #${i} was missing but marked signer -> using wallet.pubkey`);
- return { pubkey: walletPubkey, isSigner: true, isWritable };
- }
- // Otherwise replace with SystemProgram so compilation won't crash.
- // This is a safe dummy for non-signer entries (will surface a logical error later if it's wrong).
- console.warn(`sanitize: instruction key #${i} missing pubkey -> substituting SystemProgram (non-signer)`);
- return { pubkey: KAMINO_PROGRAM_ID, isSigner: false, isWritable };
- });
- // Build a clean TransactionInstruction (preserve programId and data)
- return new TransactionInstruction({
- programId: ix.programId instanceof PublicKey ? ix.programId : new PublicKey(String(ix.programId)),
- keys: sanitizedKeys as any,
- data: Buffer.isBuffer(ix.data) ? ix.data : Buffer.from(ix.data ?? ''),
- });
- }
- // 3) Sanitize both instructions
- const borrowIxnSan = sanitizeKaminoIxn(flashBorrowIxn, wallet.publicKey);
- const repayIxnSan = sanitizeKaminoIxn(flashRepayIxn, wallet.publicKey);
- // 4) Optionally log sanitized keys for verification
- console.log('--- SANITIZED flashBorrowIxn keys ---');
- borrowIxnSan.keys.forEach((k, i) =>
- console.log(i, k.pubkey.toBase58(), 'isSigner=', k.isSigner, 'isWritable=', k.isWritable)
- );
- console.log('--- SANITIZED flashRepayIxn keys ---');
- repayIxnSan.keys.forEach((k, i) =>
- console.log(i, k.pubkey.toBase58(), 'isSigner=', k.isSigner, 'isWritable=', k.isWritable)
- );
- // 4) Assemble all instructions in order (PRUNED)
- const allInstructions: TransactionInstruction[] = [];
- // Then push sanitized instructions instead of originals
- // ...
- // and later for repay:
- // --- flash borrow (must be first) ---
- allInstructions.push(borrowIxnSan);
- // --- swap 1 (WSOL β USDC) NO setup, NO cleanup ---
- allInstructions.push(
- safeDeserializeIx(swapInstructions1.swapInstruction, 'swap1.swap')
- );
- // --- swap 2 (USDC β WSOL) NO setup, NO cleanup ---
- allInstructions.push(
- safeDeserializeIx(swapInstructions2.swapInstruction, 'swap2.swap')
- );
- // --- flash repay (must be last) ---
- allInstructions.push(repayIxnSan);
- allInstructions.push(
- SystemProgram.transfer({
- fromPubkey: wallet.publicKey,
- toPubkey: new PublicKey(HELIUS_TIP_ACCOUNTS[0]),
- lamports: TIP_LAMPORTS,
- })
- );
- // sanity gate
- console.log('π Final instruction count:', allInstructions.length);
- if (allInstructions.length > 8) throw new Error('TX too large β abort');
- // 5) Get address lookup tables from both swaps
- const lookupTableAddresses = [
- ...(swapInstructions1.addressLookupTableAddresses || []),
- ...(swapInstructions2.addressLookupTableAddresses || []),
- ];
- let addressLookupTableAccounts: AddressLookupTableAccount[] = [];
- if (lookupTableAddresses.length > 0) {
- addressLookupTableAccounts = await getAddressLookupTableAccounts(
- connection,
- lookupTableAddresses
- );
- }
- // 6) Get recent blockhash
- const { blockhash } = await connection.getLatestBlockhash('finalized');
- // ALT: 1) collect every address we will ever touch -------------------------
- const allKeys = [
- wallet.publicKey,
- market.getAddress(),
- reserve.address,
- lendingMarketAuthority,
- destinationAta,
- WSOL_MINT,
- USDC_MINT,
- ];
- // ALT: 2) ensure ALT exists (cache it in prod) -----------------------------
- const alt = await getAlt(connection, wallet, allKeys);
- const jupAlts = await getAddressLookupTableAccounts(
- connection,
- lookupTableAddresses
- );
- // ALT: 3) build v0 message --------------------------------------------------
- const messageV0 = new TransactionMessage({
- payerKey: wallet.publicKey,
- recentBlockhash: blockhash,
- instructions: allInstructions,
- }).compileToV0Message([...jupAlts, alt]); // <-- ALT injected here
- const serialized = messageV0.serialize();
- console.log("TX SIZE:", serialized.length);
- // ALT: 4) create VersionedTransaction --------------------------------------
- const vtx = new VersionedTransaction(messageV0);
- vtx.sign([wallet]); // payer signature
- return vtx; // now returns VersionedTransaction
- }
- /** Send single signed VersionedTransaction to Jito as a bundle */
- /**
- * Send the already-signed VersionedTransaction through Helius Sender
- * (it automatically adds Jito tip + routes to Jito + Helius validators)
- */
- async function sendWithSender(signedTx: Transaction | VersionedTransaction): Promise<string> {
- const base64 = Buffer.from(signedTx.serialize()).toString('base64');
- console.log('DEBUG tx size:', base64.length, 'chars');
- const resp = await fetch(
- {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- jsonrpc: '2.0',
- id: Date.now().toString(),
- method: 'sendTransaction',
- params: [
- base64,
- { encoding: 'base64', skipPreflight: true, maxRetries: 0 },
- ],
- }),
- }
- );
- const raw = await resp.json();
- console.log('DEBUG Sender response:', JSON.stringify(raw, null, 2)); // β add this
- const { result, signature, error } = raw;
- const txid = signature ?? result;
- if (error) throw new Error(error.message);
- console.log('β Bundle landed:', txid);
- return txid;
- }
- /**
- * Main executor entry. Called from scanner when opportunity is found.
- * @param quote1 - WSOL -> USDC quote from scanner
- * @param quote2 - USDC -> WSOL quote from scanner
- * @param tradeAmountSol - Amount in SOL
- */
- export async function onOpportunityFound(quote1: any, quote2: any, tradeAmountSol: number) {
- console.log('\nπ― ================================================');
- console.log('π― EXECUTOR: Opportunity handler invoked');
- console.log('π― ================================================\n');
- try {
- const wallet = loadKeypairFromEnv();
- const connection = new Connection(SOLANA_RPC, 'confirmed');
- console.log('πΌ Wallet:', wallet.publicKey.toBase58());
- // Load Kamino market
- console.log('π¦ Loading Kamino market...');
- const market = await KaminoMarket.load(connection, KAMINO_MARKET_PUBKEY);
- // Find WSOL reserve
- const reserve = market.getReserveByMint(WSOL_MINT);
- if (!reserve) {
- throw new Error('WSOL reserve not found in Kamino market');
- }
- console.log('β Kamino market loaded successfully');
- // 1) Get swap instructions from Jupiter for both swaps
- console.log('\nπ Fetching swap instructions from Jupiter...');
- const swapInstructions1 = await getJupiterSwapInstructions(quote1, wallet.publicKey.toBase58());
- if (!swapInstructions1) {
- throw new Error('Failed to fetch swap instructions for WSOL -> USDC');
- }
- console.log('β Swap 1 instructions received (WSOL -> USDC)');
- const swapInstructions2 = await getJupiterSwapInstructions(quote2, wallet.publicKey.toBase58());
- if (!swapInstructions2) {
- throw new Error('Failed to fetch swap instructions for USDC -> WSOL');
- }
- console.log('β Swap 2 instructions received (USDC -> WSOL)');
- // 2) Compute borrow size
- const amountLamports = new Decimal(tradeAmountSol).mul(new Decimal(1e9)).floor();
- console.log('\nπ° Flash loan amount:', tradeAmountSol, 'SOL (', amountLamports.toString(), 'lamports)');
- // 3) Build atomic transaction
- console.log('\nπ¨ Building atomic flash loan transaction...');
- const vtx = await buildAtomicFlashloanTransaction(
- connection,
- wallet,
- market,
- reserve,
- amountLamports,
- swapInstructions1,
- swapInstructions2
- );
- // 4) Send to Jito helius
- console.log('\nπ€ Sending transaction via jito helius...');
- await sendWithSender(vtx);
- console.log('\nβ ================================================');
- console.log('β EXECUTOR: Bundle sent successfully!');
- console.log('β ================================================\n');
- } catch (error) {
- console.error('\nβ ================================================');
- console.error('β EXECUTOR FAILED:', error);
- console.error('β ================================================\n');
- throw error;
- }
- }
- //close ALT.ts
- import {
- Connection,
- Keypair,
- PublicKey,
- Transaction,
- AddressLookupTableProgram,
- } from "@solana/web3.js";
- import bs58 from "bs58";
- import * as dotenv from "dotenv";
- dotenv.config();
- /* ================= CONFIG ================= */
- const RPC =
- process.env.SOLANA_RPC ??
- // π΄ PUT THE ALT YOU WANT TO CLOSE HERE
- const ALT_ADDRESS = new PublicKey(
- process.env.ALT_ADDRESS ?? ""
- );
- /* ========================================= */
- function loadKeypairFromEnv(): Keypair {
- const pk = process.env.PRIVATE_KEY;
- if (!pk) throw new Error("PRIVATE_KEY missing in .env");
- if (pk.trim().startsWith("[")) {
- return Keypair.fromSecretKey(
- Uint8Array.from(JSON.parse(pk))
- );
- }
- return Keypair.fromSecretKey(bs58.decode(pk));
- }
- async function sleep(ms: number) {
- return new Promise((r) => setTimeout(r, ms));
- }
- async function main() {
- if (!process.env.ALT_ADDRESS) {
- throw new Error("ALT_ADDRESS missing in .env");
- }
- const connection = new Connection(RPC, "confirmed");
- const wallet = loadKeypairFromEnv();
- console.log("π Wallet:", wallet.publicKey.toBase58());
- console.log("π¦ ALT:", ALT_ADDRESS.toBase58());
- /* 1οΈβ£ Deactivate ALT */
- console.log("βΈοΈ Deactivating ALT...");
- const deactivateTx = new Transaction().add(
- AddressLookupTableProgram.deactivateLookupTable({
- lookupTable: ALT_ADDRESS,
- authority: wallet.publicKey,
- })
- );
- const deactivateSig = await connection.sendTransaction(
- deactivateTx,
- [wallet]
- );
- console.log("β Deactivate tx:", deactivateSig);
- /* 2οΈβ£ Wait cooldown (~512 slots) */
- console.log("β³ Waiting cooldown (~3 minutes)...");
- await sleep(180_000);
- /* 3οΈβ£ Close ALT */
- console.log("ποΈ Closing ALT...");
- const closeTx = new Transaction().add(
- AddressLookupTableProgram.closeLookupTable({
- lookupTable: ALT_ADDRESS,
- authority: wallet.publicKey,
- recipient: wallet.publicKey,
- })
- );
- const closeSig = await connection.sendTransaction(
- closeTx,
- [wallet]
- );
- console.log("π° ALT closed, rent refunded");
- console.log("β Close tx:", closeSig);
- }
- main().catch((e) => {
- console.error("β Failed:", e);
- process.exit(1);
- });
RAW Gist Data
Copied
