@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
190 lines (164 loc) • 5.61 kB
text/typescript
import {
Address,
GetAccountInfoApi,
GetTokenAccountBalanceApi,
IInstruction,
Rpc,
TransactionSigner,
} from '@solana/kit';
import Decimal from 'decimal.js';
import { DECIMALS_SOL, createAssociatedTokenAccountInstruction, getAssociatedTokenAddress } from './tokenUtils';
import { collToLamportsDecimal } from './utils';
import { CreateAta } from './types';
import { DEFAULT_PUBLIC_KEY, WRAPPED_SOL_MINT } from '../constants/pubkeys';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import {
COMPUTE_BUDGET_PROGRAM_ADDRESS,
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
} from '@solana-program/compute-budget';
import {
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
fetchMaybeToken,
getCloseAccountInstruction,
getSyncNativeInstruction,
} from '@solana-program/token-2022';
import { getTransferSolInstruction } from '@solana-program/system';
export const MAX_ACCOUNTS_PER_TRANSACTION = 64;
export const getComputeBudgetAndPriorityFeeIxns = (units: number, priorityFeeLamports?: Decimal): IInstruction[] => {
const ixns: IInstruction[] = [];
ixns.push(getSetComputeUnitLimitInstruction({ units }));
if (priorityFeeLamports && priorityFeeLamports.gt(0)) {
const unitPrice = priorityFeeLamports.mul(10 ** 6).div(units);
ixns.push(getSetComputeUnitPriceInstruction({ microLamports: BigInt(unitPrice.floor().toString()) }));
}
return ixns;
};
export const createAtaIfMissingIx = async (
connection: Rpc<GetAccountInfoApi>,
mint: Address,
owner: TransactionSigner,
programId: Address
): Promise<IInstruction | undefined> => {
const ata = await getAssociatedTokenAddress(mint, owner.address, programId);
const doesAtaExist = Boolean(await checkIfAccountExists(connection, ata));
const createIxn = !doesAtaExist
? createAssociatedTokenAccountInstruction(owner, ata, owner.address, mint, programId)
: undefined;
return createIxn;
};
export const getAtasWithCreateIxnsIfMissing = async (
connection: Rpc<any>,
mints: [Address, Address][],
owner: TransactionSigner
): Promise<IInstruction[]> => {
const requests = mints.map(async ([mint, tokenProgram]) => {
const createAtaIx = await createAtaIfMissingIx(connection, mint, owner, tokenProgram);
if (createAtaIx) {
return createAtaIx;
}
return undefined;
});
const result = (await Promise.all(requests.filter((x) => x !== undefined))).filter(
(ix) => ix !== undefined
) as IInstruction[];
return result;
};
export const createWsolAtaIfMissing = async (
rpc: Rpc<GetAccountInfoApi & GetTokenAccountBalanceApi>,
amount: Decimal,
owner: TransactionSigner,
method: 'deposit' | 'withdraw' = 'deposit'
): Promise<CreateAta> => {
const createIxns: IInstruction[] = [];
const closeIxns: IInstruction[] = [];
const wsolAta: Address = await getAssociatedTokenAddress(WRAPPED_SOL_MINT, owner.address);
const solDeposit = amount.toNumber();
const wsolAtaTokenAccount = await fetchMaybeToken(rpc, wsolAta);
// This checks if we need to create it
if (!wsolAtaTokenAccount.exists) {
createIxns.push(
createAssociatedTokenAccountInstruction(owner, wsolAta, owner.address, WRAPPED_SOL_MINT, TOKEN_PROGRAM_ADDRESS)
);
}
let uiAmount = 0;
try {
if (wsolAtaTokenAccount.exists) {
const tokenBalance = await findAtaBalance(rpc, wsolAta);
uiAmount = tokenBalance === null ? 0 : tokenBalance;
}
} catch (err) {
console.log('Err Token Balance', err);
}
if (solDeposit !== null && solDeposit > uiAmount && method === 'deposit') {
createIxns.push(
getTransferSolInstruction({
amount: BigInt(
collToLamportsDecimal(new Decimal(solDeposit - uiAmount), DECIMALS_SOL)
.floor()
.toString()
),
source: owner,
destination: wsolAta,
})
);
}
if (createIxns.length > 0) {
createIxns.push(
getSyncNativeInstruction(
{
account: wsolAta,
},
{ programAddress: TOKEN_PROGRAM_ADDRESS }
)
);
}
closeIxns.push(
getCloseAccountInstruction(
{ account: wsolAta, owner: owner, destination: owner.address, multiSigners: [] },
{ programAddress: TOKEN_PROGRAM_ADDRESS }
)
);
return {
ata: wsolAta,
createIxns,
closeIxns,
};
};
export const isWsolInfoInvalid = (wsolAtaAccountInfo: any): boolean => {
const res =
wsolAtaAccountInfo === null ||
(wsolAtaAccountInfo !== null &&
wsolAtaAccountInfo.data.length === 0 &&
wsolAtaAccountInfo.owner === DEFAULT_PUBLIC_KEY);
return res;
};
export async function checkIfAccountExists(connection: Rpc<GetAccountInfoApi>, account: Address): Promise<boolean> {
return (await connection.getAccountInfo(account, { encoding: 'base64' }).send()).value != null;
}
export function removeBudgetAndAtaIxns(ixns: IInstruction[], mints: Address[]): IInstruction[] {
return ixns.filter((ixn) => {
const { programAddress, accounts } = ixn;
if (programAddress === COMPUTE_BUDGET_PROGRAM_ADDRESS) {
return false;
}
if (programAddress === ASSOCIATED_TOKEN_PROGRAM_ADDRESS) {
if (!accounts || accounts.length < 2) {
return true;
}
const mint = accounts[3];
return !mints.includes(mint.address);
}
return true;
});
}
export const findAtaBalance = async (
connection: Rpc<GetTokenAccountBalanceApi>,
ata: Address
): Promise<number | null> => {
const res = await connection.getTokenAccountBalance(ata).send();
if (res && res.value) {
return res.value.uiAmount;
}
return null;
};