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 { Routes } from "./routes";
function App() {
return (
<Routes />
);
return <Routes />;
}
export default App;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,45 @@
import React, { } from "react";
import { useTokenName, useUserBalance, useCollateralBalance } from './../../hooks';
import React from "react";
import {
useTokenName,
useUserBalance,
useCollateralBalance,
} from "./../../hooks";
import { LendingReserve } from "../../models/lending";
import { formatNumber } from "../../utils/utils";
import { Card } from "antd";
import './style.less';
import "./style.less";
import { PublicKey } from "@solana/web3.js";
export const DepositInfoLine = (props: {
className?: string,
reserve: LendingReserve,
address: PublicKey }) => {
export const DepositInfoLine = (props: {
className?: string;
reserve: LendingReserve;
address: PublicKey;
}) => {
const name = useTokenName(props.reserve.liquidityMint);
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Card className={props.className} bodyStyle={{ display: 'flex', justifyContent: 'space-around', }} >
<div className="deposit-info-line-item ">
<div>Your balance in Oyster</div>
<div>{formatNumber.format(collateralBalance)} {name}</div>
</div>
<div className="deposit-info-line-item ">
<div>Your wallet balance</div>
<div>{formatNumber.format(tokenBalance)} {name}</div>
</div>
<div className="deposit-info-line-item ">
<div>Health factor</div>
<div>--</div>
</div>
</Card>
}
return (
<Card
className={props.className}
bodyStyle={{ display: "flex", justifyContent: "space-around" }}
>
<div className="deposit-info-line-item ">
<div>Your balance in Oyster</div>
<div>
{formatNumber.format(collateralBalance)} {name}
</div>
</div>
<div className="deposit-info-line-item ">
<div>Your wallet balance</div>
<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 { useTokenName, useUserBalance } from '../../hooks';
import { useTokenName, useUserBalance } from "../../hooks";
import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon";
import { Button, Card } from "antd";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
import { deposit } from '../../actions/deposit';
import { deposit } from "../../actions/deposit";
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 { wallet } = useWallet();
const [value, setValue] = useState('');
const [value, setValue] = useState("");
const reserve = props.reserve;
const address = props.address;
@ -29,42 +33,57 @@ export const DepositInput = (props: { className?: string, reserve: LendingReserv
reserve,
address,
connection,
wallet);
}, [value, reserve, fromAccounts, address]);
wallet
);
}, [connection, wallet, value, reserve, fromAccounts, address]);
const bodyStyle: React.CSSProperties = {
display: 'flex',
const bodyStyle: React.CSSProperties = {
display: "flex",
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
justifyContent: "center",
alignItems: "center",
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' }}>
<div className="deposit-input-title">
How much would you like to deposit?
<Button
type="primary"
onClick={onDeposit}
disabled={fromAccounts.length === 0}
>
Deposit
</Button>
</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>
<Button type="primary" onClick={onDeposit} disabled={fromAccounts.length === 0}>Deposit</Button>
</div>
</Card >;
}
</Card>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,77 +1,110 @@
import React, { useCallback, useState } from "react";
import { useCollateralBalance, useTokenName, useUserBalance } from '../../hooks';
import {
useCollateralBalance,
useTokenName,
useUserBalance,
} from "../../hooks";
import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon";
import { Button, Card } from "antd";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
import { withdraw } from '../../actions';
import { withdraw } from "../../actions";
import { PublicKey } from "@solana/web3.js";
import './style.less';
import { useMint } from "../../contexts/accounts";
import "./style.less";
export const WithdrawInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => {
export const WithdrawInput = (props: {
className?: string;
reserve: LendingReserve;
address: PublicKey;
}) => {
const connection = useConnection();
const { wallet } = useWallet();
const [value, setValue] = useState('');
const [value, setValue] = useState("");
const reserve = props.reserve;
const address = props.address;
const liquidityMint = useMint(reserve?.liquidityMint);
const name = useTokenName(reserve?.liquidityMint);
const { balanceLamports: collateralBalanceLamports, accounts: fromAccounts } = useUserBalance(reserve?.collateralMint);
const {
balance: collateralBalanceInLiquidity
} = useCollateralBalance(reserve);
const {
balanceLamports: collateralBalanceLamports,
accounts: fromAccounts,
} = useUserBalance(reserve?.collateralMint);
const { balance: collateralBalanceInLiquidity } = useCollateralBalance(
reserve
);
const onWithdraw = useCallback(() => {
withdraw(
fromAccounts[0],
Math.ceil(
collateralBalanceLamports *
(parseFloat(value) / collateralBalanceInLiquidity)
collateralBalanceLamports *
(parseFloat(value) / collateralBalanceInLiquidity)
),
reserve,
address,
connection,
wallet);
}, [value, reserve, fromAccounts, address, liquidityMint]);
wallet
);
}, [
connection,
wallet,
collateralBalanceLamports,
collateralBalanceInLiquidity,
value,
reserve,
fromAccounts,
address,
]);
const bodyStyle: React.CSSProperties = {
display: 'flex',
const bodyStyle: React.CSSProperties = {
display: "flex",
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
justifyContent: "center",
alignItems: "center",
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' }}>
<div className="withdraw-input-title">
How much would you like to withdraw?
<Button
type="primary"
onClick={onWithdraw}
disabled={fromAccounts.length === 0}
>
Withdraw
</Button>
</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>
<Button type="primary" onClick={onWithdraw} disabled={fromAccounts.length === 0}>Withdraw</Button>
</div>
</Card >;
}
</Card>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,18 +9,20 @@ export function useUserBalance(mint?: PublicKey) {
const mintInfo = useMint(mint);
const accounts = useMemo(() => {
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());
}, [userAccounts, mint]);
const balanceLamports = useMemo(() => {
return accounts
.reduce((res, item) => res += item.info.amount.toNumber(), 0);
},[accounts]);
return accounts.reduce(
(res, item) => (res += item.info.amount.toNumber()),
0
);
}, [accounts]);
return {
balance: fromLamports(balanceLamports, mintInfo),
balanceLamports,
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;
};
const DEFAULT_DEX_ID = new PublicKey('EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o');
const DEFAULT_DEX_ID = new PublicKey(
"EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o"
);
export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) => {
const market = MARKETS.find(m => m.address.equals(pubkey));
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID)
.decode(acc.data);
export const DexMarketParser = (
pubkey: PublicKey,
acc: AccountInfo<Buffer>
) => {
const market = MARKETS.find((m) => m.address.equals(pubkey));
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(
acc.data
);
const details = {
pubkey,
@ -41,4 +47,4 @@ export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) =>
cache.registerParser(details.info.asks, OrderBookParser);
return details;
}
};

