more refactoring

This commit is contained in:
jordansexton 2021-07-02 11:49:08 -05:00
parent 10a6a5d5cb
commit e8fae316f8
22 changed files with 76 additions and 383 deletions

View File

@ -25,9 +25,9 @@
"@types/testing-library__react": "^10.2.0",
"@welldone-software/why-did-you-render": "^6.0.5",
"antd": "^4.6.6",
"bignumber.js": "^9.0.1",
"bn.js": "^5.1.3",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.1",
"chart.js": "^2.9.4",
"craco-alias": "^2.1.1",
"craco-babel-loader": "^0.1.4",

View File

@ -10,7 +10,7 @@ import { LendingMarket, Reserve } from '@solana/spl-token-lending';
import { Card, Select } from 'antd';
import React, { useEffect, useState } from 'react';
import {
useLendingReserves,
useReserves,
useUserBalance,
useUserDeposits,
} from '../../hooks';
@ -41,7 +41,7 @@ export default function CollateralInput(props: {
const { balance: tokenBalance } = useUserBalance(
props.reserve.liquidity.mintPubkey,
);
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
const { tokenMap } = useConnectionConfig();
const [depositReserve, setCollateralReserve] = useState<string>();
const [balance, setBalance] = useState<number>(0);

View File

@ -8,7 +8,7 @@ import {
import { LendingMarket, Reserve } from '@solana/spl-token-lending';
import { Select } from 'antd';
import React from 'react';
import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks';
import { useReserves, UserDeposit, useUserDeposits } from '../../hooks';
const { cache } = contexts.Accounts;
const { useConnectionConfig } = contexts.Connection;
@ -42,7 +42,7 @@ export const CollateralSelector = (props: {
disabled?: boolean;
onCollateralReserve?: (id: string) => void;
}) => {
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
const { tokenMap } = useConnectionConfig();
const { userDeposits } = useUserDeposits();

View File

@ -7,7 +7,7 @@ import {
} from '@solana/spl-token-lending';
import { AccountInfo, PublicKey } from '@solana/web3.js';
import React, { useCallback, useEffect, useState } from 'react';
import { useLendingReserves } from '../hooks';
import { useReserves } from '../hooks';
import {
LendingMarketParser,
ObligationParser,
@ -38,7 +38,7 @@ export function LendingProvider({ children = null as any }) {
export const useLending = () => {
const connection = useConnection();
const [lendingAccounts, setLendingAccounts] = useState<any[]>([]);
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
const precacheMarkets = usePrecacheMarket();
// TODO: query for all the dex from reserves

View File

@ -2,8 +2,8 @@ export * from './useBorrowedAmount';
export * from './useBorrowingPower';
export * from './useCollateralBalance';
export * from './useLendingMarket';
export * from './useLendingObligations';
export * from './useLendingReserves';
export * from './useObligations';
export * from './useReserves';
export * from './useSliderInput';
export * from './useUserBalance';
export * from './useUserDeposits';

View File

@ -1,69 +0,0 @@
import { contexts, fromLamports, wadToLamports } from '@oyster/common';
import { PublicKey } from '@solana/web3.js';
import { useEffect, useState } from 'react';
import { useLendingReserve } from './useLendingReserves';
import { useUserObligationByReserve } from './useUserObligationByReserve';
const { useMint } = contexts.Accounts;
const { useConnection } = contexts.Connection;
export function useBorrowedAmount(address?: string | PublicKey) {
const connection = useConnection();
const { userObligationsByReserve } = useUserObligationByReserve(address);
const [borrowedInfo, setBorrowedInfo] = useState({
borrowedLamports: 0,
borrowedInUSD: 0,
collateralInUSD: 0,
ltv: 0,
health: 0,
});
const reserve = useLendingReserve(address);
const liquidityMint = useMint(reserve?.info.liquidity.mintPubkey);
useEffect(() => {
setBorrowedInfo({
borrowedLamports: 0,
borrowedInUSD: 0,
collateralInUSD: 0,
ltv: 0,
health: 0,
});
(async () => {
const result = {
borrowedLamports: wadToLamports(reserve.info.liquidity.borrowedAmountWads).toNumber(),
borrowedInUSD: 0,
collateralInUSD: 0,
ltv: 0,
health: 0,
};
let liquidationThreshold = reserve.info.config.liquidationThreshold;
// @FIXME: see if this requires obligations
userObligationsByReserve.forEach(item => {
result.borrowedInUSD += item.obligation.info.borrowedValue;
result.collateralInUSD += item.obligation.info.depositedValue;
}, 0);
if (userObligationsByReserve.length === 1) {
result.ltv = userObligationsByReserve[0].obligation.info.ltv;
result.health = userObligationsByReserve[0].obligation.info.health;
} else {
result.ltv = (100 * result.borrowedInUSD) / result.collateralInUSD;
result.health =
(result.collateralInUSD * liquidationThreshold) /
100 /
result.borrowedInUSD;
result.health = Number.isFinite(result.health) ? result.health : 0;
}
setBorrowedInfo(result);
})();
}, [connection, reserve, userObligationsByReserve, setBorrowedInfo]);
return {
borrowed: fromLamports(borrowedInfo.borrowedLamports, liquidityMint),
...borrowedInfo,
};
}

View File

@ -6,20 +6,20 @@ import { ObligationParser } from '../models';
const { cache } = contexts.Accounts;
const getLendingObligations = () => {
const getObligations = () => {
return cache
.byParser(ObligationParser)
.map(id => cache.get(id))
.filter(acc => acc !== undefined) as ParsedAccount<Obligation>[];
};
export function useLendingObligations() {
const [obligations, setObligations] = useState(getLendingObligations());
export function useObligations() {
const [obligations, setObligations] = useState(getObligations());
useEffect(() => {
const dispose = cache.emitter.onCache(args => {
if (args.parser === ObligationParser) {
setObligations(getLendingObligations());
setObligations(getObligations());
}
});

View File

@ -8,22 +8,22 @@ import { ReserveParser } from '../models';
const { cache } = contexts.Accounts;
const { useConnectionConfig } = contexts.Connection;
export const getLendingReserves = () => {
export const getReserves = () => {
return cache
.byParser(ReserveParser)
.map(id => cache.get(id))
.filter(acc => acc !== undefined) as ParsedAccount<Reserve>[];
};
export function useLendingReserves() {
export function useReserves() {
const [reserveAccounts, setReserveAccounts] = useState<
ParsedAccount<Reserve>[]
>(getLendingReserves());
>(getReserves());
useEffect(() => {
const dispose = cache.emitter.onCache(args => {
if (args.parser === ReserveParser) {
setReserveAccounts(getLendingReserves());
setReserveAccounts(getReserves());
}
});
@ -37,9 +37,9 @@ export function useLendingReserves() {
};
}
export function useLendingReserve(address?: string | PublicKey) {
export function useReserve(address?: string | PublicKey) {
const { tokenMap } = useConnectionConfig();
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
let addressName = address;
if (typeof address === 'string') {
const token: TokenInfo | null = getTokenByName(tokenMap, address);

View File

@ -12,7 +12,7 @@ import { useEffect, useMemo, useState } from 'react';
import { usePyth } from '../contexts/pyth';
import { calculateDepositAPY } from '../models';
import { calculateCollateralBalance } from './useCollateralBalance';
import { useLendingReserves } from './useLendingReserves';
import { useReserves } from './useReserves';
const { cache } = contexts.Accounts;
const { useConnectionConfig } = contexts.Connection;
@ -31,7 +31,7 @@ export interface UserDeposit {
export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
const { userAccounts } = useUserAccounts();
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
const [userDeposits, setUserDeposits] = useState<UserDeposit[]>([]);
const { getPrice } = usePyth();
const { tokenMap } = useConnectionConfig();

View File

@ -1,46 +0,0 @@
import { PublicKey } from '@solana/web3.js';
import { useMemo } from 'react';
import { useUserObligations } from './useUserObligations';
export function useUserObligationByReserve(
borrowReserve?: string | PublicKey,
depositReserve?: string | PublicKey,
) {
const { userObligations } = useUserObligations();
const userObligationsByReserve = useMemo(() => {
const borrowReservePubkey =
typeof borrowReserve === 'string'
? borrowReserve
: borrowReserve?.toBase58();
const depositReservePubkey =
typeof depositReserve === 'string'
? depositReserve
: depositReserve?.toBase58();
// @FIXME: support multiple deposits/borrows
return userObligations.filter(item => {
// @FIXME: borrows and deposits may be empty
if (borrowReservePubkey && depositReservePubkey) {
return (
item.obligation.info.borrows[0].borrowReserve.toBase58() ===
borrowReservePubkey &&
item.obligation.info.deposits[0].depositReserve.toBase58() ===
depositReservePubkey
);
} else {
return (
(borrowReservePubkey &&
item.obligation.info.borrows[0].borrowReserve.toBase58() ===
borrowReservePubkey) ||
(depositReservePubkey &&
item.obligation.info.deposits[0].depositReserve.toBase58() ===
depositReservePubkey)
);
}
});
}, [borrowReserve, depositReserve, userObligations]);
return {
userObligationsByReserve,
};
}

View File

@ -1,10 +1,10 @@
import { useWallet } from '@oyster/common';
import { useMemo } from 'react';
import { useLendingObligations } from './useLendingObligations';
import { useObligations } from './useObligations';
export function useUserObligations() {
const { wallet } = useWallet();
const { obligations } = useLendingObligations();
const { obligations } = useObligations();
const userObligations = useMemo(() => {
return obligations
@ -15,14 +15,17 @@ export function useUserObligations() {
.map(obligation => ({ obligation }))
.sort(
(a, b) =>
b.obligation.info.borrowedValue.toNumber() -
a.obligation.info.borrowedValue.toNumber(),
b.obligation.info.borrowedValue.minus(a.obligation.info.borrowedValue).toNumber(),
);
}, [obligations]);
return {
userObligations,
totalInQuote: userObligations.reduce(
totalDepositedValue: userObligations.reduce(
(result, item) => result + item.obligation.info.depositedValue.toNumber(),
0,
),
totalBorrowedValue: userObligations.reduce(
(result, item) => result + item.obligation.info.borrowedValue.toNumber(),
0,
),

View File

@ -1,88 +0,0 @@
// https://github.com/project-serum/anchor/blob/master/ts/types/buffer-layout/index.d.ts
declare module 'buffer-layout' {
// TODO: remove `any`.
export class Layout<T = any> {
span: number;
property?: string;
constructor(span: number, property?: string);
decode(b: Buffer, offset?: number): T;
encode(src: T, b: Buffer, offset?: number): number;
getSpan(b: Buffer, offset?: number): number;
replicate(name: string): this;
}
// TODO: remove any.
export class Structure<T = any> extends Layout<T> {
span: any;
}
export function greedy(
elementSpan?: number,
property?: string,
): Layout<number>;
export function offset<T>(
layout: Layout<T>,
offset?: number,
property?: string,
): Layout<T>;
export function u8(property?: string): Layout<number>;
export function u16(property?: string): Layout<number>;
export function u24(property?: string): Layout<number>;
export function u32(property?: string): Layout<number>;
export function u40(property?: string): Layout<number>;
export function u48(property?: string): Layout<number>;
export function nu64(property?: string): Layout<number>;
export function u16be(property?: string): Layout<number>;
export function u24be(property?: string): Layout<number>;
export function u32be(property?: string): Layout<number>;
export function u40be(property?: string): Layout<number>;
export function u48be(property?: string): Layout<number>;
export function nu64be(property?: string): Layout<number>;
export function s8(property?: string): Layout<number>;
export function s16(property?: string): Layout<number>;
export function s24(property?: string): Layout<number>;
export function s32(property?: string): Layout<number>;
export function s40(property?: string): Layout<number>;
export function s48(property?: string): Layout<number>;
export function ns64(property?: string): Layout<number>;
export function s16be(property?: string): Layout<number>;
export function s24be(property?: string): Layout<number>;
export function s32be(property?: string): Layout<number>;
export function s40be(property?: string): Layout<number>;
export function s48be(property?: string): Layout<number>;
export function ns64be(property?: string): Layout<number>;
export function f32(property?: string): Layout<number>;
export function f32be(property?: string): Layout<number>;
export function f64(property?: string): Layout<number>;
export function f64be(property?: string): Layout<number>;
export function struct<T>(
fields: Layout<any>[],
property?: string,
decodePrefixes?: boolean,
): Layout<T>;
export function bits(
word: Layout<number>,
msb?: boolean,
property?: string,
): any;
export function seq<T>(
elementLayout: Layout<T>,
count: number | Layout<number>,
property?: string,
): Layout<T[]>;
export function union(
discr: Layout<any>,
defaultLayout?: any,
property?: string,
): any;
export function unionLayoutDiscriminator(
layout: Layout<any>,
property?: string,
): any;
export function blob(
length: number | Layout<number>,
property?: string,
): Layout<Buffer>;
export function cstr(property?: string): Layout<string>;
export function utf8(maxSpan: number, property?: string): Layout<string>;
}

View File

@ -1,137 +0,0 @@
import { PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
/**
* Layout for a public key
*/
export const publicKey = (property = 'publicKey') => {
const layout = BufferLayout.blob(32, property);
const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout);
const publicKeyLayout = layout as BufferLayout.Layout<any> as BufferLayout.Layout<PublicKey>;
publicKeyLayout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset);
return new PublicKey(data);
};
publicKeyLayout.encode = (key: PublicKey, buffer: Buffer, offset: number) => {
return _encode(key.toBuffer(), buffer, offset);
};
return publicKeyLayout;
};
/**
* Layout for a 64bit unsigned value
*/
export const uint64 = (property = 'uint64') => {
const layout = BufferLayout.blob(8, property);
const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout);
const bnLayout = layout as BufferLayout.Layout<any> as BufferLayout.Layout<BN>;
bnLayout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset);
return new BN(
[...data]
.reverse()
.map(i => `00${i.toString(16)}`.slice(-2))
.join(''),
16,
);
};
bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => {
const a = num.toArray().reverse();
let b = Buffer.from(a);
if (b.length !== 8) {
const zeroPad = Buffer.alloc(8);
b.copy(zeroPad);
b = zeroPad;
}
return _encode(b, buffer, offset);
};
return bnLayout;
};
// TODO: wrap in BN (what about decimals?)
export const uint128 = (property = 'uint128') => {
const layout = BufferLayout.blob(16, property);
const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout);
const bnLayout = layout as BufferLayout.Layout<any> as BufferLayout.Layout<BN>;
bnLayout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset);
return new BN(
[...data]
.reverse()
.map(i => `00${i.toString(16)}`.slice(-2))
.join(''),
16,
);
};
bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => {
const a = num.toArray().reverse();
let b = Buffer.from(a);
if (b.length !== 16) {
const zeroPad = Buffer.alloc(16);
b.copy(zeroPad);
b = zeroPad;
}
return _encode(b, buffer, offset);
};
return bnLayout;
};
interface RustString {
length: number;
lengthPadding: number;
chars: Buffer;
}
/**
* Layout for a Rust String type
*/
export const rustString = (property = 'string') => {
const layout = BufferLayout.struct<RustString>(
[
BufferLayout.u32('length'),
BufferLayout.u32('lengthPadding'),
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
],
property,
);
const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout);
const stringLayout = layout as BufferLayout.Layout<any> as BufferLayout.Layout<string>;
stringLayout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset);
return data.chars.toString('utf8');
};
stringLayout.encode = (str: string, buffer: Buffer, offset: number) => {
// @TODO: does this need length/padding?
const data = {
chars: Buffer.from(str, 'utf8'),
} as RustString;
return _encode(data, buffer, offset);
};
return stringLayout;
};

View File

@ -1,6 +1,11 @@
import { KnownTokenMap, utils } from '@oyster/common';
import { KnownTokenMap, TokenAccount, utils } from '@oyster/common';
import { MintInfo } from '@solana/spl-token';
import BigNumber from 'bignumber.js';
import { PoolInfo } from '../models';
const ZERO = new BigNumber(0);
const WAD = new BigNumber('1e+18');
export function getPoolName(
map: KnownTokenMap,
pool: PoolInfo,
@ -9,3 +14,28 @@ export function getPoolName(
const sorted = pool.pubkeys.holdingMints.map(a => a.toBase58()).sort();
return sorted.map(item => utils.getTokenName(map, item, shorten)).join('/');
}
export function wadToLamports(amount?: BigNumber): BigNumber {
return amount?.div(WAD) || ZERO;
}
export function fromLamports(
account?: TokenAccount | number | BigNumber,
mint?: MintInfo,
rate: number = 1.0,
): number {
if (!account) {
return 0;
}
const amount = Math.floor(
typeof account === 'number'
? account
: BigNumber.isBigNumber(account)
? account.toNumber()
: account.info.amount.toNumber(),
);
const precision = Math.pow(10, mint?.decimals || 0);
return (amount / precision) * rate;
}

View File

@ -1,12 +1,12 @@
import { Card } from 'antd';
import React from 'react';
import { LABELS } from '../../constants';
import { useLendingReserves } from '../../hooks';
import { useReserves } from '../../hooks';
import { BorrowItem } from './item';
import './itemStyle.less';
export const BorrowView = () => {
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
return (
<div className="flexColumn">
<Card>

View File

@ -1,11 +1,11 @@
import { Card } from 'antd';
import React from 'react';
import { useLendingReserves } from '../../../hooks';
import { useReserves } from '../../../hooks';
import { ReserveItem } from './item';
import './itemStyle.less';
export const DepositView = () => {
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
return (
<div className="flexColumn">
<Card>

View File

@ -9,12 +9,12 @@ import {
SideReserveOverviewMode,
} from '../../components/SideReserveOverview';
import { GUTTER } from '../../constants';
import { useLendingReserve } from '../../hooks';
import { useReserve } from '../../hooks';
import './style.less';
export const DepositReserveView = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const lendingReserve = useReserve(id);
const reserve = lendingReserve?.info;
if (!reserve || !lendingReserve) {

View File

@ -10,7 +10,7 @@ import { Card, Col, Row, Statistic } from 'antd';
import React, { useEffect, useState } from 'react';
import { GUTTER, LABELS } from '../../constants';
import { usePyth } from '../../contexts/pyth';
import { useLendingReserves } from '../../hooks';
import { useReserves } from '../../hooks';
import { reserveMarketCap, Totals } from '../../models';
import { BarChartStatistic } from './../../components/BarChartStatistic';
import { LendingReserveItem } from './item';
@ -20,7 +20,7 @@ const { cache } = contexts.Accounts;
const { useConnectionConfig } = contexts.Connection;
export const HomeView = () => {
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
const { getPrice } = usePyth();
const { tokenMap } = useConnectionConfig();
const [totals, setTotals] = useState<Totals>({

View File

@ -1,12 +1,12 @@
import { Card } from 'antd';
import React from 'react';
import { LABELS } from '../../constants';
import { useLendingReserves } from '../../hooks/useLendingReserves';
import { useReserves } from '../../hooks/useReserves';
import { MarginTradeItem } from './item';
import './itemStyle.less';
export const MarginTrading = () => {
const { reserveAccounts } = useLendingReserves();
const { reserveAccounts } = useReserves();
return (
<div className="flexColumn">
<Card>

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useLendingReserve } from '../../../hooks';
import { useReserve } from '../../../hooks';
import Breakdown from './Breakdown';
import { Position } from './interfaces';
@ -10,7 +10,7 @@ import './style.less';
export const NewPosition = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const lendingReserve = useReserve(id);
const [newPosition, setNewPosition] = useState<Position>({
id: null,
leverage: 5,

View File

@ -4,12 +4,12 @@ import { useParams } from 'react-router-dom';
import { ReserveStatus } from '../../components/ReserveStatus';
import { UserLendingCard } from '../../components/UserLendingCard';
import { GUTTER } from '../../constants';
import { useLendingReserve } from '../../hooks';
import { useReserve } from '../../hooks';
import './style.less';
export const ReserveView = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const lendingReserve = useReserve(id);
const reserve = lendingReserve?.info;
if (!reserve || !lendingReserve) {

View File

@ -7,12 +7,12 @@ import {
} from '../../components/SideReserveOverview';
import { WithdrawInput } from '../../components/WithdrawInput';
import { useLendingReserve } from '../../hooks';
import { useReserve } from '../../hooks';
import './style.less';
export const WithdrawView = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const lendingReserve = useReserve(id);
const reserve = lendingReserve?.info;
if (!reserve || !lendingReserve) {