@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
93 lines (83 loc) • 3.71 kB
text/typescript
import Decimal from 'decimal.js';
import {
DeleverageAllDebt,
DeleverageDebtAmount,
KaminoObligationOrder,
OrderCondition,
OrderOpportunity,
} from '../classes/obligationOrder';
import { checkThat, getSingleElement } from '../utils/validations';
import { ONE_HUNDRED_PCT_IN_BPS } from '../utils';
import { OrderAction, OrderActionType, OrderContext, OrderSpecification, OrderType } from './common';
// These methods are exported, buy only used internally within the obligation orders utils:
export function toOrderIndex(orderType: OrderType): number {
switch (orderType) {
case OrderType.StopLoss:
return 0;
case OrderType.TakeProfit:
return 1;
}
}
export function createConditionBasedOrder<C>(
context: OrderContext,
condition: OrderCondition,
specification: OrderSpecification<C>
): KaminoObligationOrder {
checkThat(condition.evaluate(context.kaminoObligation) === null, `cannot create an immediately-triggered order`);
const opportunity = toOrderOpportunity(context, specification.action);
const [minExecutionBonusRate, maxExecutionBonusRate] = toExecutionBonusRates(specification.executionBonusBpsRange);
return new KaminoObligationOrder(condition, opportunity, minExecutionBonusRate, maxExecutionBonusRate);
}
export function readTriggerBasedOrder<T>(kaminoOrder: KaminoObligationOrder, trigger: T): OrderSpecification<T> {
return {
trigger,
action: toAction(kaminoOrder.opportunity),
executionBonusBpsRange: toExecutionBonusBps(kaminoOrder.minExecutionBonusRate, kaminoOrder.maxExecutionBonusRate),
};
}
// Only internals below:
function toOrderOpportunity(context: OrderContext, action: OrderAction): OrderOpportunity {
switch (action.type) {
case OrderActionType.FullRepay:
return new DeleverageAllDebt();
case OrderActionType.PartialRepay:
const { repayDebtAmountLamports } = action;
checkThat(repayDebtAmountLamports.gt(0), `repay amount must be positive; got ${repayDebtAmountLamports}`);
const availableDebtAmountLamports = getSingleElement(context.kaminoObligation.getBorrows(), 'borrow').amount;
checkThat(
repayDebtAmountLamports.lte(availableDebtAmountLamports),
`partial repay amount ${repayDebtAmountLamports} cannot exceed the borrowed amount ${availableDebtAmountLamports}`
);
return new DeleverageDebtAmount(action.repayDebtAmountLamports);
}
}
function toExecutionBonusRates(executionBonusBpsRange: [number, number]): [Decimal, Decimal] {
const [minExecutionBonusRate, maxExecutionBonusRate] = executionBonusBpsRange.map((bps) =>
new Decimal(bps).div(ONE_HUNDRED_PCT_IN_BPS)
);
checkThat(minExecutionBonusRate.gte(0), `execution bonus rate cannot be negative: ${minExecutionBonusRate}`);
checkThat(
maxExecutionBonusRate.gte(minExecutionBonusRate),
`max execution bonus rate ${maxExecutionBonusRate} cannot be lower than min ${minExecutionBonusRate}`
);
return [minExecutionBonusRate, maxExecutionBonusRate];
}
function toAction(opportunity: OrderOpportunity): OrderAction {
if (opportunity instanceof DeleverageAllDebt) {
return {
type: OrderActionType.FullRepay,
};
}
if (opportunity instanceof DeleverageDebtAmount) {
return {
type: OrderActionType.PartialRepay,
repayDebtAmountLamports: opportunity.amount,
};
}
throw new Error(`incompatible on-chain opportunity ${opportunity.constructor.name}`);
}
function toExecutionBonusBps(minExecutionBonusRate: Decimal, maxExecutionBonusRate: Decimal): [number, number] {
return [minExecutionBonusRate, maxExecutionBonusRate].map((rate) =>
new Decimal(rate).mul(ONE_HUNDRED_PCT_IN_BPS).toNumber()
) as [number, number];
}