chore: prettier

This commit is contained in:
bartosz-lipinski 2020-11-20 23:54:50 -06:00
parent 547ab00d7b
commit 5ea0434a68
59 changed files with 1274 additions and 945 deletions

View File

@ -1,3 +1,3 @@
{ {
"editor.tabSize": 2 "editor.tabSize": 2
} }

View File

@ -2,11 +2,8 @@ import React from "react";
import "./App.less"; import "./App.less";
import { Routes } from "./routes"; import { Routes } from "./routes";
function App() { function App() {
return ( return <Routes />;
<Routes />
);
} }
export default App; export default App;

View File

@ -1,8 +1,13 @@
import { AccountLayout, Token } from "@solana/spl-token"; import { AccountLayout, Token } from "@solana/spl-token";
import { Account, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js"; import {
Account,
PublicKey,
SystemProgram,
TransactionInstruction,
} from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from "../constants/ids"; import { TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from "../constants/ids";
import { TokenAccount } from "../models"; import { TokenAccount } from "../models";
import { cache, TokenAccountParser } from './../contexts/accounts'; import { cache, TokenAccountParser } from "./../contexts/accounts";
export function ensureSplAccount( export function ensureSplAccount(
instructions: TransactionInstruction[], instructions: TransactionInstruction[],
@ -17,10 +22,11 @@ export function ensureSplAccount(
} }
const account = createUninitializedAccount( const account = createUninitializedAccount(
instructions, instructions,
payer, payer,
amount, amount,
signers); signers
);
instructions.push( instructions.push(
Token.createInitAccountInstruction( Token.createInitAccountInstruction(
@ -47,10 +53,11 @@ export function ensureSplAccount(
export const DEFAULT_TEMP_MEM_SPACE = 65528; export const DEFAULT_TEMP_MEM_SPACE = 65528;
export function createTempMemoryAccount( export function createTempMemoryAccount(
instructions: TransactionInstruction[], instructions: TransactionInstruction[],
payer: PublicKey, payer: PublicKey,
signers: Account[], signers: Account[],
space = DEFAULT_TEMP_MEM_SPACE) { space = DEFAULT_TEMP_MEM_SPACE
) {
const account = new Account(); const account = new Account();
instructions.push( instructions.push(
SystemProgram.createAccount({ SystemProgram.createAccount({
@ -68,12 +75,12 @@ export function createTempMemoryAccount(
return account.publicKey; return account.publicKey;
} }
export function createUninitializedAccount( export function createUninitializedAccount(
instructions: TransactionInstruction[], instructions: TransactionInstruction[],
payer: PublicKey, payer: PublicKey,
amount: number, amount: number,
signers: Account[]) { signers: Account[]
) {
const account = new Account(); const account = new Account();
instructions.push( instructions.push(
SystemProgram.createAccount({ SystemProgram.createAccount({
@ -96,21 +103,17 @@ export function createTokenAccount(
accountRentExempt: number, accountRentExempt: number,
mint: PublicKey, mint: PublicKey,
owner: PublicKey, owner: PublicKey,
signers: Account[], signers: Account[]
) { ) {
const account = createUninitializedAccount( const account = createUninitializedAccount(
instructions, instructions,
payer, payer,
accountRentExempt, accountRentExempt,
signers); signers
);
instructions.push( instructions.push(
Token.createInitAccountInstruction( Token.createInitAccountInstruction(TOKEN_PROGRAM_ID, mint, account, owner)
TOKEN_PROGRAM_ID,
mint,
account,
owner
)
); );
return account; return account;
@ -128,15 +131,16 @@ export function findOrCreateAccountByMint(
excluded?: Set<string> excluded?: Set<string>
): PublicKey { ): PublicKey {
const accountToFind = mint.toBase58(); const accountToFind = mint.toBase58();
const account = cache.byParser(TokenAccountParser) const account = cache
.map(id => cache.get(id)) .byParser(TokenAccountParser)
.map((id) => cache.get(id))
.find( .find(
(acc) => (acc) =>
acc !== undefined && acc !== undefined &&
acc.info.mint.toBase58() === accountToFind && acc.info.mint.toBase58() === accountToFind &&
acc.info.owner.toBase58() === owner.toBase58() && acc.info.owner.toBase58() === owner.toBase58() &&
(excluded === undefined || !excluded.has(acc.pubkey.toBase58())) (excluded === undefined || !excluded.has(acc.pubkey.toBase58()))
); );
const isWrappedSol = accountToFind === WRAPPED_SOL_MINT.toBase58(); const isWrappedSol = accountToFind === WRAPPED_SOL_MINT.toBase58();
let toAccount: PublicKey; let toAccount: PublicKey;
@ -150,7 +154,7 @@ export function findOrCreateAccountByMint(
accountRentExempt, accountRentExempt,
mint, mint,
owner, owner,
signers, signers
); );
if (isWrappedSol) { if (isWrappedSol) {

View File

@ -9,11 +9,20 @@ import { notify } from "../utils/notifications";
import { LendingReserve } from "./../models/lending/reserve"; import { LendingReserve } from "./../models/lending/reserve";
import { AccountLayout, MintInfo, MintLayout, Token } from "@solana/spl-token"; import { AccountLayout, MintInfo, MintLayout, 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 { createTempMemoryAccount, createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account"; import {
createTempMemoryAccount,
createUninitializedAccount,
ensureSplAccount,
findOrCreateAccountByMint,
} from "./account";
import { cache, MintParser, ParsedAccount } from "../contexts/accounts"; import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
import { TokenAccount, LendingObligationLayout, borrowInstruction, LendingMarket } from "../models"; import {
TokenAccount,
LendingObligationLayout,
borrowInstruction,
LendingMarket,
} from "../models";
import { toLamports } from "../utils/utils"; import { toLamports } from "../utils/utils";
import { DexMarketParser } from "../models/dex";
export const borrow = async ( export const borrow = async (
from: TokenAccount, from: TokenAccount,
@ -26,8 +35,8 @@ export const borrow = async (
depositReserveAddress: PublicKey, depositReserveAddress: PublicKey,
connection: Connection, connection: Connection,
wallet: any) => { wallet: any
) => {
notify({ notify({
message: "Borrowing funds...", message: "Borrowing funds...",
description: "Please review transactions to approve.", description: "Please review transactions to approve.",
@ -48,23 +57,21 @@ export const borrow = async (
await connection.getMinimumBalanceForRentExemption( await connection.getMinimumBalanceForRentExemption(
LendingObligationLayout.span LendingObligationLayout.span
), ),
signers, signers
); );
const obligationMint = createUninitializedAccount( const obligationMint = createUninitializedAccount(
instructions, instructions,
wallet.publicKey, wallet.publicKey,
await connection.getMinimumBalanceForRentExemption( await connection.getMinimumBalanceForRentExemption(MintLayout.span),
MintLayout.span signers
),
signers,
); );
const obligationTokenOutput = createUninitializedAccount( const obligationTokenOutput = createUninitializedAccount(
instructions, instructions,
wallet.publicKey, wallet.publicKey,
accountRentExempt, accountRentExempt,
signers, signers
); );
let toAccount = await findOrCreateAccountByMint( let toAccount = await findOrCreateAccountByMint(
@ -78,7 +85,9 @@ export const borrow = async (
); );
// create all accounts in one transaction // create all accounts in one transaction
let tx = await sendTransaction(connection, wallet, instructions, [...signers]); let tx = await sendTransaction(connection, wallet, instructions, [
...signers,
]);
notify({ notify({
message: "Obligation accounts created", message: "Obligation accounts created",
@ -101,7 +110,11 @@ export const borrow = async (
LENDING_PROGRAM_ID LENDING_PROGRAM_ID
); );
const mint = (await cache.query(connection, depositReserve.collateralMint, MintParser)) as ParsedAccount<MintInfo>; const mint = (await cache.query(
connection,
depositReserve.collateralMint,
MintParser
)) as ParsedAccount<MintInfo>;
const amountLamports = toLamports(amount, mint?.info); const amountLamports = toLamports(amount, mint?.info);
const fromAccount = ensureSplAccount( const fromAccount = ensureSplAccount(
@ -121,30 +134,36 @@ export const borrow = async (
authority, authority,
wallet.publicKey, wallet.publicKey,
[], [],
amountLamports, amountLamports
) )
); );
const market = cache.get(depositReserve.lendingMarket) as ParsedAccount<LendingMarket>; const market = cache.get(depositReserve.lendingMarket) as ParsedAccount<
LendingMarket
const dexMarketAddress = borrowReserve.dexMarketOption ? borrowReserve.dexMarket : depositReserve.dexMarket; >;
const dexMarketAddress = borrowReserve.dexMarketOption
? borrowReserve.dexMarket
: depositReserve.dexMarket;
const dexMarket = cache.get(dexMarketAddress); const dexMarket = cache.get(dexMarketAddress);
if(!dexMarket) { if (!dexMarket) {
throw new Error(`Dex market doesn't exsists.`) throw new Error(`Dex market doesn't exsists.`);
} }
const dexOrderBookSide = market.info.quoteMint.equals(depositReserve.liquidityMint) ? const dexOrderBookSide = market.info.quoteMint.equals(
dexMarket?.info.bids : depositReserve.liquidityMint
dexMarket?.info.asks )
? dexMarket?.info.bids
: dexMarket?.info.asks;
const memory = createTempMemoryAccount( const memory = createTempMemoryAccount(
instructions, instructions,
wallet.publicKey, wallet.publicKey,
signers, signers
); );
// deposit // deposit
instructions.push( instructions.push(
borrowInstruction( borrowInstruction(
amountLamports, amountLamports,
@ -165,7 +184,7 @@ export const borrow = async (
dexMarketAddress, dexMarketAddress,
dexOrderBookSide, dexOrderBookSide,
memory, memory
) )
); );
try { try {
@ -185,4 +204,4 @@ export const borrow = async (
} catch { } catch {
// TODO: // TODO:
} }
} };

View File

@ -6,10 +6,18 @@ import {
} from "@solana/web3.js"; } from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection"; import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications"; import { notify } from "../utils/notifications";
import { depositInstruction, initReserveInstruction, LendingReserve } from "./../models/lending/reserve"; import {
depositInstruction,
initReserveInstruction,
LendingReserve,
} from "./../models/lending/reserve";
import { AccountLayout, MintInfo, Token } from "@solana/spl-token"; import { AccountLayout, MintInfo, 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 { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account"; import {
createUninitializedAccount,
ensureSplAccount,
findOrCreateAccountByMint,
} from "./account";
import { cache, MintParser, ParsedAccount } from "../contexts/accounts"; import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
import { TokenAccount } from "../models"; import { TokenAccount } from "../models";
import { toLamports } from "../utils/utils"; import { toLamports } from "../utils/utils";
@ -20,8 +28,8 @@ export const deposit = async (
reserve: LendingReserve, reserve: LendingReserve,
reserveAddress: PublicKey, reserveAddress: PublicKey,
connection: Connection, connection: Connection,
wallet: any) => { wallet: any
) => {
// TODO: customize ? // TODO: customize ?
const MAX_UTILIZATION_RATE = 80; const MAX_UTILIZATION_RATE = 80;
@ -47,7 +55,11 @@ export const deposit = async (
LENDING_PROGRAM_ID LENDING_PROGRAM_ID
); );
const mint = (await cache.query(connection, reserve.liquidityMint, MintParser)) as ParsedAccount<MintInfo>; const mint = (await cache.query(
connection,
reserve.liquidityMint,
MintParser
)) as ParsedAccount<MintInfo>;
const amountLamports = toLamports(amount, mint?.info); const amountLamports = toLamports(amount, mint?.info);
const fromAccount = ensureSplAccount( const fromAccount = ensureSplAccount(
@ -67,7 +79,7 @@ export const deposit = async (
authority, authority,
wallet.publicKey, wallet.publicKey,
[], [],
amountLamports, amountLamports
) )
); );
@ -88,12 +100,12 @@ export const deposit = async (
instructions, instructions,
wallet.publicKey, wallet.publicKey,
accountRentExempt, accountRentExempt,
signers, signers
); );
} }
if (isInitalized) { if (isInitalized) {
// deposit // deposit
instructions.push( instructions.push(
depositInstruction( depositInstruction(
amountLamports, amountLamports,
@ -102,25 +114,27 @@ export const deposit = async (
authority, authority,
reserveAddress, reserveAddress,
reserve.liquiditySupply, reserve.liquiditySupply,
reserve.collateralMint, reserve.collateralMint
) )
); );
} else { } else {
// TODO: finish reserve init // TODO: finish reserve init
instructions.push(initReserveInstruction( instructions.push(
amountLamports, initReserveInstruction(
MAX_UTILIZATION_RATE, amountLamports,
fromAccount, MAX_UTILIZATION_RATE,
toAccount, fromAccount,
reserveAddress, toAccount,
reserve.liquidityMint, reserveAddress,
reserve.liquiditySupply, reserve.liquidityMint,
reserve.collateralMint, reserve.liquiditySupply,
reserve.collateralSupply, reserve.collateralMint,
reserve.lendingMarket, reserve.collateralSupply,
authority, reserve.lendingMarket,
reserve.dexMarket, authority,
)); reserve.dexMarket
)
);
} }
try { try {
@ -140,4 +154,4 @@ export const deposit = async (
} catch { } catch {
// TODO: // TODO:
} }
} };

View File

@ -1,4 +1,4 @@
export { borrow } from './borrow'; export { borrow } from "./borrow";
export { deposit } from './deposit'; export { deposit } from "./deposit";
export { withdraw } from './withdraw'; export { withdraw } from "./withdraw";
export * from './account'; export * from "./account";

View File

@ -6,7 +6,10 @@ import {
} from "@solana/web3.js"; } from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection"; import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications"; import { notify } from "../utils/notifications";
import { LendingReserve, withdrawInstruction } from "./../models/lending/reserve"; import {
LendingReserve,
withdrawInstruction,
} from "./../models/lending/reserve";
import { AccountLayout, 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 { findOrCreateAccountByMint } from "./account"; import { findOrCreateAccountByMint } from "./account";
@ -18,8 +21,8 @@ export const withdraw = async (
reserve: LendingReserve, reserve: LendingReserve,
reserveAddress: PublicKey, reserveAddress: PublicKey,
connection: Connection, connection: Connection,
wallet: any) => { wallet: any
) => {
notify({ notify({
message: "Withdrawing funds...", message: "Withdrawing funds...",
description: "Please review transactions to approve.", description: "Please review transactions to approve.",
@ -50,7 +53,7 @@ export const withdraw = async (
authority, authority,
wallet.publicKey, wallet.publicKey,
[], [],
amountLamports, amountLamports
) )
); );
@ -73,7 +76,7 @@ export const withdraw = async (
reserveAddress, reserveAddress,
reserve.collateralMint, reserve.collateralMint,
reserve.liquiditySupply, reserve.liquiditySupply,
authority, authority
) )
); );
@ -94,4 +97,4 @@ export const withdraw = async (
} catch { } catch {
// TODO: // TODO:
} }
} };

View File

@ -9,42 +9,42 @@ export const AppBar = (props: { left?: JSX.Element; right?: JSX.Element }) => {
const { connected, wallet } = useWallet(); const { connected, wallet } = useWallet();
const TopBar = ( const TopBar = (
<div className="App-Bar-right"> <div className="App-Bar-right">
<CurrentUserBadge /> <CurrentUserBadge />
<div> <div>
{!connected && ( {!connected && (
<Button <Button
type="text" type="text"
size="large" size="large"
onClick={connected ? wallet.disconnect : wallet.connect} onClick={connected ? wallet.disconnect : wallet.connect}
style={{ color: "#2abdd2" }} style={{ color: "#2abdd2" }}
>
Connect
</Button>
)}
{connected && (
<Popover
placement="bottomRight"
title="Wallet public key"
trigger="click"
></Popover>
)}
</div>
<Popover
placement="topRight"
title="Settings"
content={<Settings />}
trigger="click"
> >
<Button Connect
shape="circle" </Button>
size="large" )}
type="text" {connected && (
icon={<SettingOutlined />} <Popover
/> placement="bottomRight"
</Popover> title="Wallet public key"
{props.right} trigger="click"
></Popover>
)}
</div> </div>
<Popover
placement="topRight"
title="Settings"
content={<Settings />}
trigger="click"
>
<Button
shape="circle"
size="large"
type="text"
icon={<SettingOutlined />}
/>
</Popover>
{props.right}
</div>
); );
return TopBar; return TopBar;

View File

@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from "react"; import React, { useCallback, useMemo, useState } from "react";
import { useLendingReserves, useTokenName, useUserBalance } from '../../hooks'; import { useLendingReserves, useTokenName, useUserBalance } from "../../hooks";
import { LendingReserve, LendingReserveParser } from "../../models"; import { LendingReserve, LendingReserveParser } from "../../models";
import { TokenIcon } from "../TokenIcon"; import { TokenIcon } from "../TokenIcon";
import { getTokenName } from "../../utils/utils"; import { getTokenName } from "../../utils/utils";
@ -8,9 +8,9 @@ import { cache, ParsedAccount } from "../../contexts/accounts";
import { NumericInput } from "../Input/numeric"; import { NumericInput } from "../Input/numeric";
import { useConnection, useConnectionConfig } from "../../contexts/connection"; import { useConnection, useConnectionConfig } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet"; import { useWallet } from "../../contexts/wallet";
import { borrow } from '../../actions'; import { borrow } from "../../actions";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import './style.less'; import "./style.less";
const { Option } = Select; const { Option } = Select;
@ -22,60 +22,71 @@ const CollateralSelector = (props: {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
const { tokenMap } = useConnectionConfig(); const { tokenMap } = useConnectionConfig();
return <Select return (
size="large" <Select
showSearch size="large"
style={{ minWidth: 120 }} showSearch
placeholder="Collateral" style={{ minWidth: 120 }}
value={props.mint} placeholder="Collateral"
onChange={(item) => { value={props.mint}
if (props.onMintChange) { onChange={(item) => {
props.onMintChange(item); if (props.onMintChange) {
props.onMintChange(item);
}
}}
filterOption={(input, option) =>
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
} }
}} >
filterOption={(input, option) => {reserveAccounts
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0 .filter((reserve) => reserve.info !== props.reserve)
} .map((reserve) => {
> const mint = reserve.info.liquidityMint.toBase58();
{reserveAccounts const address = reserve.pubkey.toBase58();
.filter(reserve => reserve.info !== props.reserve) const name = getTokenName(tokenMap, mint);
.map(reserve => { return (
const mint = reserve.info.liquidityMint.toBase58(); <Option key={address} value={address} name={name} title={address}>
const address = reserve.pubkey.toBase58(); <div
const name = getTokenName(tokenMap, mint); key={address}
return <Option key={address} value={address} name={name} title={address}> style={{ display: "flex", alignItems: "center" }}
<div key={address} style={{ display: "flex", alignItems: "center" }}> >
<TokenIcon mintAddress={mint} /> <TokenIcon mintAddress={mint} />
{name} {name}
</div> </div>
</Option> </Option>
})} );
</Select>; })}
} </Select>
);
};
export const BorrowInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { export const BorrowInput = (props: {
className?: string;
reserve: LendingReserve;
address: PublicKey;
}) => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const [value, setValue] = useState(''); const [value, setValue] = useState("");
const borrowReserve = props.reserve; const borrowReserve = props.reserve;
const borrowReserveAddress = props.address; const borrowReserveAddress = props.address;
const [collateralReserveMint, setCollateralReserveMint] = useState<string>(); const [collateralReserveMint, setCollateralReserveMint] = useState<string>();
const collateralReserve = useMemo(() => { const collateralReserve = useMemo(() => {
const id: string = cache.byParser(LendingReserveParser) const id: string =
.find(acc => acc === collateralReserveMint) || ''; cache
.byParser(LendingReserveParser)
.find((acc) => acc === collateralReserveMint) || "";
return cache.get(id) as ParsedAccount<LendingReserve>; return cache.get(id) as ParsedAccount<LendingReserve>;
}, [collateralReserveMint]) }, [collateralReserveMint]);
const name = useTokenName(borrowReserve?.liquidityMint); const name = useTokenName(borrowReserve?.liquidityMint);
const { const { accounts: fromAccounts } = useUserBalance(
accounts: fromAccounts collateralReserve?.info.collateralMint
} = useUserBalance(collateralReserve?.info.collateralMint); );
// const collateralBalance = useUserBalance(reserve?.collateralMint); // const collateralBalance = useUserBalance(reserve?.collateralMint);
const onBorrow = useCallback(() => { const onBorrow = useCallback(() => {
@ -91,50 +102,71 @@ export const BorrowInput = (props: { className?: string, reserve: LendingReserve
collateralReserve.info, collateralReserve.info,
collateralReserve.pubkey, collateralReserve.pubkey,
connection, connection,
wallet); wallet
}, [value, borrowReserve, fromAccounts, borrowReserveAddress]); );
}, [
connection,
wallet,
value,
collateralReserve,
borrowReserve,
fromAccounts,
borrowReserveAddress,
]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: 'flex', display: "flex",
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
height: '100%', height: "100%",
}; };
return <Card className={props.className} bodyStyle={bodyStyle}> return (
<Card className={props.className} bodyStyle={bodyStyle}>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}> <div
<div className="borrow-input-title"> style={{
How much would you like to borrow? display: "flex",
</div> flexDirection: "column",
<div className="token-input"> justifyContent: "space-around",
<TokenIcon mintAddress={borrowReserve?.liquidityMint} /> }}
<NumericInput value={value} >
onChange={(val: any) => { <div className="borrow-input-title">
setValue(val); How much would you like to borrow?
}} </div>
autoFocus={true} <div className="token-input">
style={{ <TokenIcon mintAddress={borrowReserve?.liquidityMint} />
fontSize: 20, <NumericInput
boxShadow: "none", value={value}
borderColor: "transparent", onChange={(val: any) => {
outline: "transpaernt", setValue(val);
}} }}
placeholder="0.00" autoFocus={true}
/> style={{
<div>{name}</div> fontSize: 20,
</div> boxShadow: "none",
<div className="borrow-input-title"> borderColor: "transparent",
Select collateral account? outline: "transpaernt",
</div> }}
<CollateralSelector placeholder="0.00"
reserve={borrowReserve} />
mint={collateralReserveMint} <div>{name}</div>
onMintChange={setCollateralReserveMint} </div>
<div className="borrow-input-title">Select collateral account?</div>
<CollateralSelector
reserve={borrowReserve}
mint={collateralReserveMint}
onMintChange={setCollateralReserveMint}
/> />
<Button type="primary" onClick={onBorrow} disabled={fromAccounts.length === 0}>Borrow</Button> <Button
</div> type="primary"
</Card >; onClick={onBorrow}
} disabled={fromAccounts.length === 0}
>
Borrow
</Button>
</div>
</Card>
);
};

View File

@ -18,13 +18,13 @@ export const CurrentUserBadge = (props: {}) => {
return ( return (
<div className="wallet-wrapper"> <div className="wallet-wrapper">
<span> <span>
{formatNumber.format(((account?.lamports || 0) / LAMPORTS_PER_SOL))} SOL {formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL
</span> </span>
<div className="wallet-key"> <div className="wallet-key">
{shortenAddress(`${wallet.publicKey}`)} {shortenAddress(`${wallet.publicKey}`)}
<Identicon <Identicon
address={wallet.publicKey.toBase58()} address={wallet.publicKey?.toBase58()}
style={{ marginLeft: "0.5rem", display: 'flex' }} style={{ marginLeft: "0.5rem", display: "flex" }}
/> />
</div> </div>
</div> </div>

View File

@ -1,31 +1,45 @@
import React, { } from "react"; import React from "react";
import { useTokenName, useUserBalance, useCollateralBalance } from './../../hooks'; import {
useTokenName,
useUserBalance,
useCollateralBalance,
} from "./../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
import { Card } from "antd"; import { Card } from "antd";
import './style.less'; import "./style.less";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
export const DepositInfoLine = (props: { export const DepositInfoLine = (props: {
className?: string, className?: string;
reserve: LendingReserve, reserve: LendingReserve;
address: PublicKey }) => { address: PublicKey;
}) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useCollateralBalance(props.reserve); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Card className={props.className} bodyStyle={{ display: 'flex', justifyContent: 'space-around', }} > return (
<div className="deposit-info-line-item "> <Card
<div>Your balance in Oyster</div> className={props.className}
<div>{formatNumber.format(collateralBalance)} {name}</div> bodyStyle={{ display: "flex", justifyContent: "space-around" }}
</div> >
<div className="deposit-info-line-item "> <div className="deposit-info-line-item ">
<div>Your wallet balance</div> <div>Your balance in Oyster</div>
<div>{formatNumber.format(tokenBalance)} {name}</div> <div>
</div> {formatNumber.format(collateralBalance)} {name}
<div className="deposit-info-line-item "> </div>
<div>Health factor</div> </div>
<div>--</div> <div className="deposit-info-line-item ">
</div> <div>Your wallet balance</div>
</Card> <div>
} {formatNumber.format(tokenBalance)} {name}
</div>
</div>
<div className="deposit-info-line-item ">
<div>Health factor</div>
<div>--</div>
</div>
</Card>
);
};

View File

@ -1,19 +1,23 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { useTokenName, useUserBalance } from '../../hooks'; import { 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 } from "antd"; import { Button, Card } 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 { deposit } from '../../actions/deposit'; import { deposit } from "../../actions/deposit";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import './style.less'; import "./style.less";
export const DepositInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { export const DepositInput = (props: {
className?: string;
reserve: LendingReserve;
address: PublicKey;
}) => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const [value, setValue] = useState(''); const [value, setValue] = useState("");
const reserve = props.reserve; const reserve = props.reserve;
const address = props.address; const address = props.address;
@ -29,42 +33,57 @@ export const DepositInput = (props: { className?: string, reserve: LendingReserv
reserve, reserve,
address, address,
connection, connection,
wallet); wallet
}, [value, reserve, fromAccounts, address]); );
}, [connection, wallet, value, reserve, fromAccounts, address]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: 'flex', display: "flex",
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
height: '100%', height: "100%",
}; };
return <Card className={props.className} bodyStyle={bodyStyle}> return (
<Card className={props.className} bodyStyle={bodyStyle}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<div className="deposit-input-title">
How much would you like to deposit?
</div>
<div className="token-input">
<TokenIcon mintAddress={reserve?.liquidityMint} />
<NumericInput
value={value}
onChange={(val: any) => {
setValue(val);
}}
autoFocus={true}
style={{
fontSize: 20,
boxShadow: "none",
borderColor: "transparent",
outline: "transpaernt",
}}
placeholder="0.00"
/>
<div>{name}</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}> <Button
<div className="deposit-input-title"> type="primary"
How much would you like to deposit? onClick={onDeposit}
disabled={fromAccounts.length === 0}
>
Deposit
</Button>
</div> </div>
<div className="token-input"> </Card>
<TokenIcon mintAddress={reserve?.liquidityMint} /> );
<NumericInput value={value} };
onChange={(val: any) => {
setValue(val);
}}
autoFocus={true}
style={{
fontSize: 20,
boxShadow: "none",
borderColor: "transparent",
outline: "transpaernt",
}}
placeholder="0.00"
/>
<div>{name}</div>
</div>
<Button type="primary" onClick={onDeposit} disabled={fromAccounts.length === 0}>Deposit</Button>
</div>
</Card >;
}

View File

@ -10,14 +10,17 @@ export const Identicon = (props: {
style?: React.CSSProperties; style?: React.CSSProperties;
className?: string; className?: string;
}) => { }) => {
const { style, className } = props; const { style, className } = props;
const address = typeof props.address === 'string' ? props.address : props.address?.toBase58(); const address =
typeof props.address === "string"
? props.address
: props.address?.toBase58();
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>();
useEffect(() => { useEffect(() => {
if (address && ref.current) { if (address && ref.current) {
ref.current.innerHTML = ""; ref.current.innerHTML = "";
ref.current.className = props.className || ""; ref.current.className = className || "";
ref.current.appendChild( ref.current.appendChild(
Jazzicon( Jazzicon(
style?.width || 16, style?.width || 16,

View File

@ -36,4 +36,4 @@ export class NumericInput extends React.Component<any, any> {
/> />
); );
} }
} }

View File

@ -1,16 +1,16 @@
import React from "react"; import React from "react";
import "./../../App.less"; import "./../../App.less";
import { Menu } from 'antd'; import { Menu } from "antd";
import { import {
PieChartOutlined, PieChartOutlined,
GithubOutlined, GithubOutlined,
BankOutlined, BankOutlined,
LogoutOutlined, LogoutOutlined,
HomeOutlined, HomeOutlined,
RocketOutlined RocketOutlined,
} from '@ant-design/icons'; } from "@ant-design/icons";
import BasicLayout, { } from '@ant-design/pro-layout'; import BasicLayout from "@ant-design/pro-layout";
import { AppBar } from "./../AppBar"; import { AppBar } from "./../AppBar";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { useConnectionConfig } from "../../contexts/connection"; import { useConnectionConfig } from "../../contexts/connection";
@ -19,16 +19,17 @@ export const AppLayout = (props: any) => {
const { env } = useConnectionConfig(); const { env } = useConnectionConfig();
const location = useLocation(); const location = useLocation();
console.log(location.pathname) console.log(location.pathname);
const paths: { [key: string]: string } = { const paths: { [key: string]: string } = {
'/dashboard': '2', "/dashboard": "2",
'/deposit': '3', "/deposit": "3",
'/borrow': '4', "/borrow": "4",
'/faucet': '4', "/faucet": "4",
}; };
const current = [...Object.keys(paths)].find(key => location.pathname.startsWith(key)) || ''; const current =
[...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) ||
"";
const defaultKey = paths[current] || "1"; const defaultKey = paths[current] || "1";
return ( return (
@ -36,9 +37,10 @@ export const AppLayout = (props: any) => {
<div className="Banner"> <div className="Banner">
<div className="Banner-description"> <div className="Banner-description">
Oyster Lending is unaudited software. Use at your own risk. Oyster Lending is unaudited software. Use at your own risk.
</div> </div>
</div> </div>
<BasicLayout title="Oyster Lending" <BasicLayout
title="Oyster Lending"
navTheme="realDark" navTheme="realDark"
headerTheme="dark" headerTheme="dark"
theme="dark" theme="dark"
@ -47,61 +49,66 @@ export const AppLayout = (props: any) => {
logo={<div className="App-logo" />} logo={<div className="App-logo" />}
rightContentRender={() => <AppBar />} rightContentRender={() => <AppBar />}
links={[ links={[
<div title="Fork"><GithubOutlined /></div> <div title="Fork">
<GithubOutlined />
</div>,
]} ]}
menuContentRender={() => { menuContentRender={() => {
return <Menu theme="dark" defaultSelectedKeys={[defaultKey]} mode="inline"> return (
<Menu.Item key="1" icon={<HomeOutlined />}> <Menu theme="dark" defaultSelectedKeys={[defaultKey]} mode="inline">
<Link <Menu.Item key="1" icon={<HomeOutlined />}>
to={{ <Link
pathname: "/", to={{
}} pathname: "/",
> }}
Home >
</Link> Home
</Menu.Item> </Link>
<Menu.Item key="2" icon={<PieChartOutlined />} > </Menu.Item>
<Link <Menu.Item key="2" icon={<PieChartOutlined />}>
to={{ <Link
pathname: "/dashboard", to={{
}} pathname: "/dashboard",
> }}
Dashboard >
</Link> Dashboard
</Menu.Item> </Link>
<Menu.Item key="3" icon={<BankOutlined />}> </Menu.Item>
<Link <Menu.Item key="3" icon={<BankOutlined />}>
to={{ <Link
pathname: "/deposit", to={{
}} pathname: "/deposit",
> }}
Deposit >
</Link> Deposit
</Menu.Item> </Link>
<Menu.Item key="4" icon={<LogoutOutlined />}> </Menu.Item>
<Link <Menu.Item key="4" icon={<LogoutOutlined />}>
to={{ <Link
pathname: "/borrow", to={{
}} pathname: "/borrow",
> }}
Borrow >
</Link> Borrow
</Menu.Item> </Link>
{env !== "mainnet-beta" && <Menu.Item key="5" icon={<RocketOutlined />}> </Menu.Item>
<Link {env !== "mainnet-beta" && (
to={{ <Menu.Item key="5" icon={<RocketOutlined />}>
pathname: "/faucet", <Link
}} to={{
> pathname: "/faucet",
Faucet }}
</Link> >
</Menu.Item>} Faucet
</Menu> </Link>
</Menu.Item>
)}
</Menu>
);
}} }}
> >
{props.children} {props.children}
</BasicLayout> </BasicLayout>
</div> </div>
); );
} };

View File

@ -1,26 +1,35 @@
import React, { useState } from "react"; import React from "react";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { Card } from "antd"; import { Card } from "antd";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import './style.less'; import "./style.less";
export const ReserveStatus = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { export const ReserveStatus = (props: {
const [] = useState(''); className?: string;
reserve: LendingReserve;
const bodyStyle: React.CSSProperties = { address: PublicKey;
display: 'flex', }) => {
justifyContent: 'center', const bodyStyle: React.CSSProperties = {
alignItems: 'center' display: "flex",
justifyContent: "center",
alignItems: "center",
}; };
return <Card className={props.className} return (
title={ <Card
<>Reserve Status &amp; Configuration</> className={props.className}
} title={<>Reserve Status &amp; Configuration</>}
bodyStyle={bodyStyle}> bodyStyle={bodyStyle}
>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}> <div
TODO: Reserve Status - add chart style={{
</div> display: "flex",
</Card >; flexDirection: "column",
} justifyContent: "space-around",
}}
>
TODO: Reserve Status - add chart
</div>
</Card>
);
};

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { useTokenName } from './../../hooks'; import { useTokenName } from "./../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber, formatPct, fromLamports } from "../../utils/utils"; import { formatNumber, formatPct, fromLamports } from "../../utils/utils";
@ -10,22 +10,25 @@ import { PublicKey } from "@solana/web3.js";
const { Text } = Typography; const { Text } = Typography;
export enum SideReserveOverviewMode { export enum SideReserveOverviewMode {
Deposit = 'deposit', Deposit = "deposit",
Borrow = 'borrow' Borrow = "borrow",
} }
export const SideReserveOverview = (props: { export const SideReserveOverview = (props: {
className?: string; className?: string;
reserve: LendingReserve, reserve: LendingReserve;
address: PublicKey, address: PublicKey;
mode: SideReserveOverviewMode mode: SideReserveOverviewMode;
}) => { }) => {
const reserve = props.reserve; const reserve = props.reserve;
const mode = props.mode; const mode = props.mode;
const name = useTokenName(reserve?.liquidityMint); const name = useTokenName(reserve?.liquidityMint);
const liquidityMint = useMint(props.reserve.liquidityMint); const liquidityMint = useMint(props.reserve.liquidityMint);
const totalLiquidity = fromLamports(props.reserve.totalLiquidity.toNumber(), liquidityMint); const totalLiquidity = fromLamports(
props.reserve.totalLiquidity.toNumber(),
liquidityMint
);
// TODO: calculate // TODO: calculate
const depositApy = 0.048; const depositApy = 0.048;
@ -38,80 +41,91 @@ export const SideReserveOverview = (props: {
let extraInfo: JSX.Element | null = null; let extraInfo: JSX.Element | null = null;
if (mode === SideReserveOverviewMode.Deposit) { if (mode === SideReserveOverviewMode.Deposit) {
extraInfo = (
extraInfo = <> <>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Deposit APY: Deposit APY:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">{formatPct.format(depositApy)}</div>
{formatPct.format(depositApy)}
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Maxiumum LTV: Maxiumum LTV:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">{formatPct.format(maxLTV)}</div>
{formatPct.format(maxLTV)}
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Liquidation threashold: Liquidation threashold:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatPct.format(liquidiationThreshold)} {formatPct.format(liquidiationThreshold)}
</div>
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Liquidation penalty: Liquidation penalty:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatPct.format(liquidiationPenalty)} {formatPct.format(liquidiationPenalty)}
</div>
</div> </div>
</div> </>
</>; );
} else if (mode === SideReserveOverviewMode.Borrow) { } else if (mode === SideReserveOverviewMode.Borrow) {
extraInfo = <> extraInfo = (
<div className="card-row"> <>
<Text type="secondary" className="card-cell "> <div className="card-row">
Borrow APY: <Text type="secondary" className="card-cell ">
</Text> Borrow APY:
<div className="card-cell "> </Text>
{formatPct.format(borrowApy)} <div className="card-cell ">{formatPct.format(borrowApy)}</div>
</div> </div>
</div> </>
</>; );
} }
return <Card className={props.className} title={ return (
<div style={{ display: 'flex', alignItems: 'center', fontSize: '1.2rem', justifyContent: 'center' }}> <Card
<TokenIcon mintAddress={reserve?.liquidityMint} style={{ width: 30, height: 30 }} /> {name} Reserve Overview className={props.className}
</div> title={
}> <div
<div className="card-row"> style={{
<Text type="secondary" className="card-cell "> display: "flex",
Utilization rate: alignItems: "center",
fontSize: "1.2rem",
justifyContent: "center",
}}
>
<TokenIcon
mintAddress={reserve?.liquidityMint}
style={{ width: 30, height: 30 }}
/>{" "}
{name} Reserve Overview
</div>
}
>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Utilization rate:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">{formatPct.format(utilizationRate)}</div>
{formatPct.format(utilizationRate)}
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Available liquidity: Available liquidity:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(totalLiquidity)} {name} {formatNumber.format(totalLiquidity)} {name}
</div>
</div> </div>
</div>
{extraInfo} {extraInfo}
</Card>; </Card>
} );
};

View File

@ -18,8 +18,8 @@ export const TokenIcon = (props: {
alt="Token icon" alt="Token icon"
className={props.className} className={props.className}
key={icon} key={icon}
width={props.style?.width || '20'} width={props.style?.width || "20"}
height={props.style?.height || '20'} height={props.style?.height || "20"}
src={icon} src={icon}
style={{ style={{
marginRight: "0.5rem", marginRight: "0.5rem",

View File

@ -1,18 +1,21 @@
import React from "react"; import React from "react";
import { useCollateralBalance, useTokenName, useUserBalance } from './../../hooks'; import {
useCollateralBalance,
useTokenName,
useUserBalance,
} from "./../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
import { Button, Card, Typography } from "antd"; import { Button, Card, Typography } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useMint } from "../../contexts/accounts";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
const { Text } = Typography; const { Text } = Typography;
export const UserLendingCard = (props: { export const UserLendingCard = (props: {
className?: string; className?: string;
reserve: LendingReserve, reserve: LendingReserve;
address: PublicKey, address: PublicKey;
}) => { }) => {
const reserve = props.reserve; const reserve = props.reserve;
const address = props.address; const address = props.address;
@ -22,92 +25,99 @@ export const UserLendingCard = (props: {
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useCollateralBalance(props.reserve); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
// TODO: calculate // TODO: calculate
const borrowed = 0; const borrowed = 0;
const healthFactor = '--'; const healthFactor = "--";
const ltv = 0; const ltv = 0;
const available = 0; const available = 0;
return (
<Card
className={props.className}
title={
<div
style={{
display: "flex",
alignItems: "center",
fontSize: "1.2rem",
justifyContent: "center",
}}
>
Your Information
</div>
}
>
<h3>Borrows</h3>
return <Card className={props.className} title={ <div className="card-row">
<div style={{ display: 'flex', alignItems: 'center', fontSize: '1.2rem', justifyContent: 'center' }}> <Text type="secondary" className="card-cell ">
Your Information Borrowed
</div>
}>
<h3>Borrows</h3>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Borrowed
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(borrowed)} {name} {formatNumber.format(borrowed)} {name}
</div>
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Health factor: Health factor:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">{healthFactor}</div>
{healthFactor}
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Loan to value: Loan to value:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">{formatNumber.format(ltv)}</div>
{formatNumber.format(ltv)}
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Available to you: Available to you:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(available)} {name} {formatNumber.format(available)} {name}
</div>
</div> </div>
</div>
<h3>Deposits</h3> <h3>Deposits</h3>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
Wallet balance: Wallet balance:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(tokenBalance)} {name} {formatNumber.format(tokenBalance)} {name}
</div>
</div> </div>
</div>
<div className="card-row"> <div className="card-row">
<Text type="secondary" className="card-cell "> <Text type="secondary" className="card-cell ">
You already deposited: You already deposited:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(collateralBalance)} {name} {formatNumber.format(collateralBalance)} {name}
</div>
</div> </div>
</div>
<div className="card-row" style={{ marginTop: 20, justifyContent: 'space-evenly' }}> <div
<Link to={`/deposit/${address}`}> className="card-row"
<Button>Deposit</Button> style={{ marginTop: 20, justifyContent: "space-evenly" }}
</Link> >
<Link to={`/borrow/${address}`}> <Link to={`/deposit/${address}`}>
<Button>Borrow</Button> <Button>Deposit</Button>
</Link> </Link>
<Link to={`/withdraw/${address}`}> <Link to={`/borrow/${address}`}>
<Button>Withdraw</Button> <Button>Borrow</Button>
</Link> </Link>
<Link to={`/repay/${address}`}> <Link to={`/withdraw/${address}`}>
<Button disabled={true}>Repay</Button> <Button>Withdraw</Button>
</Link> </Link>
</div> <Link to={`/repay/${address}`}>
<Button disabled={true}>Repay</Button>
</Link>
</Card>; </div>
} </Card>
);
};

View File

@ -1,77 +1,110 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { useCollateralBalance, useTokenName, useUserBalance } from '../../hooks'; import {
useCollateralBalance,
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 } from "antd"; import { Button, Card } 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 { useMint } from "../../contexts/accounts";
export const WithdrawInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { export const WithdrawInput = (props: {
className?: string;
reserve: LendingReserve;
address: PublicKey;
}) => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const [value, setValue] = useState(''); const [value, setValue] = useState("");
const reserve = props.reserve; const reserve = props.reserve;
const address = props.address; const address = props.address;
const liquidityMint = useMint(reserve?.liquidityMint);
const name = useTokenName(reserve?.liquidityMint); const name = useTokenName(reserve?.liquidityMint);
const { balanceLamports: collateralBalanceLamports, accounts: fromAccounts } = useUserBalance(reserve?.collateralMint); const {
const { balanceLamports: collateralBalanceLamports,
balance: collateralBalanceInLiquidity accounts: fromAccounts,
} = useCollateralBalance(reserve); } = useUserBalance(reserve?.collateralMint);
const { balance: collateralBalanceInLiquidity } = useCollateralBalance(
reserve
);
const onWithdraw = useCallback(() => { const onWithdraw = useCallback(() => {
withdraw( withdraw(
fromAccounts[0], fromAccounts[0],
Math.ceil( Math.ceil(
collateralBalanceLamports * collateralBalanceLamports *
(parseFloat(value) / collateralBalanceInLiquidity) (parseFloat(value) / collateralBalanceInLiquidity)
), ),
reserve, reserve,
address, address,
connection, connection,
wallet); wallet
}, [value, reserve, fromAccounts, address, liquidityMint]); );
}, [
connection,
wallet,
collateralBalanceLamports,
collateralBalanceInLiquidity,
value,
reserve,
fromAccounts,
address,
]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: 'flex', display: "flex",
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
height: '100%', height: "100%",
}; };
return <Card className={props.className} bodyStyle={bodyStyle}> return (
<Card className={props.className} bodyStyle={bodyStyle}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<div className="withdraw-input-title">
How much would you like to withdraw?
</div>
<div className="token-input">
<TokenIcon mintAddress={reserve?.liquidityMint} />
<NumericInput
value={value}
onChange={(val: any) => {
setValue(val);
}}
autoFocus={true}
style={{
fontSize: 20,
boxShadow: "none",
borderColor: "transparent",
outline: "transpaernt",
}}
placeholder="0.00"
/>
<div>{name}</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}> <Button
<div className="withdraw-input-title"> type="primary"
How much would you like to withdraw? onClick={onWithdraw}
disabled={fromAccounts.length === 0}
>
Withdraw
</Button>
</div> </div>
<div className="token-input"> </Card>
<TokenIcon mintAddress={reserve?.liquidityMint} /> );
<NumericInput value={value} };
onChange={(val: any) => {
setValue(val);
}}
autoFocus={true}
style={{
fontSize: 20,
boxShadow: "none",
borderColor: "transparent",
outline: "transpaernt",
}}
placeholder="0.00"
/>
<div>{name}</div>
</div>
<Button type="primary" onClick={onWithdraw} disabled={fromAccounts.length === 0}>Withdraw</Button>
</div>
</Card >;
}

View File

@ -12,7 +12,7 @@ export let LENDING_PROGRAM_ID = new PublicKey(
); );
export const setProgramIds = (envName: string) => { export const setProgramIds = (envName: string) => {
// Add dynamic program ids // Add dynamic program ids
}; };
export const programIds = () => { export const programIds = () => {

View File

@ -3,7 +3,7 @@ import { useConnection } from "./connection";
import { useWallet } from "./wallet"; import { useWallet } from "./wallet";
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
import { programIds, WRAPPED_SOL_MINT } from "./../constants/ids"; import { programIds, WRAPPED_SOL_MINT } from "./../constants/ids";
import { AccountLayout, u64, MintInfo, MintLayout, Token } from "@solana/spl-token"; import { AccountLayout, u64, MintInfo, MintLayout } from "@solana/spl-token";
import { TokenAccount } from "./../models"; import { TokenAccount } from "./../models";
import { chunks } from "./../utils/utils"; import { chunks } from "./../utils/utils";
import { EventEmitter } from "./../utils/eventEmitter"; import { EventEmitter } from "./../utils/eventEmitter";
@ -46,7 +46,10 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
return details; return details;
}; };
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => { export const TokenAccountParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>
) => {
const buffer = Buffer.from(info.data); const buffer = Buffer.from(info.data);
const data = deserializeAccount(buffer); const data = deserializeAccount(buffer);
@ -118,8 +121,12 @@ export const cache = {
return query; return query;
}, },
add: (id: PublicKey | string, obj: AccountInfo<Buffer>, parser?: AccountParser) => { add: (
const address = typeof id === 'string' ? id : id?.toBase58(); id: PublicKey | string,
obj: AccountInfo<Buffer>,
parser?: AccountParser
) => {
const address = typeof id === "string" ? id : id?.toBase58();
const deserialize = parser ? parser : keyToAccountParser.get(address); const deserialize = parser ? parser : keyToAccountParser.get(address);
if (!deserialize) { if (!deserialize) {
throw new Error( throw new Error(
@ -160,7 +167,7 @@ export const cache = {
}, },
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => { registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
if (pubkey) { if (pubkey) {
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58(); const address = typeof pubkey === "string" ? pubkey : pubkey?.toBase58();
keyToAccountParser.set(address, parser); keyToAccountParser.set(address, parser);
} }
@ -172,7 +179,7 @@ export const useAccountsContext = () => {
const context = useContext(AccountsContext); const context = useContext(AccountsContext);
return context; return context;
} };
function wrapNativeAccount( function wrapNativeAccount(
pubkey: PublicKey, pubkey: PublicKey,
@ -206,13 +213,16 @@ const UseNativeAccount = () => {
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>(); const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
const updateCache = useCallback((account) => { const updateCache = useCallback(
const wrapped = wrapNativeAccount(wallet.publicKey, account); (account) => {
if (wrapped !== undefined && wallet) { const wrapped = wrapNativeAccount(wallet.publicKey, account);
cache.registerParser(wallet.publicKey.toBase58(), TokenAccountParser); if (wrapped !== undefined && wallet) {
genericCache.set(wallet.publicKey.toBase58(), wrapped as TokenAccount); cache.registerParser(wallet.publicKey?.toBase58(), TokenAccountParser);
} genericCache.set(wallet.publicKey?.toBase58(), wrapped as TokenAccount);
}, [wallet]); }
},
[wallet]
);
useEffect(() => { useEffect(() => {
if (!connection || !wallet?.publicKey) { if (!connection || !wallet?.publicKey) {
@ -231,7 +241,7 @@ const UseNativeAccount = () => {
setNativeAccount(acc); setNativeAccount(acc);
} }
}); });
}, [setNativeAccount, wallet, wallet.publicKey, connection]); }, [setNativeAccount, wallet, wallet.publicKey, connection, updateCache]);
return { nativeAccount }; return { nativeAccount };
}; };
@ -252,10 +262,9 @@ const precacheUserTokenAccounts = async (
const accounts = await connection.getTokenAccountsByOwner(owner, { const accounts = await connection.getTokenAccountsByOwner(owner, {
programId: programIds().token, programId: programIds().token,
}); });
accounts.value accounts.value.forEach((info) => {
.forEach((info) => { cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser); });
});
}; };
export function AccountsProvider({ children = null as any }) { export function AccountsProvider({ children = null as any }) {
@ -266,15 +275,21 @@ export function AccountsProvider({ children = null as any }) {
const { nativeAccount } = UseNativeAccount(); const { nativeAccount } = UseNativeAccount();
const selectUserAccounts = useCallback(() => { const selectUserAccounts = useCallback(() => {
return cache.byParser(TokenAccountParser).map(id => cache.get(id)).filter( return cache
(a) => a && a.info.owner.toBase58() === wallet.publicKey.toBase58() .byParser(TokenAccountParser)
).map(a => a as TokenAccount); .map((id) => cache.get(id))
.filter(
(a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58()
)
.map((a) => a as TokenAccount);
}, [wallet]); }, [wallet]);
useEffect(() => { useEffect(() => {
const accounts = selectUserAccounts().filter((a) => a !== undefined) as TokenAccount[]; const accounts = selectUserAccounts().filter(
(a) => a !== undefined
) as TokenAccount[];
setUserAccounts(accounts); setUserAccounts(accounts);
}, [nativeAccount, wallet, tokenAccounts]); }, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
const publicKey = wallet?.publicKey; const publicKey = wallet?.publicKey;
useEffect(() => { useEffect(() => {
@ -296,10 +311,7 @@ export function AccountsProvider({ children = null as any }) {
if (info.accountInfo.data.length === AccountLayout.span) { if (info.accountInfo.data.length === AccountLayout.span) {
const data = deserializeAccount(info.accountInfo.data); const data = deserializeAccount(info.accountInfo.data);
if ( if (PRECACHED_OWNERS.has(data.owner.toBase58()) || !cache.get(id)) {
PRECACHED_OWNERS.has(data.owner.toBase58()) ||
!cache.get(id)
) {
cache.add(id, info.accountInfo, TokenAccountParser); cache.add(id, info.accountInfo, TokenAccountParser);
setTokenAccounts(selectUserAccounts()); setTokenAccounts(selectUserAccounts());
accountEmitter.raiseAccountUpdated(id); accountEmitter.raiseAccountUpdated(id);
@ -359,18 +371,20 @@ export const getMultipleAccounts = async (
const array = result const array = result
.map( .map(
(a) => (a) =>
a.array.map((acc) => { a.array
if (!acc) { .map((acc) => {
return; if (!acc) {
} return;
}
const { data, ...rest } = acc; const { data, ...rest } = acc;
const obj = { const obj = {
...rest, ...rest,
data: Buffer.from(data[0], "base64"), data: Buffer.from(data[0], "base64"),
} as AccountInfo<Buffer>; } as AccountInfo<Buffer>;
return obj; return obj;
}).filter(_ => _) as AccountInfo<Buffer>[] })
.filter((_) => _) as AccountInfo<Buffer>[]
) )
.flat(); .flat();
return { keys, array }; return { keys, array };
@ -412,15 +426,15 @@ export function useMint(key?: string | PublicKey) {
cache cache
.query(connection, id, MintParser) .query(connection, id, MintParser)
.then(acc => setMint(acc.info as any)) .then((acc) => setMint(acc.info as any))
.catch((err) => .catch((err) => console.log(err));
console.log(err)
);
const dispose = cache.emitter.onCache((e) => { const dispose = cache.emitter.onCache((e) => {
const event = e; const event = e;
if (event.id === id) { if (event.id === id) {
cache.query(connection, id, MintParser).then(mint => setMint(mint.info as any)); cache
.query(connection, id, MintParser)
.then((mint) => setMint(mint.info as any));
} }
}); });
return () => { return () => {
@ -443,9 +457,9 @@ export function useAccount(pubKey?: PublicKey) {
return; return;
} }
const acc = await cache.query(connection, key, TokenAccountParser).catch((err) => const acc = await cache
console.log(err) .query(connection, key, TokenAccountParser)
); .catch((err) => console.log(err));
if (acc) { if (acc) {
setAccount(acc); setAccount(acc);
} }

View File

@ -10,13 +10,18 @@ import React, { useContext, useEffect, useMemo, useState } from "react";
import { setProgramIds } from "./../constants/ids"; import { setProgramIds } from "./../constants/ids";
import { notify } from "./../utils/notifications"; import { notify } from "./../utils/notifications";
import { ExplorerLink } from "../components/ExplorerLink"; import { ExplorerLink } from "../components/ExplorerLink";
import LocalTokens from '../config/tokens.json'; import LocalTokens from "../config/tokens.json";
export type ENV = "mainnet-beta" | "testnet" | "devnet" | "localnet" | "lending"; export type ENV =
| "mainnet-beta"
| "testnet"
| "devnet"
| "localnet"
| "lending";
export const ENDPOINTS = [ export const ENDPOINTS = [
{ {
name: 'lending' as ENV, name: "lending" as ENV,
endpoint: "https://tln.solana.com", endpoint: "https://tln.solana.com",
}, },
{ {
@ -88,7 +93,7 @@ export function ConnectionProvider({ children = undefined as any }) {
.then((res) => { .then((res) => {
return res.json(); return res.json();
}) })
.catch(err => []) .catch((err) => [])
.then((list: KnownToken[]) => { .then((list: KnownToken[]) => {
const knownMints = [...LocalTokens, ...list].reduce((map, item) => { const knownMints = [...LocalTokens, ...list].reduce((map, item) => {
map.set(item.mintAddress, item); map.set(item.mintAddress, item);

View File

@ -1,14 +1,23 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useConnection } from "./connection"; import { useConnection } from "./connection";
import { LENDING_PROGRAM_ID } from "./../constants/ids"; import { LENDING_PROGRAM_ID } from "./../constants/ids";
import { LendingMarketParser, isLendingReserve, isLendingMarket, LendingReserveParser, LendingReserve } from "./../models/lending"; import {
import { cache, getMultipleAccounts, MintParser, ParsedAccount } from "./accounts"; LendingMarketParser,
isLendingReserve,
isLendingMarket,
LendingReserveParser,
LendingReserve,
} from "./../models/lending";
import {
cache,
getMultipleAccounts,
MintParser,
ParsedAccount,
} from "./accounts";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { DexMarketParser } from "../models/dex"; import { DexMarketParser } from "../models/dex";
export interface LendingContextState { export interface LendingContextState {}
}
const LendingContext = React.createContext<LendingContextState | null>(null); const LendingContext = React.createContext<LendingContextState | null>(null);
@ -17,14 +26,13 @@ export function LendingProvider({ children = null as any }) {
return ( return (
<LendingContext.Provider <LendingContext.Provider
value={{ value={{
accounts accounts,
}} }}
> >
{children} {children}
</LendingContext.Provider> </LendingContext.Provider>
); );
}; }
export const useLending = () => { export const useLending = () => {
const connection = useConnection(); const connection = useConnection();
@ -32,9 +40,17 @@ export const useLending = () => {
const processAccount = useCallback((item) => { const processAccount = useCallback((item) => {
if (isLendingReserve(item.account)) { if (isLendingReserve(item.account)) {
return cache.add(item.pubkey.toBase58(), item.account, LendingReserveParser); return cache.add(
item.pubkey.toBase58(),
item.account,
LendingReserveParser
);
} else if (isLendingMarket(item.account)) { } else if (isLendingMarket(item.account)) {
return cache.add(item.pubkey.toBase58(), item.account, LendingMarketParser); return cache.add(
item.pubkey.toBase58(),
item.account,
LendingMarketParser
);
} }
}, []); }, []);
@ -45,20 +61,32 @@ export const useLending = () => {
const queryLendingAccounts = async () => { const queryLendingAccounts = async () => {
const accounts = (await connection.getProgramAccounts(LENDING_PROGRAM_ID)) const accounts = (await connection.getProgramAccounts(LENDING_PROGRAM_ID))
.map(processAccount) .map(processAccount)
.filter(item => item !== undefined); .filter((item) => item !== undefined);
const toQuery = [ const toQuery = [
...accounts.filter(acc => (acc?.info as LendingReserve).lendingMarket !== undefined) ...accounts
.map(acc => acc as ParsedAccount<LendingReserve>) .filter(
.map(acc => { (acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
const result = [ )
cache.registerParser(acc?.info.collateralMint.toBase58(), MintParser), .map((acc) => acc as ParsedAccount<LendingReserve>)
cache.registerParser(acc?.info.liquidityMint.toBase58(), MintParser), .map((acc) => {
// ignore dex if its not set const result = [
cache.registerParser(acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : '', DexMarketParser), cache.registerParser(
].filter(_ => _); acc?.info.collateralMint.toBase58(),
return result; MintParser
}) ),
cache.registerParser(
acc?.info.liquidityMint.toBase58(),
MintParser
),
// ignore dex if its not set
cache.registerParser(
acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : "",
DexMarketParser
),
].filter((_) => _);
return result;
}),
].flat() as string[]; ].flat() as string[];
// This will pre-cache all accounts used by pools // This will pre-cache all accounts used by pools
@ -76,12 +104,10 @@ export const useLending = () => {
return accounts; return accounts;
}; };
Promise.all([ Promise.all([queryLendingAccounts()]).then((all) => {
queryLendingAccounts(),
]).then((all) => {
setLendingAccounts(all.flat()); setLendingAccounts(all.flat());
}); });
}, [connection]); }, [connection, processAccount]);
useEffect(() => { useEffect(() => {
const subID = connection.onProgramAccountChange( const subID = connection.onProgramAccountChange(
@ -100,7 +126,7 @@ export const useLending = () => {
return () => { return () => {
connection.removeProgramAccountChangeListener(subID); connection.removeProgramAccountChangeListener(subID);
}; };
}, [connection, lendingAccounts]); }, [connection, lendingAccounts, processAccount]);
return { accounts: lendingAccounts }; return { accounts: lendingAccounts };
}; };

View File

@ -1,13 +1,8 @@
import React, { useCallback, useContext, useEffect, useState } from "react"; import React, { useCallback, useContext, useEffect, useState } from "react";
import { MINT_TO_MARKET } from "./../models/marketOverrides"; import { MINT_TO_MARKET } from "./../models/marketOverrides";
import { import { STABLE_COINS } from "./../utils/utils";
STABLE_COINS,
} from "./../utils/utils";
import { useConnectionConfig } from "./connection"; import { useConnectionConfig } from "./connection";
import { import { cache, getMultipleAccounts } from "./accounts";
cache,
getMultipleAccounts,
} from "./accounts";
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum"; import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum";
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
import { useMemo } from "react"; import { useMemo } from "react";
@ -39,14 +34,10 @@ export function MarketProvider({ children = null as any }) {
]); ]);
// TODO: identify which markets to query ... // TODO: identify which markets to query ...
const mints = useMemo(() => [ const mints = useMemo(() => [] as PublicKey[], []);
] as PublicKey[], []);
const marketByMint = useMemo(() => { const marketByMint = useMemo(() => {
return [ return [...new Set(mints).values()].reduce((acc, key) => {
...new Set(mints).values(),
].reduce((acc, key) => {
const mintAddress = key.toBase58(); const mintAddress = key.toBase58();
const SERUM_TOKEN = TOKEN_MINTS.find( const SERUM_TOKEN = TOKEN_MINTS.find(
@ -240,8 +231,6 @@ export const useMidPriceInUSD = (mint: string) => {
return { price, isBase: price === 1.0 }; return { price, isBase: price === 1.0 };
}; };
const getMidPrice = (marketAddress?: string, mintAddress?: string) => { const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
const SERUM_TOKEN = TOKEN_MINTS.find( const SERUM_TOKEN = TOKEN_MINTS.find(
(a) => a.address.toBase58() === mintAddress (a) => a.address.toBase58() === mintAddress

View File

@ -1,6 +1,6 @@
export * from './useUserAccounts'; export * from "./useUserAccounts";
export * from './useAccountByMint'; export * from "./useAccountByMint";
export * from './useLendingReserves'; export * from "./useLendingReserves";
export * from './useTokenName'; export * from "./useTokenName";
export * from './useUserBalance'; export * from "./useUserBalance";
export * from './useCollateralBalance'; export * from "./useCollateralBalance";

View File

@ -1,4 +1,4 @@
import { useUserAccounts } from './useUserAccounts'; import { useUserAccounts } from "./useUserAccounts";
export const useAccountByMint = (mint: string) => { export const useAccountByMint = (mint: string) => {
const { userAccounts } = useUserAccounts(); const { userAccounts } = useUserAccounts();

View File

@ -14,6 +14,6 @@ export function useCollateralBalance(reserve?: LendingReserve) {
return { return {
balance: fromLamports(collateralRatioLamports, mint), balance: fromLamports(collateralRatioLamports, mint),
balanceLamports: collateralRatioLamports, balanceLamports: collateralRatioLamports,
accounts accounts,
}; };
} }

View File

@ -1,14 +1,19 @@
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { LendingReserve, LendingReserveParser } from "../models/lending"; import { LendingReserve, LendingReserveParser } from "../models/lending";
import { cache, ParsedAccount } from './../contexts/accounts'; import { cache, ParsedAccount } from "./../contexts/accounts";
const getLendingReserves = () => { const getLendingReserves = () => {
return cache.byParser(LendingReserveParser).map(id => cache.get(id)).filter(acc => acc !== undefined) as any[]; return cache
.byParser(LendingReserveParser)
.map((id) => cache.get(id))
.filter((acc) => acc !== undefined) as any[];
}; };
export function useLendingReserves() { export function useLendingReserves() {
const [reserveAccounts, setReserveAccounts] = useState<ParsedAccount<LendingReserve>[]>([]); const [reserveAccounts, setReserveAccounts] = useState<
ParsedAccount<LendingReserve>[]
>([]);
useEffect(() => { useEffect(() => {
setReserveAccounts(getLendingReserves()); setReserveAccounts(getLendingReserves());
@ -22,7 +27,7 @@ export function useLendingReserves() {
return () => { return () => {
dispose(); dispose();
}; };
}, [setReserveAccounts]) }, [setReserveAccounts]);
return { return {
reserveAccounts, reserveAccounts,
@ -30,8 +35,10 @@ export function useLendingReserves() {
} }
export function useLendingReserve(address: string | PublicKey) { export function useLendingReserve(address: string | PublicKey) {
const id = typeof address === 'string' ? address : address?.toBase58(); const id = typeof address === "string" ? address : address?.toBase58();
const [reserveAccount, setReserveAccount] = useState<ParsedAccount<LendingReserve>>(); const [reserveAccount, setReserveAccount] = useState<
ParsedAccount<LendingReserve>
>();
useEffect(() => { useEffect(() => {
setReserveAccount(cache.get(id)); setReserveAccount(cache.get(id));
@ -45,7 +52,7 @@ export function useLendingReserve(address: string | PublicKey) {
return () => { return () => {
dispose(); dispose();
}; };
}, [setReserveAccount]) }, [id, setReserveAccount]);
return reserveAccount; return reserveAccount;
} }

View File

@ -4,6 +4,7 @@ import { getTokenName } from "../utils/utils";
export function useTokenName(mintAddress?: string | PublicKey) { export function useTokenName(mintAddress?: string | PublicKey) {
const { tokenMap } = useConnectionConfig(); const { tokenMap } = useConnectionConfig();
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58(); const address =
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58();
return getTokenName(tokenMap, address); return getTokenName(tokenMap, address);
} }

View File

@ -1,9 +1,9 @@
import { TokenAccount } from "../models"; import { TokenAccount } from "../models";
import { useAccountsContext } from './../contexts/accounts'; import { useAccountsContext } from "./../contexts/accounts";
export function useUserAccounts() { export function useUserAccounts() {
const context = useAccountsContext(); const context = useAccountsContext();
return { return {
userAccounts: context.userAccounts as TokenAccount[], userAccounts: context.userAccounts as TokenAccount[],
}; };
} }

View File

@ -9,18 +9,20 @@ export function useUserBalance(mint?: PublicKey) {
const mintInfo = useMint(mint); const mintInfo = useMint(mint);
const accounts = useMemo(() => { const accounts = useMemo(() => {
return userAccounts return userAccounts
.filter(acc => mint?.equals(acc.info.mint)) .filter((acc) => mint?.equals(acc.info.mint))
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber()); .sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
}, [userAccounts, mint]); }, [userAccounts, mint]);
const balanceLamports = useMemo(() => { const balanceLamports = useMemo(() => {
return accounts return accounts.reduce(
.reduce((res, item) => res += item.info.amount.toNumber(), 0); (res, item) => (res += item.info.amount.toNumber()),
},[accounts]); 0
);
}, [accounts]);
return { return {
balance: fromLamports(balanceLamports, mintInfo), balance: fromLamports(balanceLamports, mintInfo),
balanceLamports, balanceLamports,
accounts, accounts,
}; };
} }

View File

@ -1 +1 @@
export * from "./market"; export * from "./market";

View File

@ -20,12 +20,18 @@ export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
return details; return details;
}; };
const DEFAULT_DEX_ID = new PublicKey('EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o'); const DEFAULT_DEX_ID = new PublicKey(
"EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o"
);
export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) => { export const DexMarketParser = (
const market = MARKETS.find(m => m.address.equals(pubkey)); pubkey: PublicKey,
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID) acc: AccountInfo<Buffer>
.decode(acc.data); ) => {
const market = MARKETS.find((m) => m.address.equals(pubkey));
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(
acc.data
);
const details = { const details = {
pubkey, pubkey,
@ -41,4 +47,4 @@ export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) =>
cache.registerParser(details.info.asks, OrderBookParser); cache.registerParser(details.info.asks, OrderBookParser);
return details; return details;
} };

View File

@ -1,2 +1,2 @@
export * from "./account"; export * from "./account";
export * from './lending'; export * from "./lending";

View File

@ -8,7 +8,7 @@ import BN from "bn.js";
import * as BufferLayout from "buffer-layout"; import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids"; import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout"; import * as Layout from "./../../utils/layout";
import { LendingInstruction } from './lending'; import { LendingInstruction } from "./lending";
/// Borrow tokens from a reserve by depositing collateral tokens. The number of borrowed tokens /// Borrow tokens from a reserve by depositing collateral tokens. The number of borrowed tokens
/// is calculated by market price. The debt obligation is tokenized. /// is calculated by market price. The debt obligation is tokenized.
@ -49,7 +49,7 @@ export const borrowInstruction = (
dexMarket: PublicKey, dexMarket: PublicKey,
dexOrderBookSide: PublicKey, dexOrderBookSide: PublicKey,
memory: PublicKey, memory: PublicKey
): TransactionInstruction => { ): TransactionInstruction => {
const dataLayout = BufferLayout.struct([ const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"), BufferLayout.u8("instruction"),
@ -59,7 +59,7 @@ export const borrowInstruction = (
const data = Buffer.alloc(dataLayout.span); const data = Buffer.alloc(dataLayout.span);
dataLayout.encode( dataLayout.encode(
{ {
instruction: LendingInstruction.BorrowReserveLiquidity, instruction: LendingInstruction.BorrowReserveLiquidity,
collateralAmount: new BN(collateralAmount), collateralAmount: new BN(collateralAmount),
}, },
data data
@ -69,9 +69,17 @@ export const borrowInstruction = (
{ pubkey: from, isSigner: false, isWritable: true }, { pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true }, { pubkey: to, isSigner: false, isWritable: true },
{ pubkey: depositReserve, isSigner: false, isWritable: true }, { pubkey: depositReserve, isSigner: false, isWritable: true },
{ pubkey: depositReserveCollateralSupply, isSigner: false, isWritable: true }, {
pubkey: depositReserveCollateralSupply,
isSigner: false,
isWritable: true,
},
{ pubkey: borrowReserve, isSigner: false, isWritable: true }, { pubkey: borrowReserve, isSigner: false, isWritable: true },
{ pubkey: borrowReserveLiquiditySupply, isSigner: false, isWritable: false }, {
pubkey: borrowReserveLiquiditySupply,
isSigner: false,
isWritable: false,
},
{ pubkey: obligation, isSigner: false, isWritable: true }, { pubkey: obligation, isSigner: false, isWritable: true },
{ pubkey: obligationMint, isSigner: false, isWritable: true }, { pubkey: obligationMint, isSigner: false, isWritable: true },
{ pubkey: obligationTokenOutput, isSigner: false, isWritable: true }, { pubkey: obligationTokenOutput, isSigner: false, isWritable: true },

View File

@ -1,5 +1,5 @@
export * from './market'; export * from "./market";
export * from './reserve'; export * from "./reserve";
export * from './obligation'; export * from "./obligation";
export * from './lending'; export * from "./lending";
export * from './borrow'; export * from "./borrow";

View File

@ -4,5 +4,5 @@ export enum LendingInstruction {
DepositReserveLiquidity = 2, DepositReserveLiquidity = 2,
WithdrawReserveLiquidity = 3, WithdrawReserveLiquidity = 3,
BorrowReserveLiquidity = 4, BorrowReserveLiquidity = 4,
RepayReserveLiquidity = 5 RepayReserveLiquidity = 5,
} }

View File

@ -1,15 +1,9 @@
import { import { AccountInfo, PublicKey } from "@solana/web3.js";
AccountInfo,
PublicKey,
} from "@solana/web3.js";
import * as BufferLayout from "buffer-layout"; import * as BufferLayout from "buffer-layout";
import * as Layout from "./../../utils/layout"; import * as Layout from "./../../utils/layout";
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct( export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[ [BufferLayout.u8("isInitialized"), Layout.publicKey("quoteMint")]
BufferLayout.u8("isInitialized"),
Layout.publicKey("quoteMint"),
]
); );
export interface LendingMarket { export interface LendingMarket {
@ -19,9 +13,12 @@ export interface LendingMarket {
export const isLendingMarket = (info: AccountInfo<Buffer>) => { export const isLendingMarket = (info: AccountInfo<Buffer>) => {
return info.data.length === LendingMarketLayout.span; return info.data.length === LendingMarketLayout.span;
} };
export const LendingMarketParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => { export const LendingMarketParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>
) => {
const buffer = Buffer.from(info.data); const buffer = Buffer.from(info.data);
const data = LendingMarketLayout.decode(buffer); const data = LendingMarketLayout.decode(buffer);
@ -36,6 +33,5 @@ export const LendingMarketParser = (pubKey: PublicKey, info: AccountInfo<Buffer>
return details; return details;
}; };
// TODO: // TODO:
// create instructions for init // create instructions for init

View File

@ -1,26 +1,24 @@
import { import { PublicKey } from "@solana/web3.js";
PublicKey,
} from "@solana/web3.js";
import BN from "bn.js"; import BN from "bn.js";
import * as BufferLayout from "buffer-layout"; import * as BufferLayout from "buffer-layout";
import * as Layout from "./../../utils/layout"; import * as Layout from "./../../utils/layout";
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct( export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[ [
/// Slot when obligation was updated. Used for calculating interest. /// Slot when obligation was updated. Used for calculating interest.
Layout.uint64("lastUpdateSlot"), Layout.uint64("lastUpdateSlot"),
/// Amount of collateral tokens deposited for this obligation /// Amount of collateral tokens deposited for this obligation
Layout.uint64("collateralAmount"), Layout.uint64("collateralAmount"),
/// Reserve which collateral tokens were deposited into /// Reserve which collateral tokens were deposited into
Layout.publicKey("collateralSupply"), Layout.publicKey("collateralSupply"),
/// Borrow rate used for calculating interest. /// Borrow rate used for calculating interest.
Layout.uint128("cumulativeBorrowRate"), Layout.uint128("cumulativeBorrowRate"),
/// Amount of tokens borrowed for this obligation plus interest /// Amount of tokens borrowed for this obligation plus interest
Layout.uint128("borrowAmount"), Layout.uint128("borrowAmount"),
/// Reserve which tokens were borrowed from /// Reserve which tokens were borrowed from
Layout.publicKey("borrowReserve"), Layout.publicKey("borrowReserve"),
/// Mint address of the tokens for this obligation /// Mint address of the tokens for this obligation
Layout.publicKey("tokenMint"), Layout.publicKey("tokenMint"),
] ]
); );
@ -32,4 +30,4 @@ export interface LendingObligation {
borrowAmount: BN; // decimals borrowAmount: BN; // decimals
borrowReserve: PublicKey; borrowReserve: PublicKey;
tokenMint: PublicKey; tokenMint: PublicKey;
} }

View File

@ -9,7 +9,7 @@ import BN from "bn.js";
import * as BufferLayout from "buffer-layout"; import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids"; import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout"; import * as Layout from "./../../utils/layout";
import { LendingInstruction } from './lending'; import { LendingInstruction } from "./lending";
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct( export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[ [
@ -21,19 +21,22 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
Layout.publicKey("collateralMint"), Layout.publicKey("collateralMint"),
Layout.publicKey("collateralSupply"), Layout.publicKey("collateralSupply"),
// TODO: replace u32 option with generic quivalent // TODO: replace u32 option with generic quivalent
BufferLayout.u32('dexMarketOption'), BufferLayout.u32("dexMarketOption"),
Layout.publicKey("dexMarket"), Layout.publicKey("dexMarket"),
BufferLayout.struct([ BufferLayout.struct(
/// Max utilization rate as a percent [
BufferLayout.u8("maxUtilizationRate"), /// Max utilization rate as a percent
/// The ratio of the loan to the value of the collateral as a percent BufferLayout.u8("maxUtilizationRate"),
BufferLayout.u8("loanToValueRatio"), /// The ratio of the loan to the value of the collateral as a percent
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation BufferLayout.u8("loanToValueRatio"),
BufferLayout.u8("liquidationBonus"), /// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
/// The percent at which an obligation is considered unhealthy BufferLayout.u8("liquidationBonus"),
BufferLayout.u8("liquidationThreshold"), /// The percent at which an obligation is considered unhealthy
], "config"), BufferLayout.u8("liquidationThreshold"),
],
"config"
),
Layout.uint128("cumulativeBorrowRate"), Layout.uint128("cumulativeBorrowRate"),
Layout.uint128("totalBorrows"), Layout.uint128("totalBorrows"),
@ -47,7 +50,7 @@ export const isLendingReserve = (info: AccountInfo<Buffer>) => {
console.log(LendingReserveLayout.span); console.log(LendingReserveLayout.span);
console.log(info.data.length); console.log(info.data.length);
return info.data.length === LendingReserveLayout.span; return info.data.length === LendingReserveLayout.span;
} };
export interface LendingReserve { export interface LendingReserve {
lastUpdateSlot: BN; lastUpdateSlot: BN;
@ -63,11 +66,11 @@ export interface LendingReserve {
dexMarketPrice: BN; // what is precision on the price? dexMarketPrice: BN; // what is precision on the price?
config: { config: {
maxUtilizationRate: number, maxUtilizationRate: number;
loanToValueRatio: number, loanToValueRatio: number;
liquidationBonus: number, liquidationBonus: number;
liquidationThreshold: number, liquidationThreshold: number;
} };
// collateralFactor: number; // collateralFactor: number;
cumulativeBorrowRate: BN; cumulativeBorrowRate: BN;
@ -80,7 +83,10 @@ export interface LendingReserve {
// Layout.uint128("total_borrows"), // Layout.uint128("total_borrows"),
} }
export const LendingReserveParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => { export const LendingReserveParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>
) => {
const buffer = Buffer.from(info.data); const buffer = Buffer.from(info.data);
const data = LendingReserveLayout.decode(buffer); const data = LendingReserveLayout.decode(buffer);
@ -110,12 +116,12 @@ export const initReserveInstruction = (
lendingMarket: PublicKey, lendingMarket: PublicKey,
lendingMarketAuthority: PublicKey, lendingMarketAuthority: PublicKey,
dexMarket: PublicKey, // TODO: optional dexMarket: PublicKey // TODO: optional
): TransactionInstruction => { ): TransactionInstruction => {
const dataLayout = BufferLayout.struct([ const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"), BufferLayout.u8("instruction"),
Layout.uint64("liquidityAmount"), Layout.uint64("liquidityAmount"),
BufferLayout.u8("maxUtilizationRate") BufferLayout.u8("maxUtilizationRate"),
]); ]);
const data = Buffer.alloc(dataLayout.span); const data = Buffer.alloc(dataLayout.span);
@ -172,7 +178,7 @@ export const depositInstruction = (
reserveAuthority: PublicKey, reserveAuthority: PublicKey,
reserveAccount: PublicKey, reserveAccount: PublicKey,
reserveSupply: PublicKey, reserveSupply: PublicKey,
collateralMint: PublicKey, collateralMint: PublicKey
): TransactionInstruction => { ): TransactionInstruction => {
const dataLayout = BufferLayout.struct([ const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"), BufferLayout.u8("instruction"),
@ -212,7 +218,7 @@ export const withdrawInstruction = (
reserveAccount: PublicKey, reserveAccount: PublicKey,
collateralMint: PublicKey, collateralMint: PublicKey,
reserveSupply: PublicKey, reserveSupply: PublicKey,
authority: PublicKey, authority: PublicKey
): TransactionInstruction => { ): TransactionInstruction => {
const dataLayout = BufferLayout.struct([ const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"), BufferLayout.u8("instruction"),
@ -244,4 +250,3 @@ export const withdrawInstruction = (
data, data,
}); });
}; };

View File

@ -7,7 +7,7 @@ import { MarketProvider } from "./contexts/market";
import { LendingProvider } from "./contexts/lending"; import { LendingProvider } from "./contexts/lending";
import { AppLayout } from "./components/Layout"; import { AppLayout } from "./components/Layout";
import { import {
HomeView, HomeView,
DepositView, DepositView,
DepositReserveView, DepositReserveView,
@ -17,7 +17,7 @@ import {
BorrowReserveView, BorrowReserveView,
WithdrawView, WithdrawView,
FaucetView, FaucetView,
} from './views'; } from "./views";
export function Routes() { export function Routes() {
return ( return (
@ -31,13 +31,27 @@ export function Routes() {
<AppLayout> <AppLayout>
<Switch> <Switch>
<Route exact path="/" component={() => <HomeView />} /> <Route exact path="/" component={() => <HomeView />} />
<Route exact path="/dashboard" children={<DashboardView />} /> <Route
exact
path="/dashboard"
children={<DashboardView />}
/>
<Route path="/reserve/:id" children={<ReserveView />} /> <Route path="/reserve/:id" children={<ReserveView />} />
<Route exact path="/deposit" component={() => <DepositView />} /> <Route
<Route path="/deposit/:id" children={<DepositReserveView />} /> exact
path="/deposit"
component={() => <DepositView />}
/>
<Route
path="/deposit/:id"
children={<DepositReserveView />}
/>
<Route path="/withdraw/:id" children={<WithdrawView />} /> <Route path="/withdraw/:id" children={<WithdrawView />} />
<Route exact path="/borrow" children={<BorrowView />} /> <Route exact path="/borrow" children={<BorrowView />} />
<Route path="/borrow/:id" children={<BorrowReserveView />} /> <Route
path="/borrow/:id"
children={<BorrowReserveView />}
/>
<Route exact path="/faucet" children={<FaucetView />} /> <Route exact path="/faucet" children={<FaucetView />} />
</Switch> </Switch>
</AppLayout> </AppLayout>

View File

@ -44,7 +44,7 @@ export class EventEmitter {
onCache(callback: (args: CacheUpdateEvent) => void) { onCache(callback: (args: CacheUpdateEvent) => void) {
this.emitter.on(CacheUpdateEvent.type, callback); this.emitter.on(CacheUpdateEvent.type, callback);
return () => this.emitter.removeListener(CacheUpdateEvent .type, callback); return () => this.emitter.removeListener(CacheUpdateEvent.type, callback);
} }
raiseAccountUpdated(id: string) { raiseAccountUpdated(id: string) {

View File

@ -32,15 +32,14 @@ export const uint64 = (property = "uint64"): unknown => {
const _decode = layout.decode.bind(layout); const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout); const _encode = layout.encode.bind(layout);
layout.decode = (buffer: Buffer, offset: number) => { layout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset); const data = _decode(buffer, offset);
return new BN( return new BN(
[...data] [...data]
.reverse() .reverse()
.map(i => `00${i.toString(16)}`.slice(-2)) .map((i) => `00${i.toString(16)}`.slice(-2))
.join(''), .join(""),
16, 16
); );
}; };
@ -65,15 +64,14 @@ export const uint128 = (property = "uint128"): unknown => {
const _decode = layout.decode.bind(layout); const _decode = layout.decode.bind(layout);
const _encode = layout.encode.bind(layout); const _encode = layout.encode.bind(layout);
layout.decode = (buffer: Buffer, offset: number) => { layout.decode = (buffer: Buffer, offset: number) => {
const data = _decode(buffer, offset); const data = _decode(buffer, offset);
return new BN( return new BN(
[...data] [...data]
.reverse() .reverse()
.map(i => `00${i.toString(16)}`.slice(-2)) .map((i) => `00${i.toString(16)}`.slice(-2))
.join(''), .join(""),
16, 16
); );
}; };
@ -89,7 +87,6 @@ export const uint128 = (property = "uint128"): unknown => {
return _encode(b, buffer, offset); return _encode(b, buffer, offset);
}; };
return layout; return layout;
}; };
@ -121,4 +118,4 @@ export const rustString = (property = "string"): unknown => {
}; };
return rsl; return rsl;
}; };

View File

@ -53,7 +53,7 @@ export function getTokenName(
shorten = true shorten = true
): string { ): string {
if (!mintAddress) { if (!mintAddress) {
return 'N/A'; return "N/A";
} }
const knownSymbol = map.get(mintAddress)?.tokenSymbol; const knownSymbol = map.get(mintAddress)?.tokenSymbol;
@ -66,9 +66,10 @@ export function getTokenName(
export function getTokenIcon( export function getTokenIcon(
map: KnownTokenMap, map: KnownTokenMap,
mintAddress?: string | PublicKey, mintAddress?: string | PublicKey
): string | undefined { ): string | undefined {
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58(); const address =
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58();
if (!address) { if (!address) {
return; return;
} }
@ -101,7 +102,7 @@ export function toLamports(
typeof account === "number" ? account : account.info.amount?.toNumber(); typeof account === "number" ? account : account.info.amount?.toNumber();
const precision = Math.pow(10, mint?.decimals || 0); const precision = Math.pow(10, mint?.decimals || 0);
return (amount * precision); return amount * precision;
} }
export function fromLamports( export function fromLamports(
@ -171,12 +172,12 @@ const numberFormater = new Intl.NumberFormat("en-US", {
export const formatNumber = { export const formatNumber = {
format: (val?: number) => { format: (val?: number) => {
if (!val) { if (!val) {
return '--'; return "--";
} }
return numberFormater.format(val); return numberFormater.format(val);
} },
} };
export const formatPct = new Intl.NumberFormat("en-US", { export const formatPct = new Intl.NumberFormat("en-US", {
style: "percent", style: "percent",

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useLendingReserves } from '../../hooks'; import { useLendingReserves } from "../../hooks";
import { BorrowItem } from './item'; import { BorrowItem } from "./item";
import './itemStyle.less'; import "./itemStyle.less";
export const BorrowView = () => { export const BorrowView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
@ -13,7 +13,9 @@ export const BorrowView = () => {
<div>APY</div> <div>APY</div>
<div>Action</div> <div>Action</div>
</div> </div>
{reserveAccounts.map(account => <BorrowItem reserve={account.info} address={account.pubkey} />)} {reserveAccounts.map((account) => (
<BorrowItem reserve={account.info} address={account.pubkey} />
))}
</div> </div>
); );
} };

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { useCollateralBalance, useTokenName } from '../../hooks'; import { useCollateralBalance, useTokenName } from "../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
@ -7,24 +7,34 @@ import { Button, Card } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
export const BorrowItem = (props: { reserve: LendingReserve, address: PublicKey }) => { export const BorrowItem = (props: {
reserve: LendingReserve;
address: PublicKey;
}) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
// TODO: calculate avilable amount... based on total owned collateral across all the reserves // TODO: calculate avilable amount... based on total owned collateral across all the reserves
const { balance: collateralBalance } = useCollateralBalance(props.reserve); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Link to={`/borrow/${props.address.toBase58()}`}> return (
<Card> <Link to={`/borrow/${props.address.toBase58()}`}>
<div className="borrow-item"> <Card>
<span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span> <div className="borrow-item">
<div>{formatNumber.format(collateralBalance)} {name}</div> <span style={{ display: "flex" }}>
<div>--</div> <TokenIcon mintAddress={props.reserve.liquidityMint} />
<div> {name}
<Button> </span>
<span>Borrow</span> <div>
</Button> {formatNumber.format(collateralBalance)} {name}
</div>
<div>--</div>
<div>
<Button>
<span>Borrow</span>
</Button>
</div>
</div> </div>
</div> </Card>
</Card> </Link>
</Link>; );
} };

View File

@ -1,10 +1,13 @@
import React, { } from "react"; import React from "react";
import { useLendingReserve } from '../../hooks'; import { useLendingReserve } from "../../hooks";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import './style.less'; import "./style.less";
import { BorrowInput } from '../../components/BorrowInput'; import { BorrowInput } from "../../components/BorrowInput";
import { SideReserveOverview, SideReserveOverviewMode } from '../../components/SideReserveOverview'; import {
SideReserveOverview,
SideReserveOverviewMode,
} from "../../components/SideReserveOverview";
export const BorrowReserveView = () => { export const BorrowReserveView = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -15,17 +18,21 @@ export const BorrowReserveView = () => {
return null; return null;
} }
return <div className="borrow-reserve"> return (
<div className="borrow-reserve-container"> <div className="borrow-reserve">
<BorrowInput <div className="borrow-reserve-container">
className="borrow-reserve-item borrow-reserve-item-left" <BorrowInput
reserve={reserve} className="borrow-reserve-item borrow-reserve-item-left"
address={lendingReserve.pubkey} /> reserve={reserve}
<SideReserveOverview address={lendingReserve.pubkey}
className="borrow-reserve-item borrow-reserve-item-right" />
reserve={reserve} <SideReserveOverview
address={lendingReserve.pubkey} className="borrow-reserve-item borrow-reserve-item-right"
mode={SideReserveOverviewMode.Borrow} /> reserve={reserve}
address={lendingReserve.pubkey}
mode={SideReserveOverviewMode.Borrow}
/>
</div>
</div> </div>
</div>; );
} };

View File

@ -1,11 +1,9 @@
import React from "react"; import React from "react";
export const DashboardView = () => { export const DashboardView = () => {
return (
return <div className="flexColumn"> <div className="flexColumn">
DASHBOARD: DASHBOARD: TODO: 1. Add deposits 2. Add obligations
TODO: </div>
1. Add deposits );
2. Add obligations };
</div>;
}

View File

@ -1 +1 @@
export * from './view'; export * from "./view";

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useLendingReserves } from '../../../hooks'; import { useLendingReserves } from "../../../hooks";
import { ReserveItem } from './item'; import { ReserveItem } from "./item";
import './itemStyle.less'; import "./itemStyle.less";
export const DepositView = () => { export const DepositView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
@ -14,7 +14,9 @@ export const DepositView = () => {
<div>APY</div> <div>APY</div>
<div>Action</div> <div>Action</div>
</div> </div>
{reserveAccounts.map(account => <ReserveItem reserve={account.info} address={account.pubkey} />)} {reserveAccounts.map((account) => (
<ReserveItem reserve={account.info} address={account.pubkey} />
))}
</div> </div>
); );
}; };

View File

@ -1,5 +1,9 @@
import React from "react"; import React from "react";
import { useCollateralBalance, useTokenName, useUserBalance } from '../../../hooks'; import {
useCollateralBalance,
useTokenName,
useUserBalance,
} from "../../../hooks";
import { LendingReserve } from "../../../models/lending"; import { LendingReserve } from "../../../models/lending";
import { TokenIcon } from "../../../components/TokenIcon"; import { TokenIcon } from "../../../components/TokenIcon";
import { formatNumber } from "../../../utils/utils"; import { formatNumber } from "../../../utils/utils";
@ -7,24 +11,36 @@ import { Button, Card } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
export const ReserveItem = (props: { reserve: LendingReserve, address: PublicKey }) => { export const ReserveItem = (props: {
reserve: LendingReserve;
address: PublicKey;
}) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useCollateralBalance(props.reserve); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Link to={`/deposit/${props.address.toBase58()}`}> return (
<Card> <Link to={`/deposit/${props.address.toBase58()}`}>
<div className="deposit-item"> <Card>
<span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span> <div className="deposit-item">
<div>{formatNumber.format(tokenBalance)} {name}</div> <span style={{ display: "flex" }}>
<div>{formatNumber.format(collateralBalance)} {name}</div> <TokenIcon mintAddress={props.reserve.liquidityMint} />
<div>--</div> {name}
<div> </span>
<Button> <div>
<span>Deposit</span> {formatNumber.format(tokenBalance)} {name}
</Button> </div>
<div>
{formatNumber.format(collateralBalance)} {name}
</div>
<div>--</div>
<div>
<Button>
<span>Deposit</span>
</Button>
</div>
</div> </div>
</div> </Card>
</Card> </Link>
</Link>; );
} };

View File

@ -1,11 +1,14 @@
import React, { } from "react"; import React from "react";
import { useLendingReserve } from '../../hooks'; import { useLendingReserve } from "../../hooks";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import './style.less'; import "./style.less";
import { DepositInput } from '../../components/DepositInput'; import { DepositInput } from "../../components/DepositInput";
import { DepositInfoLine } from '../../components/DepositInfoLine'; import { DepositInfoLine } from "../../components/DepositInfoLine";
import { SideReserveOverview, SideReserveOverviewMode } from '../../components/SideReserveOverview'; import {
SideReserveOverview,
SideReserveOverviewMode,
} from "../../components/SideReserveOverview";
export const DepositReserveView = () => { export const DepositReserveView = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -16,21 +19,26 @@ export const DepositReserveView = () => {
return null; return null;
} }
return <div className="deposit-reserve"> return (
<DepositInfoLine <div className="deposit-reserve">
<DepositInfoLine
className="deposit-reserve-item" className="deposit-reserve-item"
reserve={reserve} reserve={reserve}
address={lendingReserve.pubkey} />
<div className="deposit-reserve-container">
<DepositInput
className="deposit-reserve-item deposit-reserve-item-left"
reserve={reserve}
address={lendingReserve.pubkey} />
<SideReserveOverview
className="deposit-reserve-item deposit-reserve-item-right"
reserve={reserve}
address={lendingReserve.pubkey} address={lendingReserve.pubkey}
mode={SideReserveOverviewMode.Deposit} /> />
<div className="deposit-reserve-container">
<DepositInput
className="deposit-reserve-item deposit-reserve-item-left"
reserve={reserve}
address={lendingReserve.pubkey}
/>
<SideReserveOverview
className="deposit-reserve-item deposit-reserve-item-right"
reserve={reserve}
address={lendingReserve.pubkey}
mode={SideReserveOverviewMode.Deposit}
/>
</div>
</div> </div>
</div>; );
} };

View File

@ -7,30 +7,39 @@ import { LAMPORTS_PER_SOL } from "@solana/web3.js";
export const FaucetView = () => { export const FaucetView = () => {
const connection = useConnection(); const connection = useConnection();
const { wallet } = useWallet(); const { wallet } = useWallet();
const airdrop = useCallback(() => { const airdrop = useCallback(() => {
connection.requestAirdrop(wallet.publicKey, 1 * LAMPORTS_PER_SOL); connection.requestAirdrop(wallet.publicKey, 1 * LAMPORTS_PER_SOL);
}, [wallet, connection]) }, [wallet, connection]);
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: 'flex', display: "flex",
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
height: '100%', height: "100%",
}; };
return ( return (
<div className="flexColumn" style={{ flex: 1 }}> <div className="flexColumn" style={{ flex: 1 }}>
<Card title={'Faucet'} bodyStyle={bodyStyle} style={{ flex: 1 }}> <Card title={"Faucet"} bodyStyle={bodyStyle} style={{ flex: 1 }}>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around', alignItems: 'center' }}> <div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignItems: "center",
}}
>
<div className="deposit-input-title" style={{ margin: 10 }}> <div className="deposit-input-title" style={{ margin: 10 }}>
This Faucet will help you fund your accounts outside of Solana main network. This Faucet will help you fund your accounts outside of Solana main
network.
</div> </div>
<Button type="primary" onClick={airdrop} >Give me SOL</Button> <Button type="primary" onClick={airdrop}>
Give me SOL
</Button>
</div> </div>
</Card> </Card>
</div> </div>
); );
} };

View File

@ -1,21 +1,25 @@
import React from "react"; import React from "react";
import { useLendingReserves } from '../../hooks'; import { useLendingReserves } from "../../hooks";
import { LendingReserveItem } from "./item"; import { LendingReserveItem } from "./item";
import './itemStyle.less'; import "./itemStyle.less";
export const HomeView = () => { export const HomeView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
// TODO: add total Liquidity amount ... // TODO: add total Liquidity amount ...
return <div className="flexColumn"> return (
<div className="home-item home-header"> <div className="flexColumn">
<div>Asset</div> <div className="home-item home-header">
<div>Market Size</div> <div>Asset</div>
<div>Total Borrowed</div> <div>Market Size</div>
<div>Deposit APY</div> <div>Total Borrowed</div>
<div>Borrow APY</div> <div>Deposit APY</div>
<div>Borrow APY</div>
</div>
{reserveAccounts.map((account) => (
<LendingReserveItem reserve={account.info} address={account.pubkey} />
))}
</div> </div>
{reserveAccounts.map(account => <LendingReserveItem reserve={account.info} address={account.pubkey} />)} );
</div>; };
}

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { useTokenName } from '../../hooks'; import { useTokenName } from "../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber, fromLamports } from "../../utils/utils"; import { formatNumber, fromLamports } from "../../utils/utils";
@ -8,26 +8,40 @@ import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { useMint } from "../../contexts/accounts"; import { useMint } from "../../contexts/accounts";
export const LendingReserveItem = (props: { reserve: LendingReserve, address: PublicKey }) => { export const LendingReserveItem = (props: {
reserve: LendingReserve;
address: PublicKey;
}) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
const liquidityMint = useMint(props.reserve.liquidityMint); const liquidityMint = useMint(props.reserve.liquidityMint);
const totalLiquidity = fromLamports(props.reserve.totalLiquidity.toNumber(), liquidityMint); const totalLiquidity = fromLamports(
props.reserve.totalLiquidity.toNumber(),
liquidityMint
);
const totalBorrows = props.reserve.totalBorrows.toString(); const totalBorrows = props.reserve.totalBorrows.toString();
console.log(liquidityMint); console.log(liquidityMint);
return <Link to={`/reserve/${props.address.toBase58()}`}> return (
<Card> <Link to={`/reserve/${props.address.toBase58()}`}>
<div className="home-item"> <Card>
<span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span> <div className="home-item">
<div>{formatNumber.format(totalLiquidity)} {name}</div> <span style={{ display: "flex" }}>
<div>{totalBorrows} {name}</div> <TokenIcon mintAddress={props.reserve.liquidityMint} />
<div>--</div> {name}
<div>--</div> </span>
</div> <div>
{formatNumber.format(totalLiquidity)} {name}
</Card> </div>
</Link>; <div>
} {totalBorrows} {name}
</div>
<div>--</div>
<div>--</div>
</div>
</Card>
</Link>
);
};

View File

@ -1,9 +1,9 @@
export { HomeView } from './home'; export { HomeView } from "./home";
export { BorrowView } from './borrow'; export { BorrowView } from "./borrow";
export { BorrowReserveView } from './borrowReserve'; export { BorrowReserveView } from "./borrowReserve";
export { DashboardView } from './dashboard'; export { DashboardView } from "./dashboard";
export { DepositView } from './deposit'; export { DepositView } from "./deposit";
export { DepositReserveView } from './depositReserve'; export { DepositReserveView } from "./depositReserve";
export { ReserveView } from './reserve'; export { ReserveView } from "./reserve";
export { WithdrawView } from './withdraw'; export { WithdrawView } from "./withdraw";
export { FaucetView } from './faucet'; export { FaucetView } from "./faucet";

View File

@ -1,10 +1,10 @@
import React, { } from "react"; import React from "react";
import { useLendingReserve } from './../../hooks'; import { useLendingReserve } from "./../../hooks";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import './style.less'; import "./style.less";
import { UserLendingCard } from './../../components/UserLendingCard'; import { UserLendingCard } from "./../../components/UserLendingCard";
import { ReserveStatus } from './../../components/ReserveStatus'; import { ReserveStatus } from "./../../components/ReserveStatus";
export const ReserveView = () => { export const ReserveView = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -15,16 +15,20 @@ export const ReserveView = () => {
return null; return null;
} }
return <div className="reserve-overview"> return (
<div className="reserve-overview-container"> <div className="reserve-overview">
<ReserveStatus <div className="reserve-overview-container">
className="reserve-overview-item reserve-overview-item-left" <ReserveStatus
reserve={reserve} className="reserve-overview-item reserve-overview-item-left"
address={lendingReserve.pubkey} /> reserve={reserve}
<UserLendingCard address={lendingReserve.pubkey}
className="reserve-overview-item reserve-overview-item-right" />
reserve={reserve} <UserLendingCard
address={lendingReserve.pubkey} /> className="reserve-overview-item reserve-overview-item-right"
reserve={reserve}
address={lendingReserve.pubkey}
/>
</div>
</div> </div>
</div>; );
} };

View File

@ -1,11 +1,14 @@
import React, { } from "react"; import React from "react";
import { useLendingReserve } from '../../hooks'; import { useLendingReserve } from "../../hooks";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import './style.less'; import "./style.less";
import { WithdrawInput } from '../../components/WithdrawInput'; import { WithdrawInput } from "../../components/WithdrawInput";
import { DepositInfoLine } from '../../components/DepositInfoLine'; import { DepositInfoLine } from "../../components/DepositInfoLine";
import { SideReserveOverview, SideReserveOverviewMode } from '../../components/SideReserveOverview'; import {
SideReserveOverview,
SideReserveOverviewMode,
} from "../../components/SideReserveOverview";
export const WithdrawView = () => { export const WithdrawView = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -16,21 +19,26 @@ export const WithdrawView = () => {
return null; return null;
} }
return <div className="deposit-reserve"> return (
<DepositInfoLine <div className="deposit-reserve">
<DepositInfoLine
className="deposit-reserve-item" className="deposit-reserve-item"
reserve={reserve} reserve={reserve}
address={lendingReserve.pubkey} />
<div className="deposit-reserve-container">
<WithdrawInput
className="deposit-reserve-item deposit-reserve-item-left"
reserve={reserve}
address={lendingReserve.pubkey} />
<SideReserveOverview
className="deposit-reserve-item deposit-reserve-item-right"
reserve={reserve}
address={lendingReserve.pubkey} address={lendingReserve.pubkey}
mode={SideReserveOverviewMode.Deposit} /> />
<div className="deposit-reserve-container">
<WithdrawInput
className="deposit-reserve-item deposit-reserve-item-left"
reserve={reserve}
address={lendingReserve.pubkey}
/>
<SideReserveOverview
className="deposit-reserve-item deposit-reserve-item-right"
reserve={reserve}
address={lendingReserve.pubkey}
mode={SideReserveOverviewMode.Deposit}
/>
</div>
</div> </div>
</div>; );
} };