UNPKG

@kamino-finance/klend-sdk

Version:

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

261 lines 10.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.positiveOrZero = exports.isEmptyObject = exports.valueOrZero = exports.isSolMint = exports.parseTokenSymbol = exports.getBorrowRate = exports.interpolate = exports.estimateCurrentScore = exports.calculateNewScore = void 0; exports.parseZeroPaddedUtf8 = parseZeroPaddedUtf8; exports.renderZeroPaddedUtf8 = renderZeroPaddedUtf8; exports.sleep = sleep; exports.numberToLamportsDecimal = numberToLamportsDecimal; exports.lamportsToNumberDecimal = lamportsToNumberDecimal; exports.lamportsToDecimal = lamportsToDecimal; exports.calculateAPYFromAPR = calculateAPYFromAPR; exports.calculateAPRFromAPY = calculateAPRFromAPY; exports.sameLengthArrayEquals = sameLengthArrayEquals; exports.getTokenBalanceFromAccountInfoLamports = getTokenBalanceFromAccountInfoLamports; exports.bpsToPct = bpsToPct; exports.truncateDecimals = truncateDecimals; exports.decodeVaultName = decodeVaultName; exports.pubkeyHashMapToJson = pubkeyHashMapToJson; exports.toJson = toJson; exports.assertNever = assertNever; exports.orThrow = orThrow; exports.blobEquals = blobEquals; exports.roundNearest = roundNearest; exports.getMedianSlotDurationInMsFromLastEpochs = getMedianSlotDurationInMsFromLastEpochs; const utils_1 = require("../utils"); const decimal_js_1 = __importDefault(require("decimal.js")); const spl_token_1 = require("@solana/spl-token"); const axios_1 = __importDefault(require("axios")); function getLatestRewardRate(rewardRates, slot) { return rewardRates .filter((rr) => slot >= rr.beginningSlot) .reduce((v1, v2) => (v1.beginningSlot > v2.beginningSlot ? v1 : v2), { beginningSlot: 0, rewardRate: '0', }); } const calculateNewScore = (rewardStat, pool, rewardRate, endSlot, startSlot) => { const { balance, debt, score } = pool; const { rewardsPerShare, totalBalance } = rewardStat; const oldDebt = new decimal_js_1.default(debt); const oldScore = new decimal_js_1.default(score); const oldRewardsPerShare = new decimal_js_1.default(rewardsPerShare); const oldBalance = new decimal_js_1.default(balance); const totalBalanceVal = new decimal_js_1.default(totalBalance); const newRewardsPerShare = !totalBalanceVal.isZero() ? oldRewardsPerShare.plus(new decimal_js_1.default(endSlot) .minus(new decimal_js_1.default(startSlot.toString())) .times(new decimal_js_1.default(rewardRate)) .div(totalBalanceVal) .div(new decimal_js_1.default(utils_1.SLOTS_PER_YEAR))) : new decimal_js_1.default(0); return oldScore.plus(newRewardsPerShare.times(oldBalance).minus(oldDebt)); }; exports.calculateNewScore = calculateNewScore; const estimateCurrentScore = (rewardStat, rewardScore, mostRecentSlot, mostRecentSlotTime) => { const { lastSlot, rewardRates } = rewardStat; const estimatedCurrentSlot = mostRecentSlot + utils_1.SLOTS_PER_SECOND * (Date.now() / 1000 - mostRecentSlotTime); const { rewardRate } = getLatestRewardRate(rewardRates, estimatedCurrentSlot); const currentScore = (0, exports.calculateNewScore)(rewardStat, rewardScore, rewardRate, estimatedCurrentSlot, lastSlot); return currentScore; }; exports.estimateCurrentScore = estimateCurrentScore; const interpolate = (x, x0, x1, y0, y1) => { if (x > x1) { throw 'Cannot do extrapolation'; } return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0); }; exports.interpolate = interpolate; const getBorrowRate = (currentUtilization, curve) => { let [x0, y0, x1, y1] = [0, 0, 0, 0]; if (curve.length < 2) { throw 'Invalid borrow rate curve, only one point'; } if (currentUtilization > 1) { currentUtilization = 1; } for (let i = 1; i < curve.length; i++) { const [pointUtilization, pointRate] = curve[i]; if (pointUtilization === currentUtilization) { return pointRate; } if (currentUtilization <= pointUtilization) { x0 = curve[i - 1][0]; y0 = curve[i - 1][1]; x1 = curve[i][0]; y1 = curve[i][1]; break; } } if (x0 === 0 && y0 === 0 && x1 === 0 && y1 === 0) { console.log('All are 0'); throw 'Invalid borrow rate curve, could not identify the interpolation points.'; } if (x0 >= x1 || y0 > y1) { console.log('(x0, y0), (x1, y1)', x0, y0, x1, y1); throw 'Invalid borrow rate curve, curve is not uniformly increasing'; } return (0, exports.interpolate)(currentUtilization, x0, x1, y0, y1); }; exports.getBorrowRate = getBorrowRate; const parseTokenSymbol = (tokenSymbol) => { return String.fromCharCode(...tokenSymbol.filter((x) => x > 0)); }; exports.parseTokenSymbol = parseTokenSymbol; function parseZeroPaddedUtf8(utf8Array) { for (let last = utf8Array.length - 1; last >= 0; last--) { const trailing_zero = utf8Array[last]; if (trailing_zero != 0) { const encoding = new Uint8Array(last + 1); for (let i = 0; i <= last; i++) { encoding[i] = utf8Array[i]; } return new TextDecoder().decode(encoding); } } return ''; } function renderZeroPaddedUtf8(str, utf8ArrayLength) { const encoding = new TextEncoder().encode(str); const result = new Array(utf8ArrayLength); for (let i = 0; i < result.length; i++) { result[i] = i < encoding.length ? encoding[i] : 0; } return result; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function numberToLamportsDecimal(amount, decimals) { const factor = 10 ** decimals; return new decimal_js_1.default(amount).mul(factor); } function lamportsToNumberDecimal(amount, decimals) { const factor = 10 ** decimals; return new decimal_js_1.default(amount).div(factor); } function lamportsToDecimal(amount, decimals) { const factor = new decimal_js_1.default(10).pow(decimals); return new decimal_js_1.default(amount).div(factor); } const isSolMint = (mint) => { return spl_token_1.NATIVE_MINT.equals(mint); }; exports.isSolMint = isSolMint; const valueOrZero = (value) => { const zero = new decimal_js_1.default(0); if (value.isNaN() || !value.isFinite()) { return zero; } else { return value; } }; exports.valueOrZero = valueOrZero; const isEmptyObject = (obj) => { return Object.keys(obj).length === 0 && obj.constructor === Object; }; exports.isEmptyObject = isEmptyObject; const positiveOrZero = (value) => { const zero = new decimal_js_1.default(0); return decimal_js_1.default.max(value, zero); }; exports.positiveOrZero = positiveOrZero; function calculateAPYFromAPR(apr) { const apy = new decimal_js_1.default(1).plus(new decimal_js_1.default(apr).dividedBy(utils_1.SLOTS_PER_YEAR)).toNumber() ** utils_1.SLOTS_PER_YEAR - 1; return apy; } function calculateAPRFromAPY(apy) { return new decimal_js_1.default(apy) .plus(1) .pow(1 / utils_1.SLOTS_PER_YEAR) .minus(1) .times(utils_1.SLOTS_PER_YEAR); } function sameLengthArrayEquals(left, right) { if (left.length != right.length) { throw new Error(`Not same length: ${left.length} != ${right.length}`); } return left.every((value, index) => { const other = right[index]; if (value != null && typeof value.eq === 'function') { return value.eq(other); } return value === other; }); } function getTokenBalanceFromAccountInfoLamports(accountInfo) { // Decode the buffer using the AccountLayout from @solana/spl-token const tokenAccountData = spl_token_1.AccountLayout.decode(accountInfo.data); // Extract the balance from the `amount` field, which is a 64-bit unsigned integer const balance = tokenAccountData.amount; return new decimal_js_1.default(balance.toString()); } function bpsToPct(bps) { return bps.div(100); } /** * Truncate ( not round ) number to keep up to max amount of decimals * @param num * @param maxDecimals */ function truncateDecimals(num, maxDecimals) { const factor = new decimal_js_1.default(10).pow(maxDecimals); return new decimal_js_1.default(num).times(factor).floor().dividedBy(factor); } /**Convert an u8 array to a string */ function decodeVaultName(token) { const maxArray = new Uint8Array(token); let s = new TextDecoder().decode(maxArray); // Remove trailing zeros and spaces s = s.replace(/[\0 ]+$/, ''); return s; } function pubkeyHashMapToJson(map) { const obj = {}; map.forEach((value, key) => { obj[key.toBase58()] = value.toString(); }); return obj; } function toJson(object, inline = false) { const replacer = (key, value) => (typeof value === 'bigint' ? value.toString() : value); return inline ? JSON.stringify(object, replacer) : JSON.stringify(object, replacer, 2); } function assertNever(x) { throw new Error('Unexpected object: ' + x); } function orThrow(message) { throw new Error(message); } function blobEquals(left, right) { if (left.length !== right.length) { return false; } for (let i = 0; i < left.length; ++i) { if (left[i] !== right[i]) { return false; } } return true; } /** * Returns an integer {@link Decimal} nearest to the given one. * * NOTE: the `Decimal.round()` should actually be called `.trunc()` (by default, it uses floor rounding). In most cases, * we prefer the traditional behavior (as `Math.round()`). */ function roundNearest(decimal) { return decimal.toDecimalPlaces(0, decimal_js_1.default.ROUND_HALF_CEIL); } /** * Fetch median slot duration in milliseconds from the last 10 epochs */ async function getMedianSlotDurationInMsFromLastEpochs() { const response = await axios_1.default.get('https://api.kamino.finance/slots/duration'); return response.data.recentSlotDurationInMs; } //# sourceMappingURL=utils.js.map