feat: add slider support

This commit is contained in:
bartosz-lipinski 2020-12-01 00:14:35 -06:00
parent 2682001514
commit d02ea83f1d
14 changed files with 147 additions and 58 deletions

View File

@ -11,20 +11,18 @@ import {
initReserveInstruction, initReserveInstruction,
LendingReserve, LendingReserve,
} from "./../models/lending"; } 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 { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
import { import {
createUninitializedAccount, createUninitializedAccount,
ensureSplAccount, ensureSplAccount,
findOrCreateAccountByMint, findOrCreateAccountByMint,
} from "./account"; } from "./account";
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
import { TokenAccount } from "../models"; import { TokenAccount } from "../models";
import { toLamports } from "../utils/utils";
export const deposit = async ( export const deposit = async (
from: TokenAccount, from: TokenAccount,
amount: number, amountLamports: number,
reserve: LendingReserve, reserve: LendingReserve,
reserveAddress: PublicKey, reserveAddress: PublicKey,
connection: Connection, connection: Connection,
@ -55,13 +53,6 @@ export const deposit = async (
LENDING_PROGRAM_ID LENDING_PROGRAM_ID
); );
const mint = (await cache.query(
connection,
reserve.liquidityMint,
MintParser
)) as ParsedAccount<MintInfo>;
const amountLamports = toLamports(amount, mint?.info);
const fromAccount = ensureSplAccount( const fromAccount = ensureSplAccount(
instructions, instructions,
cleanupInstructions, cleanupInstructions,

View File

@ -1,8 +1,13 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { useTokenName, useUserBalance } from "../../hooks"; import {
InputType,
useSliderInput,
useTokenName,
useUserBalance,
} from "../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon"; import { TokenIcon } from "../TokenIcon";
import { Button, Card, Spin } from "antd"; import { Button, Card, Slider, Spin } from "antd";
import { NumericInput } from "../Input/numeric"; import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection"; import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet"; import { useWallet } from "../../contexts/wallet";
@ -11,6 +16,7 @@ import { PublicKey } from "@solana/web3.js";
import "./style.less"; import "./style.less";
import { LoadingOutlined } from "@ant-design/icons"; import { LoadingOutlined } from "@ant-design/icons";
import { ActionConfirmation } from "./../ActionConfirmation"; import { ActionConfirmation } from "./../ActionConfirmation";
import { marks } from "../../constants";
const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />; const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
@ -21,7 +27,6 @@ export const DepositInput = (props: {
}) => { }) => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const [value, setValue] = useState("");
const [pendingTx, setPendingTx] = useState(false); const [pendingTx, setPendingTx] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false);
@ -29,9 +34,24 @@ export const DepositInput = (props: {
const address = props.address; const address = props.address;
const name = useTokenName(reserve?.liquidityMint); 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 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(() => { const onDeposit = useCallback(() => {
setPendingTx(true); setPendingTx(true);
@ -39,7 +59,9 @@ export const DepositInput = (props: {
try { try {
await deposit( await deposit(
fromAccounts[0], fromAccounts[0],
parseFloat(value), type === InputType.Slider
? (mark * balanceLamports) / 100
: Math.ceil(balanceLamports * (parseFloat(value) / balance)),
reserve, reserve,
address, address,
connection, connection,
@ -54,7 +76,19 @@ export const DepositInput = (props: {
setPendingTx(false); setPendingTx(false);
} }
})(); })();
}, [connection, wallet, value, reserve, fromAccounts, address]); }, [
connection,
setValue,
balanceLamports,
balance,
wallet,
value,
mark,
type,
reserve,
fromAccounts,
address,
]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: "flex", display: "flex",
@ -98,6 +132,8 @@ export const DepositInput = (props: {
<div>{name}</div> <div>{name}</div>
</div> </div>
<Slider marks={marks} value={mark} onChange={setMark} />
<Button <Button
type="primary" type="primary"
onClick={onDeposit} onClick={onDeposit}

View File

@ -1,19 +1,21 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { import {
InputType,
useCollateralBalance, useCollateralBalance,
useSliderInput,
useTokenName, useTokenName,
useUserBalance, useUserBalance,
} from "../../hooks"; } from "../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon"; import { TokenIcon } from "../TokenIcon";
import { Button, Card, Spin } from "antd"; import { Button, Card, Slider, Spin } from "antd";
import { NumericInput } from "../Input/numeric"; import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection"; import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet"; import { useWallet } from "../../contexts/wallet";
import { withdraw } from "../../actions"; import { withdraw } from "../../actions";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import "./style.less"; import "./style.less";
import { LABELS } from "../../constants"; import { LABELS, marks } from "../../constants";
import { LoadingOutlined } from "@ant-design/icons"; import { LoadingOutlined } from "@ant-design/icons";
import { ActionConfirmation } from "./../ActionConfirmation"; import { ActionConfirmation } from "./../ActionConfirmation";
@ -26,7 +28,6 @@ export const WithdrawInput = (props: {
}) => { }) => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const [value, setValue] = useState("");
const [pendingTx, setPendingTx] = useState(false); const [pendingTx, setPendingTx] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false);
@ -42,6 +43,19 @@ export const WithdrawInput = (props: {
reserve 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(() => { const onWithdraw = useCallback(() => {
setPendingTx(true); setPendingTx(true);
@ -49,7 +63,9 @@ export const WithdrawInput = (props: {
try { try {
await withdraw( await withdraw(
fromAccounts[0], fromAccounts[0],
Math.ceil( type === InputType.Slider
? (mark * collateralBalanceLamports) / 100
: Math.ceil(
collateralBalanceLamports * collateralBalanceLamports *
(parseFloat(value) / collateralBalanceInLiquidity) (parseFloat(value) / collateralBalanceInLiquidity)
), ),
@ -68,14 +84,17 @@ export const WithdrawInput = (props: {
} }
})(); })();
}, [ }, [
connection,
wallet,
collateralBalanceLamports,
collateralBalanceInLiquidity,
value,
reserve,
fromAccounts,
address, address,
collateralBalanceInLiquidity,
collateralBalanceLamports,
connection,
fromAccounts,
mark,
reserve,
setValue,
type,
value,
wallet,
]); ]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
@ -103,9 +122,7 @@ export const WithdrawInput = (props: {
<TokenIcon mintAddress={reserve?.liquidityMint} /> <TokenIcon mintAddress={reserve?.liquidityMint} />
<NumericInput <NumericInput
value={value} value={value}
onChange={(val: any) => { onChange={setValue}
setValue(val);
}}
autoFocus={true} autoFocus={true}
style={{ style={{
fontSize: 20, fontSize: 20,
@ -118,6 +135,8 @@ export const WithdrawInput = (props: {
<div>{name}</div> <div>{name}</div>
</div> </div>
<Slider marks={marks} value={mark} onChange={setMark} />
<Button <Button
type="primary" type="primary"
onClick={onWithdraw} onClick={onWithdraw}

View File

@ -1,3 +1,4 @@
export * from "./ids"; export * from "./ids";
export * from "./labels"; export * from "./labels";
export * from "./math"; export * from "./math";
export * from "./marks";

7
src/constants/marks.ts Normal file
View File

@ -0,0 +1,7 @@
export const marks = {
0: "0%",
25: "25%",
50: "50%",
75: "75%",
100: "100%",
};

View File

@ -83,7 +83,9 @@ export const useLending = () => {
setLendingAccounts([]); setLendingAccounts([]);
const queryLendingAccounts = async () => { const queryLendingAccounts = async () => {
const programAccounts = (await connection.getProgramAccounts(LENDING_PROGRAM_ID)); const programAccounts = await connection.getProgramAccounts(
LENDING_PROGRAM_ID
);
const accounts = programAccounts const accounts = programAccounts
.map(processAccount) .map(processAccount)
@ -116,7 +118,6 @@ export const useLending = () => {
}), }),
].flat() as string[]; ].flat() as string[];
// This will pre-cache all accounts used by pools // This will pre-cache all accounts used by pools
// All those accounts are updated whenever there is a change // All those accounts are updated whenever there is a change
await getMultipleAccounts(connection, toQuery, "single").then( await getMultipleAccounts(connection, toQuery, "single").then(
@ -130,9 +131,7 @@ export const useLending = () => {
); );
// HACK: fix, force account refresh // HACK: fix, force account refresh
programAccounts programAccounts.map(processAccount).filter((item) => item !== undefined);
.map(processAccount)
.filter((item) => item !== undefined);
return accounts; return accounts;
}; };

View File

@ -89,7 +89,7 @@ export function MarketProvider({ children = null as any }) {
allMarkets.filter((a) => cache.get(a) === undefined), allMarkets.filter((a) => cache.get(a) === undefined),
"single" "single"
).then(({ keys, array }) => { ).then(({ keys, array }) => {
allMarkets.forEach(() => { }); allMarkets.forEach(() => {});
return array.map((item, index) => { return array.map((item, index) => {
const marketAddress = keys[index]; const marketAddress = keys[index];
@ -158,7 +158,7 @@ export function MarketProvider({ children = null as any }) {
const info = marketByMint.get(mintAddress); const info = marketByMint.get(mintAddress);
const market = cache.get(info?.marketInfo.address.toBase58() || ""); const market = cache.get(info?.marketInfo.address.toBase58() || "");
if (!market) { if (!market) {
return () => { }; return () => {};
} }
// TODO: get recent volume // TODO: get recent volume

View File

@ -9,3 +9,4 @@ export * from "./useUserObligations";
export * from "./useUserObligationByReserve"; export * from "./useUserObligationByReserve";
export * from "./useBorrowedAmount"; export * from "./useBorrowedAmount";
export * from "./useUserDeposits"; export * from "./useUserDeposits";
export * from "./useSliderInput";

View File

@ -21,6 +21,7 @@ export function useCollateralBalance(
return { return {
balance: fromLamports(collateralRatioLamports, mint), balance: fromLamports(collateralRatioLamports, mint),
balanceLamports: collateralRatioLamports, balanceLamports: collateralRatioLamports,
mint: reserve?.collateralMint,
accounts, accounts,
}; };
} }

View File

@ -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,
};
};

View File

@ -176,10 +176,10 @@ export const calculateUtilizationRatio = (reserve: LendingReserve) => {
); );
}; };
export const reserveMarketCap = (reserve?: LendingReserve ) => { export const reserveMarketCap = (reserve?: LendingReserve) => {
const available = (reserve?.availableLiquidity.toNumber() || 0) ; const available = reserve?.availableLiquidity.toNumber() || 0;
const borrowed = wadToLamports(reserve?.borrowedLiquidityWad).toNumber() ; const borrowed = wadToLamports(reserve?.borrowedLiquidityWad).toNumber();
const total = available + borrowed; const total = available + borrowed;
return total; return total;
} };

View File

@ -1,8 +1,5 @@
import React from "react"; import React from "react";
import { import { useCollateralBalance, useTokenName } from "../../hooks";
useCollateralBalance,
useTokenName,
} from "../../hooks";
import { calculateBorrowAPY, LendingReserve } from "../../models/lending"; import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber, formatPct } from "../../utils/utils"; import { formatNumber, formatPct } from "../../utils/utils";

View File

@ -14,9 +14,7 @@ export const DashboardView = () => {
return ( return (
<div className="dashboard-container"> <div className="dashboard-container">
{!connected && ( {!connected && (
<div className="dashboard-info"> <div className="dashboard-info">{LABELS.DASHBOARD_INFO}</div>
{LABELS.DASHBOARD_INFO}
</div>
)} )}
{connected && {connected &&
userDeposits.length === 0 && userDeposits.length === 0 &&

View File

@ -20,13 +20,17 @@ export const HomeView = () => {
const marketCapLamports = reserveMarketCap(item.info); const marketCapLamports = reserveMarketCap(item.info);
const localCache = cache; 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) { if (!mint) {
return result; 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; return result + marketCap;
}, 0); }, 0);
@ -43,8 +47,6 @@ export const HomeView = () => {
return () => { return () => {
dispose(); dispose();
}; };
}, [marketEmitter, midPriceInUSD, setTotalMarketSize, reserveAccounts]); }, [marketEmitter, midPriceInUSD, setTotalMarketSize, reserveAccounts]);
return ( return (