@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
326 lines (286 loc) • 9.2 kB
text/typescript
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;
}