View File

@ -1,2 +1,2 @@
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 { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
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
/// is calculated by market price. The debt obligation is tokenized.
@ -49,7 +49,7 @@ export const borrowInstruction = (
dexMarket: PublicKey,
dexOrderBookSide: PublicKey,
memory: PublicKey,
memory: PublicKey
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
@ -59,7 +59,7 @@ export const borrowInstruction = (
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.BorrowReserveLiquidity,
instruction: LendingInstruction.BorrowReserveLiquidity,
collateralAmount: new BN(collateralAmount),
},
data
@ -69,9 +69,17 @@ export const borrowInstruction = (
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, 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: borrowReserveLiquiditySupply, isSigner: false, isWritable: false },
{
pubkey: borrowReserveLiquiditySupply,
isSigner: false,
isWritable: false,
},
{ pubkey: obligation, isSigner: false, isWritable: true },
{ pubkey: obligationMint, isSigner: false, isWritable: true },
{ pubkey: obligationTokenOutput, isSigner: false, isWritable: true },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,9 @@
import React from "react";
export const DashboardView = () => {
return <div className="flexColumn">
DASHBOARD:
TODO:
1. Add deposits
2. Add obligations
</div>;
}
return (
<div className="flexColumn">
DASHBOARD: TODO: 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 { useLendingReserves } from '../../../hooks';
import { ReserveItem } from './item';
import './itemStyle.less';
import { useLendingReserves } from "../../../hooks";
import { ReserveItem } from "./item";
import "./itemStyle.less";
export const DepositView = () => {
const { reserveAccounts } = useLendingReserves();
@ -14,7 +14,9 @@ export const DepositView = () => {
<div>APY</div>
<div>Action</div>
</div>
{reserveAccounts.map(account => <ReserveItem reserve={account.info} address={account.pubkey} />)}
{reserveAccounts.map((account) => (
<ReserveItem reserve={account.info} address={account.pubkey} />
))}
</div>
);
};
};

View File

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

View File

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

View File

@ -7,30 +7,39 @@ import { LAMPORTS_PER_SOL } from "@solana/web3.js";
export const FaucetView = () => {
const connection = useConnection();
const { wallet } = useWallet();
const airdrop = useCallback(() => {
connection.requestAirdrop(wallet.publicKey, 1 * LAMPORTS_PER_SOL);
}, [wallet, connection])
}, [wallet, connection]);
const bodyStyle: React.CSSProperties = {
display: 'flex',
display: "flex",
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
justifyContent: "center",
alignItems: "center",
height: "100%",
};
return (
<div className="flexColumn" style={{ flex: 1 }}>
<Card title={'Faucet'} bodyStyle={bodyStyle} style={{ flex: 1 }}>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around', alignItems: 'center' }}>
<Card title={"Faucet"} bodyStyle={bodyStyle} style={{ flex: 1 }}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignItems: "center",
}}
>
<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>
<Button type="primary" onClick={airdrop} >Give me SOL</Button>
<Button type="primary" onClick={airdrop}>
Give me SOL
</Button>
</div>
</Card>
</div>
);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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