@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
214 lines • 9.28 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWsolAtaIfMissing = exports.getAtasWithCreateIxsIfMissing = void 0;
exports.createAssociatedTokenAccountIdempotentInstruction = createAssociatedTokenAccountIdempotentInstruction;
exports.getAssociatedTokenAddress = getAssociatedTokenAddress;
exports.createAtasIdempotent = createAtasIdempotent;
exports.getTransferWsolIxs = getTransferWsolIxs;
exports.getTokenAccountBalance = getTokenAccountBalance;
exports.getTokenAccountBalanceDecimal = getTokenAccountBalanceDecimal;
exports.getAllStandardTokenProgramTokenAccounts = getAllStandardTokenProgramTokenAccounts;
exports.getTokenAccountMint = getTokenAccountMint;
exports.getTokenAccountAmount = getTokenAccountAmount;
const kit_1 = require("@solana/kit");
const decimal_js_1 = __importDefault(require("decimal.js"));
const dist_1 = require("@kamino-finance/kliquidity-sdk/dist");
const token_1 = require("@solana-program/token");
const token_2022_1 = require("@solana-program/token-2022");
const pubkey_1 = require("./pubkey");
const system_1 = require("@solana-program/system");
/**
* Create an idempotent create ATA instruction
* Overrides the create ATA ix to use the idempotent version as the spl-token library does not provide this ix yet
* @param owner - owner of the ATA
* @param mint - mint of the ATA
* @param payer - payer of the transaction
* @param tokenProgram - optional token program address - spl-token if not provided
* @param ata - optional ata address - derived if not provided
* @returns The ATA address public key and the transaction instruction
*/
async function createAssociatedTokenAccountIdempotentInstruction(payer, mint, owner = payer.address, tokenProgram = token_1.TOKEN_PROGRAM_ADDRESS, ata) {
let ataAddress = ata;
if (!ataAddress) {
ataAddress = await getAssociatedTokenAddress(mint, owner, tokenProgram, token_2022_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS);
}
const createUserTokenAccountIx = (0, token_2022_1.getCreateAssociatedTokenIdempotentInstruction)({
owner,
mint,
tokenProgram,
ata: ataAddress,
payer,
}, {
programAddress: token_2022_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
});
return [ataAddress, createUserTokenAccountIx];
}
async function getAssociatedTokenAddress(mint, owner, tokenProgram = token_1.TOKEN_PROGRAM_ADDRESS, associatedTokenProgramId = token_2022_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS) {
const [ata] = await (0, token_2022_1.findAssociatedTokenPda)({
mint,
owner,
tokenProgram,
}, { programAddress: associatedTokenProgramId });
return ata;
}
const getAtasWithCreateIxsIfMissing = async (rpc, user, mints) => {
const atas = await Promise.all(mints.map(async (x) => getAssociatedTokenAddress(x.mint, user.address, x.tokenProgram)));
const accountInfos = await rpc.getMultipleAccounts(atas).send();
const createAtaIxs = [];
for (let i = 0; i < atas.length; i++) {
if (accountInfos.value[i] === null) {
const { mint, tokenProgram } = mints[i];
const [ata, createIxn] = await createAssociatedTokenAccountIdempotentInstruction(user, mint, user.address, tokenProgram);
atas[i] = ata;
createAtaIxs.push(createIxn);
}
}
return {
atas,
createAtaIxs,
};
};
exports.getAtasWithCreateIxsIfMissing = getAtasWithCreateIxsIfMissing;
async function createAtasIdempotent(user, mints) {
const res = [];
for (const mint of mints) {
const [ata, createAtaIx] = await createAssociatedTokenAccountIdempotentInstruction(user, mint.mint, user.address, mint.tokenProgram);
res.push({
ata,
createAtaIx,
});
}
return res;
}
function getTransferWsolIxs(owner, ata, amountLamports) {
const ixs = [];
ixs.push((0, system_1.getTransferSolInstruction)({
source: owner,
amount: amountLamports,
destination: ata,
}));
ixs.push((0, token_2022_1.getSyncNativeInstruction)({
account: ata,
}, { programAddress: token_1.TOKEN_PROGRAM_ADDRESS }));
return ixs;
}
async function getTokenAccountBalance(connection, tokenAccount) {
const tokenAccountBalance = await connection.getTokenAccountBalance(tokenAccount).send();
return Number(tokenAccountBalance.value.amount).valueOf();
}
/// Get the balance of a token account in decimal format (tokens, not lamports)
async function getTokenAccountBalanceDecimal(rpc, mint, owner, tokenProgram = token_1.TOKEN_PROGRAM_ADDRESS) {
const ata = await getAssociatedTokenAddress(mint, owner, tokenProgram);
const accInfo = await (0, kit_1.fetchEncodedAccount)(rpc, ata);
if (!accInfo.exists) {
return new decimal_js_1.default('0');
}
const { value } = await rpc.getTokenAccountBalance(ata).send();
return new decimal_js_1.default(value.uiAmountString);
}
/**
* Creates a wSOL ata if missing and syncs the balance. If the ata exists and it has more or equal no wrapping happens
* @param rpc - Solana RPC rpc (read)
* @param amount min amount to have in the wSOL ata. If the ata exists and it has more or equal no wrapping happens
* @param owner - owner of the ata
* @returns wsolAta: the keypair of the ata, used to sign the initialization transaction; createAtaIxs: a list with ixs to initialize the ata and wrap SOL if needed; closeAtaIxs: a list with ixs to close the ata
*/
const createWsolAtaIfMissing = async (rpc, amount, owner) => {
const createIxs = [];
const closeIxs = [];
const wsolAta = await getAssociatedTokenAddress(pubkey_1.WRAPPED_SOL_MINT, owner.address, token_1.TOKEN_PROGRAM_ADDRESS);
const solDeposit = amount;
const wsolAtaAccountInfo = await (0, token_2022_1.fetchMaybeToken)(rpc, wsolAta);
// This checks if we need to create it
if (wsolAtaAccountInfo.exists) {
createIxs.push((0, token_2022_1.getCreateAssociatedTokenIdempotentInstruction)({
owner: owner.address,
payer: owner,
ata: wsolAta,
mint: pubkey_1.WRAPPED_SOL_MINT,
tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
}));
}
let wsolExistingBalanceLamports = new decimal_js_1.default(0);
try {
if (wsolAtaAccountInfo.exists) {
const uiAmount = (await getTokenAccountBalanceDecimal(rpc, pubkey_1.WRAPPED_SOL_MINT, owner.address)).toNumber();
wsolExistingBalanceLamports = (0, dist_1.collToLamportsDecimal)(new decimal_js_1.default(uiAmount), dist_1.DECIMALS_SOL);
}
}
catch (err) {
console.log('Err Token Balance', err);
}
if (solDeposit !== null && solDeposit.gt(wsolExistingBalanceLamports)) {
createIxs.push((0, system_1.getTransferSolInstruction)({
source: owner,
destination: wsolAta,
amount: BigInt(solDeposit.sub(wsolExistingBalanceLamports).floor().toString()),
}));
}
if (createIxs.length > 0) {
// Primitive way of wrapping SOL
createIxs.push((0, token_2022_1.getSyncNativeInstruction)({
account: wsolAta,
}, { programAddress: token_1.TOKEN_PROGRAM_ADDRESS }));
}
closeIxs.push((0, token_2022_1.getCloseAccountInstruction)({
owner,
account: wsolAta,
destination: owner.address,
}, { programAddress: token_1.TOKEN_PROGRAM_ADDRESS }));
return {
wsolAta,
createAtaIxs: createIxs,
closeAtaIxs: closeIxs,
};
};
exports.createWsolAtaIfMissing = createWsolAtaIfMissing;
/**
* Get all standard token accounts for tokens using old Token Program, not Token 2022 for a given wallet
* @param rpc - Solana RPC rpc (read)
* @param wallet - wallet to get the token accounts for
* @returns an array of all token accounts for the given wallet
*/
async function getAllStandardTokenProgramTokenAccounts(rpc, wallet) {
return rpc
.getProgramAccounts(token_1.TOKEN_PROGRAM_ADDRESS, {
filters: [
{ dataSize: 165n },
{ memcmp: { offset: 32n, bytes: wallet.toString(), encoding: 'base58' } },
],
encoding: 'jsonParsed',
})
.send();
}
// Type guard to check if account data is parsed
function isParsedTokenAccountData(data) {
return (data &&
typeof data === 'object' &&
'parsed' in data &&
data.parsed &&
typeof data.parsed === 'object' &&
'info' in data.parsed &&
data.parsed.info &&
typeof data.parsed.info === 'object' &&
'mint' in data.parsed.info &&
'tokenAmount' in data.parsed.info);
}
// Helper function to safely get mint from parsed token account
function getTokenAccountMint(accountData) {
if (isParsedTokenAccountData(accountData)) {
return accountData.parsed.info.mint;
}
return null;
}
// Helper function to safely get token amount from parsed token account
function getTokenAccountAmount(accountData) {
if (isParsedTokenAccountData(accountData)) {
return accountData.parsed.info.tokenAmount.uiAmount;
}
return null;
}
//# sourceMappingURL=ata.js.map