UNPKG

@kamino-finance/klend-sdk

Version:

Typescript SDK for interacting with the Kamino Lending (klend) protocol

326 lines (286 loc) 9.2 kB
import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { KaminoMarket } from '../classes'; import { PublicKeyMap, buildAndSendTxnWithLogs, buildVersionedTransaction, referrerStatePda, shortUrlPda, } from '../utils'; import { getDeleteReferrerStateAndShortUrlIxns, getInitAllReferrerTokenStateIxns, getInitReferrerStateAndShortUrlIxns, } from './instructions'; import { PROGRAM_ID, UserMetadata, ReferrerState, ShortUrl } from '../lib'; import Decimal from 'decimal.js'; /** * Initialize all referrer token states for a given referrer * @param referrer * @param kaminoMarket */ export const initAllReferrerTokenStates = async ({ referrer, kaminoMarket, }: { referrer: Keypair; kaminoMarket: KaminoMarket; }) => { const ixns = await getInitAllReferrerTokenStateIxns({ referrer: referrer.publicKey, kaminoMarket }); const tx = await buildVersionedTransaction(kaminoMarket.getConnection(), referrer.publicKey, ixns); console.log('Init Referral Token States'); return await buildAndSendTxnWithLogs(kaminoMarket.getConnection(), tx, referrer, [referrer]); }; /** * Initialize referrer state and short url * @param connection * @param referrer * @param shortUrl */ export const createReferrerStateAndShortUrl = async ({ connection, referrer, shortUrl, programId = PROGRAM_ID, }: { connection: Connection; referrer: Keypair; shortUrl: string; programId?: PublicKey; }) => { const ixn = getInitReferrerStateAndShortUrlIxns({ referrer: referrer.publicKey, shortUrl, programId }); const tx = await buildVersionedTransaction(connection, referrer.publicKey, [ixn]); console.log('Init ReferrerState for referrer ' + referrer.publicKey.toBase58() + ' and shortUrl ' + shortUrl); return await buildAndSendTxnWithLogs(connection, tx, referrer, [referrer]); }; /** * Initialize referrer state and short url * @param connection * @param referrer * @param newShortUrl */ export const updateReferrerStateAndShortUrl = async ({ connection, referrer, newShortUrl, programId = PROGRAM_ID, }: { connection: Connection; referrer: Keypair; newShortUrl: string; programId?: PublicKey; }) => { const deleteIxn = await getDeleteReferrerStateAndShortUrlIxns({ referrer: referrer.publicKey, connection, programId, }); const initIxn = getInitReferrerStateAndShortUrlIxns({ referrer: referrer.publicKey, shortUrl: newShortUrl, programId, }); const tx = await buildVersionedTransaction(connection, referrer.publicKey, [deleteIxn, initIxn]); console.log('Update ReferrerState for referrer ' + referrer.publicKey.toBase58() + ' and shortUrl ' + newShortUrl); return await buildAndSendTxnWithLogs(connection, tx, referrer, [referrer]); }; /** * Check if short URL available - also checks if short URL is valid (ascii-alphanumeric plus '_' '-', max 32 chars) * @param connection * @param shortUrl * @param programId */ export async function isShortUrlAvailable( connection: Connection, shortUrl: string, programId: PublicKey = PROGRAM_ID ): Promise<boolean> { if (shortUrl.length > 32) { return false; } if (!/^[a-zA-Z0-9-_]+$/.test(shortUrl)) { return false; } const [shortUrlAddress] = shortUrlPda(shortUrl, programId); const info = await connection.getAccountInfo(shortUrlAddress); return info === null; } /** * Get referrer short URL address and shortUrl * @param connection * @param referrer * @param programId */ export async function getReferrerShortUrl( connection: Connection, referrer: PublicKey, programId: PublicKey = PROGRAM_ID ): Promise<[PublicKey | null, string | null]> { const [referrerStateAddress] = referrerStatePda(referrer, programId); const referrerState = await ReferrerState.fetch(connection, referrerStateAddress, programId); const shortUrlState = await ShortUrl.fetch(connection, referrerState!.shortUrl, programId); const shortUrlAddress = referrerState ? referrerState.shortUrl : null; const shortUrl = shortUrlState ? shortUrlState.shortUrl : null; return [shortUrlAddress, shortUrl]; } /** * Get referrer for a given Short URL * @param connection * @param shortUrl * @param programId */ export async function getReferrerForShortUrl( connection: Connection, shortUrl: string, programId: PublicKey = PROGRAM_ID ): Promise<PublicKey> { const [shortUrlAddress] = shortUrlPda(shortUrl, programId); const shortUrlState = await ShortUrl.fetch(connection, shortUrlAddress, programId); return shortUrlState!.referrer; } /** * Get referrer all UserMetadata user accounts linked to a given referrer * @param connection * @param referrer * @param programId */ export async function getUserMetadatasByReferrer( connection: Connection, referrer: PublicKey, programId: PublicKey = PROGRAM_ID ): Promise<UserMetadata[]> { const userMetadatas = await connection.getProgramAccounts(programId, { filters: [ { dataSize: UserMetadata.layout.span + 8, }, { memcmp: { offset: 8, bytes: referrer.toBase58(), }, }, ], }); return userMetadatas.map((userMetadata) => { if (userMetadata.account === null) { throw new Error('Invalid account'); } if (!userMetadata.account.owner.equals(programId)) { throw new Error("account doesn't belong to this program"); } const userMetadataAccount = UserMetadata.decode(userMetadata.account.data); if (!userMetadataAccount) { throw Error('Could not parse obligation.'); } return userMetadataAccount; }); } /** * Get referrer all UserMetadata user accounts * @param connection * @param referrer * @param programId */ export async function getAllUserMetadatas( connection: Connection, programId: PublicKey = PROGRAM_ID ): Promise<UserMetadata[]> { const userMetadatas = await connection.getProgramAccounts(programId, { filters: [ { dataSize: UserMetadata.layout.span + 8, }, ], }); return userMetadatas.map((userMetadata) => { if (userMetadata.account === null) { throw new Error('Invalid account'); } if (!userMetadata.account.owner.equals(programId)) { throw new Error("account doesn't belong to this program"); } const userMetadataAccount = UserMetadata.decode(userMetadata.account.data); if (!userMetadataAccount) { throw Error('Could not parse obligation.'); } return userMetadataAccount; }); } /** * Get referrer all UserMetadata user accounts linked to a given referrer * @param connection * @param referrer * @param programId */ export async function getTotalUsersReferred( connection: Connection, referrer: PublicKey, programId: PublicKey = PROGRAM_ID ): Promise<number> { const userMetadatas = await getUserMetadatasByReferrer(connection, referrer, programId); return userMetadatas.length; } export type ReferralRank = { referrer: PublicKey; totalUsersReferred: number; totalEarningsUsd: Decimal; }; /** * Get ReferralRank array of all referrers ordered by how much they've earned in USD * @param connection * @param kaminoMarket */ export async function getReferralsRanking(connection: Connection, kaminoMarket: KaminoMarket): Promise<ReferralRank[]> { const referrersUsersReferred = new PublicKeyMap<PublicKey, number>(); // counting users referred for each referrer const userMetadatas = await getAllUserMetadatas(connection, kaminoMarket.programId); for (const userMetadata of userMetadatas) { const referrer = userMetadata.referrer; const usersReferred = referrersUsersReferred.get(referrer); if (usersReferred) { referrersUsersReferred.set(referrer, usersReferred + 1); } else { referrersUsersReferred.set(referrer, 1); } } const referralsRankArray: ReferralRank[] = []; for (const referrer of referrersUsersReferred.keys()) { const referrerTokenStates = await kaminoMarket.getAllReferrerFeesCumulative(referrer); let totalEarningsUsd = new Decimal(0); // calculating earnings for each referrer for (const reserve of kaminoMarket.reserves.values()) { totalEarningsUsd = totalEarningsUsd.add( referrerTokenStates.get(reserve.getLiquidityMint())!.mul(reserve.getOracleMarketPrice()) ); } referralsRankArray.push({ referrer: new PublicKey(referrer), totalUsersReferred: referrersUsersReferred.get(referrer)!, totalEarningsUsd: totalEarningsUsd, }); } referralsRankArray.sort((a, b) => { return b.totalEarningsUsd.comparedTo(a.totalEarningsUsd); }); return referralsRankArray; } /** * Get ReferralRank array of all referrers ordered by how much they've earned in USD * @param connection * @param user * @param kaminoMarket */ export async function getUserReferralRanking( connection: Connection, user: PublicKey, kaminoMarket: KaminoMarket ): Promise<number | undefined> { const referralsRanking = await getReferralsRanking(connection, kaminoMarket); for (let index = 0; index < referralsRanking.length; index++) { if (user.equals(referralsRanking[index].referrer)) { return index + 1; } } return undefined; }