diff --git a/solend-lite/package.json b/solend-lite/package.json
index 406b52cf..d5dba9b0 100644
--- a/solend-lite/package.json
+++ b/solend-lite/package.json
@@ -48,6 +48,7 @@
"react-no-ssr": "^1.1.0",
"react-svg": "^16.0.0",
"react-timer-hook": "^3.0.5",
+ "recharts": "^2.7.2",
"sass": "^1.57.1",
"string-comparison": "^1.1.0",
"typescript": "4.9.4"
diff --git a/solend-lite/src/components/InterestGraph/InterestGraph.tsx b/solend-lite/src/components/InterestGraph/InterestGraph.tsx
new file mode 100644
index 00000000..1736393c
--- /dev/null
+++ b/solend-lite/src/components/InterestGraph/InterestGraph.tsx
@@ -0,0 +1,165 @@
+import React, { ReactElement } from 'react';
+import { ReserveType } from '@solendprotocol/solend-sdk';
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+ ReferenceDot,
+ Label,
+} from 'recharts';
+import { themeConfig } from 'theme/theme';
+import { Box, Text } from '@chakra-ui/react';
+import { formatPercent } from 'utils/numberFormatter';
+
+const CustomTooltip = ({
+ active,
+ payload,
+ label,
+}: {
+ active?: boolean;
+ payload?: Array<{
+ payload?: { name: string };
+ value?: number;
+ }>;
+ label?: number;
+}) => {
+ if (active && payload && payload.length) {
+ return (
+
+ {payload[0]?.payload?.name}
+ {`Utilization: ${formatPercent(
+ (payload[0]?.value ?? 0) / 100,
+ )}`}
+ {`Interest: ${formatPercent(
+ (label ?? 0) / 100,
+ )}`}
+
+
+ Click to return to parameter view
+
+
+ );
+ }
+
+ return null;
+};
+
+function InterestGraph({ reserve }: { reserve: ReserveType }): ReactElement {
+ const data = [
+ {
+ name: 'Min borrow rate',
+ utilization: 0,
+ interest: reserve.minBorrowApr * 100,
+ current: false,
+ },
+ {
+ name: 'Target rate',
+ utilization: reserve.targetUtilization * 100,
+ interest: reserve.targetBorrowApr * 100,
+ current: false,
+ },
+ {
+ name: 'Max rate',
+ utilization: reserve.maxUtilizationRate * 100,
+ interest: reserve.maxBorrowApr * 100,
+ current: false,
+ },
+ {
+ name: 'Supermax rate',
+ utilization: 100,
+ interest: reserve.superMaxBorrowRate * 100,
+ current: false,
+ },
+ {
+ name: 'Current',
+ utilization: reserve.reserveUtilization.toNumber() * 100,
+ interest: reserve.borrowInterest.toNumber() * 100,
+ current: true,
+ },
+ ].sort((a, b) => a.utilization - b.utilization);
+
+ return (
+
+
+
+
+
+
+
+
+
+ ;
+ label?: number;
+ }) => (
+
+ )}
+ />
+
+
+
+
+ );
+}
+
+export default InterestGraph;
diff --git a/solend-lite/src/components/Metric/Metric.module.scss b/solend-lite/src/components/Metric/Metric.module.scss
index a6c2f45f..51d38c8a 100644
--- a/solend-lite/src/components/Metric/Metric.module.scss
+++ b/solend-lite/src/components/Metric/Metric.module.scss
@@ -25,3 +25,10 @@
}
}
}
+
+.rowMetric {
+ .label {
+ align-items: center;
+ gap: 4px;
+ }
+}
diff --git a/solend-lite/src/components/Metric/Metric.tsx b/solend-lite/src/components/Metric/Metric.tsx
index 22dbd538..016dac30 100644
--- a/solend-lite/src/components/Metric/Metric.tsx
+++ b/solend-lite/src/components/Metric/Metric.tsx
@@ -35,7 +35,11 @@ function Metric({
align={row ? 'center' : undefined}
justify='space-between'
style={style}
- className={classNames(styles.alignCenter, !alignCenter && styles.metric)}
+ className={classNames(
+ 'metric',
+ styles.alignCenter,
+ !alignCenter && (row ? styles.rowMetric : styles.metric),
+ )}
>
{label && (
diff --git a/solend-lite/src/components/Pool/PoolTable/PoolTable.tsx b/solend-lite/src/components/Pool/PoolTable/PoolTable.tsx
index da86d23d..18ed0131 100644
--- a/solend-lite/src/components/Pool/PoolTable/PoolTable.tsx
+++ b/solend-lite/src/components/Pool/PoolTable/PoolTable.tsx
@@ -47,17 +47,19 @@ export default function PoolTable({
header: 'Open LTV / BW',
meta: { isNumeric: true },
cell: ({ row: { original: reserve } }) => (
-
- {formatPercent(reserve.loanToValueRatio, false, 0)}
-
+ <>
+
+ {formatPercent(reserve.loanToValueRatio, false, 0)}
+ {' '}
+
/
-
-
+ {' '}
+
{reserve.addedBorrowWeightBPS.toString() !== U64_MAX
? formatToken(reserve.borrowWeight.toString(), 2, false, true)
: '∞'}
-
+ >
),
}),
columnHelper.accessor('totalSupplyUsd', {
diff --git a/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.module.scss b/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.module.scss
index 9a9f3399..5ebbe861 100644
--- a/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.module.scss
+++ b/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.module.scss
@@ -12,7 +12,7 @@
.params {
background-color: var(--chakra-colors-neutralAlt);
padding-top: 8px;
- padding-bottom: 32px;
+ padding-bottom: 8px;
margin-left: -36px;
margin-right: -36px;
padding-left: 36px;
@@ -37,3 +37,19 @@
.reserveAddress {
cursor: pointer;
}
+
+.rateSection {
+ &:hover {
+ opacity: 0.5;
+ :global(.metric) {
+ filter: blur(0.25px);
+ }
+ .graphHover {
+ visibility: visible;
+ }
+ }
+}
+
+.graphHover {
+ visibility: hidden;
+}
diff --git a/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.tsx b/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.tsx
index 3e6d88d1..751e7b96 100644
--- a/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.tsx
+++ b/solend-lite/src/components/TransactionTakeover/ReserveStats/ReserveStats.tsx
@@ -11,6 +11,7 @@ import { computeExtremeRates } from '@solendprotocol/solend-sdk';
import { useAtom } from 'jotai';
import humanizeDuration from 'humanize-duration';
import { SLOT_RATE } from 'utils/utils';
+import InterestGraph from 'components/InterestGraph/InterestGraph';
// certain oracles do not match their underlying asset, hence this mapping
const PYTH_ORACLE_MAPPING: Record = {
@@ -49,6 +50,7 @@ function ReserveStats({
}: ReserveStatsPropsType): ReactElement {
const [rateLimiter] = useAtom(rateLimiterAtom);
const [showParams, setShowParams] = useState(false);
+ const [showGraph, setShowGraph] = useState(false);
let newBorrowLimitDisplay = null;
if (newBorrowLimit) {
const nbuObj = newBorrowLimit;
@@ -166,10 +168,112 @@ function ReserveStats({
showParams ? styles.visible : styles.hidden,
)}
style={{
- maxHeight: showParams ? 500 : 0,
+ maxHeight: showParams ? 1000 : 0,
display: showParams ? 'visible' : 'hidden',
}}
>
+ {showGraph && (
+ setShowGraph(false)}>
+
+
+ Interest rate curve
+
+
+
+
+ )}
+ {!showGraph && (
+ setShowGraph(true)}
+ >
+
+ Percentage of the asset being lent out. Utilization determines
+ interest rates via a function.{' '}
+
+ Learn more
+
+ .
+ >
+ }
+ />
+
+
+
+
+
+
+
+
+ Click to show graph
+
+
+
+ )}
{reserve.reserveSupplyLimit && (
+
+ {formatPercent(reserve.protocolLiquidationFee)}>}
+ tooltip='Liquidation penalty increases past close LTV until max close LTV, where max liquidation penalty occurs.'
+ />
{formatPercent(reserve.interestRateSpread)}>}
/>
-
-
-
-
- Percentage of the asset being lent out. Utilization determines
- interest rates via a function.{' '}
-
- Learn more
-
- .
- >
- }
- />
}
/>
+ {rateLimiter && (
+
+ {formatToken(
+ new BigNumber(
+ rateLimiter.config.maxOutflow.toString(),
+ reserve.decimals,
+ ).toString(),
+ )}{' '}
+ {reserve.symbol} per{' '}
+ {humanizeDuration(
+ (rateLimiter.config.windowDuration.toNumber() / SLOT_RATE) *
+ 1000,
+ )}
+ >
+ )
+ }
+ tooltip={
+ <>
+ For the safety of the pool, amounts being withdrawn or borrowed
+ from the pool are limited by this rate.
+ Remaining outflow this window:{' '}
+ {formatUsd(
+ rateLimiter.remainingOutflow?.toString() ?? '0',
+ false,
+ true,
+ )}
+ >
+ }
+ />
+ )}
+ {!new BigNumber(reserve.borrowWeight).isEqualTo(new BigNumber(0)) && (
+
+ )}
{reserve.pythOracle !==
'nu11111111111111111111111111111111111111111' && (
}
/>
- {rateLimiter && (
-
- {formatToken(
- new BigNumber(
- rateLimiter.config.maxOutflow.toString(),
- reserve.decimals,
- ).toString(),
- )}{' '}
- {reserve.symbol} per{' '}
- {humanizeDuration(
- (rateLimiter.config.windowDuration.toNumber() / SLOT_RATE) *
- 1000,
- )}
- >
- )
- }
- tooltip={
- <>
- For the safety of the pool, amounts being withdrawn or borrowed
- from the pool are limited by this rate.
- Remaining outflow this window:{' '}
- {formatUsd(
- rateLimiter.remainingOutflow?.toString() ?? '0',
- false,
- true,
- )}
- >
- }
- />
- )}
- {!new BigNumber(reserve.borrowWeight).isEqualTo(new BigNumber(0)) && (
-
- )}
);
diff --git a/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx b/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx
index 77bdadb0..6cfe100f 100644
--- a/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx
+++ b/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx
@@ -62,10 +62,10 @@ export default function TransactionTakeover() {
selectedReserve,
walletAssets,
selectedObligation,
- rateLimiter
+ rateLimiter,
)
: BigNumber(0),
- [selectedReserve, walletAssets, selectedObligation],
+ [selectedReserve, walletAssets, selectedObligation, rateLimiter],
);
const withdrawMax = useMemo(
() =>
@@ -74,10 +74,10 @@ export default function TransactionTakeover() {
selectedReserve,
walletAssets,
selectedObligation,
- rateLimiter
+ rateLimiter,
)
: BigNumber(0),
- [selectedReserve, walletAssets, selectedObligation],
+ [selectedReserve, walletAssets, selectedObligation, rateLimiter],
);
const repayMax = useMemo(
() =>
@@ -177,7 +177,7 @@ export default function TransactionTakeover() {
const useMax = new BigNumber(value).isGreaterThanOrEqualTo(
borrowMax,
);
-
+ console.log(useMax);
return borrowConfigs.action(
useMax
? U64_MAX
@@ -246,7 +246,7 @@ export default function TransactionTakeover() {
selectedObligation,
selectedReserve,
walletAssets,
- rateLimiter
+ rateLimiter,
)}
getNewCalculations={withdrawConfigs.getNewCalculations}
/>
diff --git a/solend-lite/src/components/Wallet/Wallet.tsx b/solend-lite/src/components/Wallet/Wallet.tsx
index ea05fe28..d92f7874 100644
--- a/solend-lite/src/components/Wallet/Wallet.tsx
+++ b/solend-lite/src/components/Wallet/Wallet.tsx
@@ -47,7 +47,7 @@ export default function Wallet() {
- Wallet asset
+ Wallet assets
|
diff --git a/solend-sdk/src/core/utils/pools.ts b/solend-sdk/src/core/utils/pools.ts
index 9ab6eecc..d9d94c50 100644
--- a/solend-sdk/src/core/utils/pools.ts
+++ b/solend-sdk/src/core/utils/pools.ts
@@ -114,7 +114,10 @@ export function formatReserve(
).shiftedBy(-decimals),
targetBorrowApr: reserve.info.config.optimalBorrowRate / 100,
targetUtilization: reserve.info.config.optimalUtilizationRate / 100,
+ maxUtilizationRate: reserve.info.config.maxUtilizationRate / 100,
+ minBorrowApr: reserve.info.config.minBorrowRate / 100,
maxBorrowApr: reserve.info.config.maxBorrowRate / 100,
+ superMaxBorrowRate: reserve.info.config.superMaxBorrowRate / 100,
supplyInterest: calculateSupplyInterest(reserve.info, false),
borrowInterest: calculateBorrowInterest(reserve.info, false),
totalSupply,
@@ -126,7 +129,9 @@ export function formatReserve(
availableAmountUsd: availableAmount.times(priceResolved),
loanToValueRatio: reserve.info.config.loanToValueRatio / 100,
liquidationThreshold: reserve.info.config.liquidationThreshold / 100,
+ maxLiquidationThreshold: reserve.info.config.maxLiquidationThreshold / 100,
liquidationPenalty: reserve.info.config.liquidationBonus / 100,
+ maxLiquidationPenalty: reserve.info.config.maxLiquidationBonus / 100,
liquidityAddress: reserve.info.liquidity.supplyPubkey.toBase58(),
cTokenLiquidityAddress: reserve.info.collateral.supplyPubkey.toBase58(),
liquidityFeeReceiverAddress: reserve.info.config.feeReceiver.toBase58(),
diff --git a/solend-sdk/src/core/utils/rates.ts b/solend-sdk/src/core/utils/rates.ts
index 405a7352..1c805c48 100644
--- a/solend-sdk/src/core/utils/rates.ts
+++ b/solend-sdk/src/core/utils/rates.ts
@@ -30,34 +30,50 @@ const calculateBorrowAPR = (reserve: Reserve) => {
const optimalUtilization = new BigNumber(
reserve.config.optimalUtilizationRate / 100
);
-
+ const maxUtilizationRate = new BigNumber(
+ reserve.config.maxUtilizationRate
+ );
let borrowAPR;
if (
- optimalUtilization.isEqualTo(1) ||
- currentUtilization.isLessThan(optimalUtilization)
+ currentUtilization.isLessThanOrEqualTo(
+ optimalUtilization
+ )
) {
+ const minBorrowRate = new BigNumber(reserve.config.minBorrowRate / 100)
+ if (optimalUtilization.isEqualTo(0)) {
+ return minBorrowRate;
+ }
const normalizedFactor = currentUtilization.dividedBy(optimalUtilization);
+
const optimalBorrowRate = new BigNumber(
reserve.config.optimalBorrowRate / 100
);
- const minBorrowRate = new BigNumber(reserve.config.minBorrowRate / 100);
+
borrowAPR = normalizedFactor
.times(optimalBorrowRate.minus(minBorrowRate))
.plus(minBorrowRate);
- } else {
- if (reserve.config.optimalBorrowRate === reserve.config.maxBorrowRate) {
- return new BigNumber(
- computeExtremeRates(reserve.config.maxBorrowRate / 100)
+ } else if (currentUtilization.isLessThanOrEqualTo(maxUtilizationRate)) {
+ const weight = currentUtilization
+ .minus(optimalUtilization)
+ .dividedBy(maxUtilizationRate.minus(optimalUtilization));
+
+ const optimalBorrowRate = new BigNumber(
+ reserve.config.optimalBorrowRate / 100
);
- }
- const normalizedFactor = currentUtilization
+ const maxBorrowRate = new BigNumber(reserve.config.maxBorrowRate / 100);
+
+ borrowAPR = weight.times(maxBorrowRate.minus(optimalBorrowRate)).plus(optimalBorrowRate)
+ } else {
+ const weight = currentUtilization
.minus(optimalUtilization)
- .dividedBy(new BigNumber(1).minus(optimalUtilization));
- const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
- const maxBorrowRate = reserve.config.maxBorrowRate / 100;
- borrowAPR = normalizedFactor
- .times(maxBorrowRate - optimalBorrowRate)
- .plus(optimalBorrowRate);
+ .dividedBy(maxUtilizationRate.minus(optimalUtilization));
+
+ const maxBorrowRate = new BigNumber(reserve.config.maxBorrowRate / 100)
+ const superMaxBorrowRate = new BigNumber(reserve.config.superMaxBorrowRate.toString()).dividedBy(100);
+
+ borrowAPR = weight
+ .times(superMaxBorrowRate.minus(maxBorrowRate))
+ .plus(maxBorrowRate);
}
return borrowAPR;
diff --git a/solend-sdk/src/instructions/index.ts b/solend-sdk/src/instructions/index.ts
index d8ec71c7..17dde38c 100644
--- a/solend-sdk/src/instructions/index.ts
+++ b/solend-sdk/src/instructions/index.ts
@@ -15,4 +15,7 @@ export * from "./initReserve";
export * from "./updateReserveConfig";
export * from "./flashBorrowReserveLiquidity";
export * from "./flashRepayReserveLiquidity";
-export * from "./instruction";
+export * from "./forgiveDebt";
+export * from "./setLendingMarketOwnerAndConfig";
+export * from "./updateMetadata";
+export * from "./instruction";
\ No newline at end of file
diff --git a/solend-sdk/src/state/reserve.ts b/solend-sdk/src/state/reserve.ts
index f25380b5..9c598875 100644
--- a/solend-sdk/src/state/reserve.ts
+++ b/solend-sdk/src/state/reserve.ts
@@ -52,7 +52,7 @@ export interface ReserveConfig {
minBorrowRate: number;
optimalBorrowRate: number;
maxBorrowRate: number;
- superMaxBorrowRate: BN;
+ superMaxBorrowRate: number;
fees: {
borrowFeeWad: BN;
flashLoanFeeWad: BN;
@@ -183,16 +183,16 @@ function decodeReserve(buffer: Buffer): Reserve {
},
config: {
optimalUtilizationRate: reserve.optimalUtilizationRate,
- maxUtilizationRate: reserve.maxUtilizationRate,
+ maxUtilizationRate: Math.max(reserve.maxUtilizationRate, reserve.optimalUtilizationRate),
loanToValueRatio: reserve.loanToValueRatio,
liquidationBonus: reserve.liquidationBonus,
- maxLiquidationBonus: reserve.maxLiquidationBonus,
+ maxLiquidationBonus: Math.max(reserve.maxLiquidationBonus, reserve.liquidationBonus),
liquidationThreshold: reserve.liquidationThreshold,
- maxLiquidationThreshold: reserve.maxLiquidationThreshold,
+ maxLiquidationThreshold: Math.max(reserve.maxLiquidationThreshold, reserve.liquidationThreshold),
minBorrowRate: reserve.minBorrowRate,
optimalBorrowRate: reserve.optimalBorrowRate,
maxBorrowRate: reserve.maxBorrowRate,
- superMaxBorrowRate: reserve.superMaxBorrowRate,
+ superMaxBorrowRate: Math.max(reserve.superMaxBorrowRate, reserve.maxBorrowRate),
fees: {
borrowFeeWad: reserve.borrowFeeWad,
flashLoanFeeWad: reserve.flashLoanFeeWad,
@@ -201,7 +201,7 @@ function decodeReserve(buffer: Buffer): Reserve {
depositLimit: reserve.depositLimit,
borrowLimit: reserve.borrowLimit,
feeReceiver: reserve.feeReceiver,
- protocolLiquidationFee: reserve.protocolLiquidationFee,
+ protocolLiquidationFee: reserve.protocolLiquidationFee * 10,
protocolTakeRate: reserve.protocolTakeRate,
addedBorrowWeightBPS: reserve.addedBorrowWeightBPS,
borrowWeight: new BigNumber(reserve.addedBorrowWeightBPS.toString())
|