import {
createSolanaRpc,
createSolanaRpcSubscriptions,
address,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
sendAndConfirmTransactionFactory,
getSignatureFromTransaction,
compressTransactionMessageUsingAddressLookupTables,
assertIsTransactionWithinSizeLimit,
} from "@solana/kit";
import type { Address } from "@solana/kit";
import {
Kamino,
createComputeUnitLimitIx,
getAssociatedTokenAddressAndAccount,
} from "@kamino-finance/kliquidity-sdk";
import { parseKeypairFile } from "@kamino-finance/klend-sdk/dist/utils/signer";
import { fetchAllAddressLookupTable } from "@solana-program/address-lookup-table";
const KEYPAIR_FILE = "/path/to/your/keypair.json";
const RPC_ENDPOINT = "https://api.mainnet-beta.solana.com";
const WS_ENDPOINT = "wss://api.mainnet-beta.solana.com";
const STRATEGY = address("CEz5keL9hBCUbtVbmcwenthRMwmZLupxJ6YtYAgzp4ex");
const signer = await parseKeypairFile(KEYPAIR_FILE);
const rpc = createSolanaRpc(RPC_ENDPOINT);
const rpcSubscriptions = createSolanaRpcSubscriptions(WS_ENDPOINT);
const kamino = new Kamino("mainnet-beta", rpc);
const strategy = await kamino.getStrategyByAddress(STRATEGY);
if (!strategy) {
console.log("Strategy not found:", STRATEGY);
process.exit(1);
}
const strategyWithAddress = { strategy, address: STRATEGY };
const [sharesAta, sharesMintData] = await getAssociatedTokenAddressAndAccount(
rpc,
strategy.sharesMint,
signer.address,
);
const [tokenAAta, tokenAData] = await getAssociatedTokenAddressAndAccount(
rpc,
strategy.tokenAMint,
signer.address,
);
const [tokenBAta, tokenBData] = await getAssociatedTokenAddressAndAccount(
rpc,
strategy.tokenBMint,
signer.address,
);
const ataIxs =
await kamino.getCreateAssociatedTokenAccountInstructionsIfNotExist(
signer,
strategyWithAddress,
tokenAData,
tokenAAta,
tokenBData,
tokenBAta,
sharesMintData,
sharesAta,
);
const bundle = await kamino.withdrawAllShares(strategyWithAddress, signer);
if (!bundle) {
console.log("No shares to withdraw");
process.exit(0);
}
const instructions = [
createComputeUnitLimitIx(1_400_000),
...ataIxs,
...bundle.prerequisiteIxs,
bundle.withdrawIx,
...(bundle.closeSharesAtaIx ? [bundle.closeSharesAtaIx] : []),
];
const allAddresses = new Set<string>();
for (const ix of instructions) {
allAddresses.add(ix.programAddress.toString());
for (const acc of ix.accounts ?? []) allAddresses.add(acc.address.toString());
}
const lutResp = await fetch("https://api.kamino.finance/luts/find-minimal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ addresses: Array.from(allAddresses) }),
});
if (!lutResp.ok) {
console.log(
"luts/find-minimal failed:",
lutResp.status,
await lutResp.text(),
);
process.exit(1);
}
const { lutAddresses: minimalLutAddresses } = (await lutResp.json()) as {
lutAddresses: string[];
};
const dedupedLutAddresses = Array.from(new Set(minimalLutAddresses)).map((a) =>
address(a),
);
const lutAccounts = await fetchAllAddressLookupTable(rpc, dedupedLutAddresses);
const lutsByAddress: Record<Address, Address[]> = {};
for (const acc of lutAccounts) {
lutsByAddress[acc.address] = acc.data.addresses;
}
const { value: latestBlockhash } = await rpc
.getLatestBlockhash({ commitment: "finalized" })
.send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => compressTransactionMessageUsingAddressLookupTables(tx, lutsByAddress),
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithinSizeLimit(signedTransaction);
const signature = getSignatureFromTransaction(signedTransaction);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{
commitment: "confirmed",
skipPreflight: true,
},
);
console.log("Strategy withdraw-all successful! Signature:", signature);