mirror of https://github.com/certusone/oyster.git
feat: add slider support
This commit is contained in:
parent
2682001514
commit
d02ea83f1d
|
@ -11,20 +11,18 @@ import {
|
|||
initReserveInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending";
|
||||
import { AccountLayout, MintInfo, Token } from "@solana/spl-token";
|
||||
import { AccountLayout, Token } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
|
||||
import {
|
||||
createUninitializedAccount,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from "./account";
|
||||
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
||||
import { TokenAccount } from "../models";
|
||||
import { toLamports } from "../utils/utils";
|
||||
|
||||
export const deposit = async (
|
||||
from: TokenAccount,
|
||||
amount: number,
|
||||
amountLamports: number,
|
||||
reserve: LendingReserve,
|
||||
reserveAddress: PublicKey,
|
||||
connection: Connection,
|
||||
|
@ -55,13 +53,6 @@ export const deposit = async (
|
|||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const mint = (await cache.query(
|
||||
connection,
|
||||
reserve.liquidityMint,
|
||||
MintParser
|
||||
)) as ParsedAccount<MintInfo>;
|
||||
const amountLamports = toLamports(amount, mint?.info);
|
||||
|
||||
const fromAccount = ensureSplAccount(
|
||||
instructions,
|
||||
cleanupInstructions,
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import { useTokenName, useUserBalance } from "../../hooks";
|
||||
import {
|
||||
InputType,
|
||||
useSliderInput,
|
||||
useTokenName,
|
||||
useUserBalance,
|
||||
} from "../../hooks";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import { Button, Card, Spin } from "antd";
|
||||
import { Button, Card, Slider, Spin } from "antd";
|
||||
import { NumericInput } from "../Input/numeric";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
|
@ -11,6 +16,7 @@ import { PublicKey } from "@solana/web3.js";
|
|||
import "./style.less";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
import { marks } from "../../constants";
|
||||
|
||||
const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
|
||||
|
||||
|
@ -21,7 +27,6 @@ export const DepositInput = (props: {
|
|||
}) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [value, setValue] = useState("");
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
|
@ -29,9 +34,24 @@ export const DepositInput = (props: {
|
|||
const address = props.address;
|
||||
|
||||
const name = useTokenName(reserve?.liquidityMint);
|
||||
const { accounts: fromAccounts } = useUserBalance(reserve?.liquidityMint);
|
||||
const { accounts: fromAccounts, balance, balanceLamports } = useUserBalance(
|
||||
reserve?.liquidityMint
|
||||
);
|
||||
// const collateralBalance = useUserBalance(reserve?.collateralMint);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
if (typeof val === "string") {
|
||||
return (parseFloat(val) / balance) * 100;
|
||||
} else {
|
||||
return ((val * balance) / 100).toFixed(2);
|
||||
}
|
||||
},
|
||||
[balance]
|
||||
);
|
||||
|
||||
const { value, setValue, mark, setMark, type } = useSliderInput(convert);
|
||||
|
||||
const onDeposit = useCallback(() => {
|
||||
setPendingTx(true);
|
||||
|
||||
|
@ -39,7 +59,9 @@ export const DepositInput = (props: {
|
|||
try {
|
||||
await deposit(
|
||||
fromAccounts[0],
|
||||
parseFloat(value),
|
||||
type === InputType.Slider
|
||||
? (mark * balanceLamports) / 100
|
||||
: Math.ceil(balanceLamports * (parseFloat(value) / balance)),
|
||||
reserve,
|
||||
address,
|
||||
connection,
|
||||
|
@ -54,7 +76,19 @@ export const DepositInput = (props: {
|
|||
setPendingTx(false);
|
||||
}
|
||||
})();
|
||||
}, [connection, wallet, value, reserve, fromAccounts, address]);
|
||||
}, [
|
||||
connection,
|
||||
setValue,
|
||||
balanceLamports,
|
||||
balance,
|
||||
wallet,
|
||||
value,
|
||||
mark,
|
||||
type,
|
||||
reserve,
|
||||
fromAccounts,
|
||||
address,
|
||||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
|
@ -98,6 +132,8 @@ export const DepositInput = (props: {
|
|||
<div>{name}</div>
|
||||
</div>
|
||||
|
||||
<Slider marks={marks} value={mark} onChange={setMark} />
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onDeposit}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
InputType,
|
||||
useCollateralBalance,
|
||||
useSliderInput,
|
||||
useTokenName,
|
||||
useUserBalance,
|
||||
} from "../../hooks";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import { Button, Card, Spin } from "antd";
|
||||
import { Button, Card, Slider, Spin } from "antd";
|
||||
import { NumericInput } from "../Input/numeric";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { withdraw } from "../../actions";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import "./style.less";
|
||||
import { LABELS } from "../../constants";
|
||||
import { LABELS, marks } from "../../constants";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
|
||||
|
@ -26,7 +28,6 @@ export const WithdrawInput = (props: {
|
|||
}) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [value, setValue] = useState("");
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
|
@ -42,6 +43,19 @@ export const WithdrawInput = (props: {
|
|||
reserve
|
||||
);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
if (typeof val === "string") {
|
||||
return (parseFloat(val) / collateralBalanceInLiquidity) * 100;
|
||||
} else {
|
||||
return ((val * collateralBalanceInLiquidity) / 100).toFixed(2);
|
||||
}
|
||||
},
|
||||
[collateralBalanceInLiquidity]
|
||||
);
|
||||
|
||||
const { value, setValue, mark, setMark, type } = useSliderInput(convert);
|
||||
|
||||
const onWithdraw = useCallback(() => {
|
||||
setPendingTx(true);
|
||||
|
||||
|
@ -49,10 +63,12 @@ export const WithdrawInput = (props: {
|
|||
try {
|
||||
await withdraw(
|
||||
fromAccounts[0],
|
||||
Math.ceil(
|
||||
collateralBalanceLamports *
|
||||
(parseFloat(value) / collateralBalanceInLiquidity)
|
||||
),
|
||||
type === InputType.Slider
|
||||
? (mark * collateralBalanceLamports) / 100
|
||||
: Math.ceil(
|
||||
collateralBalanceLamports *
|
||||
(parseFloat(value) / collateralBalanceInLiquidity)
|
||||
),
|
||||
reserve,
|
||||
address,
|
||||
connection,
|
||||
|
@ -68,14 +84,17 @@ export const WithdrawInput = (props: {
|
|||
}
|
||||
})();
|
||||
}, [
|
||||
connection,
|
||||
wallet,
|
||||
collateralBalanceLamports,
|
||||
collateralBalanceInLiquidity,
|
||||
value,
|
||||
reserve,
|
||||
fromAccounts,
|
||||
address,
|
||||
collateralBalanceInLiquidity,
|
||||
collateralBalanceLamports,
|
||||
connection,
|
||||
fromAccounts,
|
||||
mark,
|
||||
reserve,
|
||||
setValue,
|
||||
type,
|
||||
value,
|
||||
wallet,
|
||||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
|
@ -103,9 +122,7 @@ export const WithdrawInput = (props: {
|
|||
<TokenIcon mintAddress={reserve?.liquidityMint} />
|
||||
<NumericInput
|
||||
value={value}
|
||||
onChange={(val: any) => {
|
||||
setValue(val);
|
||||
}}
|
||||
onChange={setValue}
|
||||
autoFocus={true}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
|
@ -118,6 +135,8 @@ export const WithdrawInput = (props: {
|
|||
<div>{name}</div>
|
||||
</div>
|
||||
|
||||
<Slider marks={marks} value={mark} onChange={setMark} />
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onWithdraw}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./ids";
|
||||
export * from "./labels";
|
||||
export * from "./math";
|
||||
export * from "./marks";
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const marks = {
|
||||
0: "0%",
|
||||
25: "25%",
|
||||
50: "50%",
|
||||
75: "75%",
|
||||
100: "100%",
|
||||
};
|
|
@ -83,7 +83,9 @@ export const useLending = () => {
|
|||
setLendingAccounts([]);
|
||||
|
||||
const queryLendingAccounts = async () => {
|
||||
const programAccounts = (await connection.getProgramAccounts(LENDING_PROGRAM_ID));
|
||||
const programAccounts = await connection.getProgramAccounts(
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const accounts = programAccounts
|
||||
.map(processAccount)
|
||||
|
@ -116,7 +118,6 @@ export const useLending = () => {
|
|||
}),
|
||||
].flat() as string[];
|
||||
|
||||
|
||||
// This will pre-cache all accounts used by pools
|
||||
// All those accounts are updated whenever there is a change
|
||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||
|
@ -130,10 +131,8 @@ export const useLending = () => {
|
|||
);
|
||||
|
||||
// HACK: fix, force account refresh
|
||||
programAccounts
|
||||
.map(processAccount)
|
||||
.filter((item) => item !== undefined);
|
||||
|
||||
programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||
|
||||
return accounts;
|
||||
};
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
allMarkets.filter((a) => cache.get(a) === undefined),
|
||||
"single"
|
||||
).then(({ keys, array }) => {
|
||||
allMarkets.forEach(() => { });
|
||||
allMarkets.forEach(() => {});
|
||||
|
||||
return array.map((item, index) => {
|
||||
const marketAddress = keys[index];
|
||||
|
@ -158,7 +158,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
const info = marketByMint.get(mintAddress);
|
||||
const market = cache.get(info?.marketInfo.address.toBase58() || "");
|
||||
if (!market) {
|
||||
return () => { };
|
||||
return () => {};
|
||||
}
|
||||
|
||||
// TODO: get recent volume
|
||||
|
|
|
@ -9,3 +9,4 @@ export * from "./useUserObligations";
|
|||
export * from "./useUserObligationByReserve";
|
||||
export * from "./useBorrowedAmount";
|
||||
export * from "./useUserDeposits";
|
||||
export * from "./useSliderInput";
|
||||
|
|
|
@ -21,6 +21,7 @@ export function useCollateralBalance(
|
|||
return {
|
||||
balance: fromLamports(collateralRatioLamports, mint),
|
||||
balanceLamports: collateralRatioLamports,
|
||||
mint: reserve?.collateralMint,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { useCallback, useState } from "react";
|
||||
|
||||
export enum InputType {
|
||||
Input = 0,
|
||||
Slider = 1,
|
||||
}
|
||||
|
||||
export const useSliderInput = (
|
||||
convert: (val: string | number) => string | number
|
||||
) => {
|
||||
const [value, setValue] = useState("");
|
||||
const [mark, setMark] = useState(0);
|
||||
const [type, setType] = useState(InputType.Slider);
|
||||
|
||||
return {
|
||||
value,
|
||||
setValue: useCallback(
|
||||
(val: string) => {
|
||||
console.log(val);
|
||||
setType(InputType.Input);
|
||||
setValue(val);
|
||||
setMark(convert(val) as number);
|
||||
},
|
||||
[setType, setValue, setMark, convert]
|
||||
),
|
||||
mark,
|
||||
setMark: useCallback(
|
||||
(val: number) => {
|
||||
setType(InputType.Input);
|
||||
setMark(val);
|
||||
setValue(convert(val) as string);
|
||||
},
|
||||
[setType, setValue, setMark, convert]
|
||||
),
|
||||
type,
|
||||
};
|
||||
};
|
|
@ -176,10 +176,10 @@ export const calculateUtilizationRatio = (reserve: LendingReserve) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const reserveMarketCap = (reserve?: LendingReserve ) => {
|
||||
const available = (reserve?.availableLiquidity.toNumber() || 0) ;
|
||||
const borrowed = wadToLamports(reserve?.borrowedLiquidityWad).toNumber() ;
|
||||
export const reserveMarketCap = (reserve?: LendingReserve) => {
|
||||
const available = reserve?.availableLiquidity.toNumber() || 0;
|
||||
const borrowed = wadToLamports(reserve?.borrowedLiquidityWad).toNumber();
|
||||
const total = available + borrowed;
|
||||
|
||||
return total;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import React from "react";
|
||||
import {
|
||||
useCollateralBalance,
|
||||
useTokenName,
|
||||
} from "../../hooks";
|
||||
import { useCollateralBalance, useTokenName } from "../../hooks";
|
||||
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../../components/TokenIcon";
|
||||
import { formatNumber, formatPct } from "../../utils/utils";
|
||||
|
|
|
@ -14,14 +14,12 @@ export const DashboardView = () => {
|
|||
return (
|
||||
<div className="dashboard-container">
|
||||
{!connected && (
|
||||
<div className="dashboard-info">
|
||||
{LABELS.DASHBOARD_INFO}
|
||||
</div>
|
||||
<div className="dashboard-info">{LABELS.DASHBOARD_INFO}</div>
|
||||
)}
|
||||
{connected &&
|
||||
userDeposits.length === 0 &&
|
||||
userObligations.length === 0 && (
|
||||
<div className="dashboard-info">{LABELS.NO_LOANS_NO_DEPOSITS}</div>
|
||||
<div className="dashboard-info">{LABELS.NO_LOANS_NO_DEPOSITS}</div>
|
||||
)}
|
||||
{userDeposits.length > 0 && (
|
||||
<div className="dashboard-left">
|
||||
|
|
|
@ -20,13 +20,17 @@ export const HomeView = () => {
|
|||
const marketCapLamports = reserveMarketCap(item.info);
|
||||
|
||||
const localCache = cache;
|
||||
const mint = localCache.get(item.info.liquidityMint.toBase58()) as ParsedAccount<MintInfo>;
|
||||
const mint = localCache.get(
|
||||
item.info.liquidityMint.toBase58()
|
||||
) as ParsedAccount<MintInfo>;
|
||||
|
||||
if (!mint) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const marketCap = fromLamports(marketCapLamports, mint?.info) * midPriceInUSD(mint?.pubkey.toBase58());
|
||||
const marketCap =
|
||||
fromLamports(marketCapLamports, mint?.info) *
|
||||
midPriceInUSD(mint?.pubkey.toBase58());
|
||||
|
||||
return result + marketCap;
|
||||
}, 0);
|
||||
|
@ -43,8 +47,6 @@ export const HomeView = () => {
|
|||
return () => {
|
||||
dispose();
|
||||
};
|
||||
|
||||
|
||||
}, [marketEmitter, midPriceInUSD, setTotalMarketSize, reserveAccounts]);
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue