diff --git a/src/actions/borrow.tsx b/src/actions/borrow.tsx new file mode 100644 index 0000000..b0a4477 --- /dev/null +++ b/src/actions/borrow.tsx @@ -0,0 +1,130 @@ +import { + Account, + AccountInfo, + Connection, + PublicKey, + sendAndConfirmRawTransaction, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from "@solana/web3.js"; +import BN from "bn.js"; +import * as BufferLayout from "buffer-layout"; +import { sendTransaction } from "../contexts/connection"; +import { notify } from "../utils/notifications"; +import * as Layout from "./../utils/layout"; +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 { cache, GenericAccountParser, MintParser, ParsedAccount } from "../contexts/accounts"; +import { TokenAccount } from "../models"; +import { isConstructorDeclaration } from "typescript"; +import { LendingMarketParser } from "../models/lending"; +import { sign } from "crypto"; +import { fromLamports, toLamports } from "../utils/utils"; + +export const borrow = async ( + from: TokenAccount, + amount: number, + reserve: LendingReserve, + reserveAddress: PublicKey, + connection: Connection, + wallet: any) => { + + notify({ + message: "Borrowing funds...", + description: "Please review transactions to approve.", + type: "warn", + }); + + const isInitalized = true; // TODO: finish reserve init + + // user from account + const signers: Account[] = []; + const instructions: TransactionInstruction[] = []; + const cleanupInstructions: TransactionInstruction[] = []; + + const accountRentExempt = await connection.getMinimumBalanceForRentExemption( + AccountLayout.span + ); + + const [authority] = await PublicKey.findProgramAddress( + [reserve.lendingMarket.toBuffer()], // which account should be authority + LENDING_PROGRAM_ID + ); + + const mint = (await cache.query(connection, reserve.liquidityMint, MintParser)) as ParsedAccount; + const amountLamports = toLamports(amount, mint?.info); + + const fromAccount = ensureSplAccount( + instructions, + cleanupInstructions, + from, + wallet.publicKey, + amountLamports + accountRentExempt, + signers + ); + + // create approval for transfer transactions + instructions.push( + Token.createApproveInstruction( + TOKEN_PROGRAM_ID, + fromAccount, + authority, + wallet.publicKey, + [], + amountLamports, + ) + ); + + let toAccount: PublicKey; + if (isInitalized) { + // get destination account + toAccount = await findOrCreateAccountByMint( + wallet.publicKey, + wallet.publicKey, + instructions, + cleanupInstructions, + accountRentExempt, + reserve.collateralMint, + signers + ); + } else { + toAccount = createUninitializedAccount( + instructions, + wallet.publicKey, + accountRentExempt, + signers, + ); + } + + // deposit + instructions.push( + depositInstruction( + amountLamports, + fromAccount, + toAccount, + authority, + reserveAddress, + reserve.liquiditySupply, + reserve.collateralMint, + ) + ); + try { + let tx = await sendTransaction( + connection, + wallet, + instructions.concat(cleanupInstructions), + signers, + true + ); + + notify({ + message: "Funds borrowed.", + type: "success", + description: `Transaction - ${tx}`, + }); + } catch { + // TODO: + } +} diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 0000000..1f87f56 --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,3 @@ +export { borrow } from './borrow'; +export { deposit } from './deposit'; +export * from './account'; diff --git a/src/components/BorrowInput/index.tsx b/src/components/BorrowInput/index.tsx new file mode 100644 index 0000000..c063191 --- /dev/null +++ b/src/components/BorrowInput/index.tsx @@ -0,0 +1,74 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from '../../hooks'; +import { LendingReserve, LendingReserveParser } from "../../models/lending"; +import { TokenIcon } from "../TokenIcon"; +import { formatNumber } from "../../utils/utils"; +import { Button, Card } from "antd"; +import { useParams } from "react-router-dom"; +import { cache, useAccount } from "../../contexts/accounts"; +import { NumericInput } from "../Input/numeric"; +import { useConnection } from "../../contexts/connection"; +import { useWallet } from "../../contexts/wallet"; +import { borrow } from '../../actions'; +import { PublicKey } from "@solana/web3.js"; +import './style.less'; + +export const BorrowInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { + const connection = useConnection(); + const { wallet } = useWallet(); + const { id } = useParams<{ id: string }>(); + const [value, setValue] = useState(''); + + const reserve = props.reserve; + const address = props.address; + + const name = useTokenName(reserve?.liquidityMint); + const { balance: tokenBalance, accounts: fromAccounts } = useUserBalance(reserve?.liquidityMint); + // const collateralBalance = useUserBalance(reserve?.collateralMint); + + const onBorrow = useCallback(() => { + borrow( + fromAccounts[0], + parseFloat(value), + reserve, + address, + connection, + wallet); + }, [value, reserve, fromAccounts, address]); + + const bodyStyle: React.CSSProperties = { + display: 'flex', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + height: '100%', + }; + + return + +
+
+ How much would you like to borrow? +
+
+ + { + setValue(val); + }} + autoFocus={true} + style={{ + fontSize: 20, + boxShadow: "none", + borderColor: "transparent", + outline: "transpaernt", + }} + placeholder="0.00" + /> +
{name}
+
+ + +
+
; +} \ No newline at end of file diff --git a/src/components/BorrowInput/style.less b/src/components/BorrowInput/style.less new file mode 100644 index 0000000..c4d8776 --- /dev/null +++ b/src/components/BorrowInput/style.less @@ -0,0 +1,3 @@ +.borrow-input-title { + font-size: 1.05rem; +} \ No newline at end of file diff --git a/src/components/DepositAdd/style.less b/src/components/DepositAdd/style.less deleted file mode 100644 index 2d7bc95..0000000 --- a/src/components/DepositAdd/style.less +++ /dev/null @@ -1,3 +0,0 @@ -.deposit-add-title { - font-size: 1.05rem; -} \ No newline at end of file diff --git a/src/components/DepositAdd/index.tsx b/src/components/DepositInput/index.tsx similarity index 90% rename from src/components/DepositAdd/index.tsx rename to src/components/DepositInput/index.tsx index 6a31550..dd663fd 100644 --- a/src/components/DepositAdd/index.tsx +++ b/src/components/DepositInput/index.tsx @@ -1,19 +1,19 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks'; +import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from '../../hooks'; import { LendingReserve, LendingReserveParser } from "../../models/lending"; -import { TokenIcon } from "../../components/TokenIcon"; +import { TokenIcon } from "../TokenIcon"; import { formatNumber } from "../../utils/utils"; import { Button, Card } from "antd"; import { useParams } from "react-router-dom"; import { cache, useAccount } from "../../contexts/accounts"; -import { NumericInput } from "../../components/Input/numeric"; +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'; -export const DepositAdd = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { +export const DepositInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => { const connection = useConnection(); const { wallet } = useWallet(); const { id } = useParams<{ id: string }>(); @@ -66,7 +66,7 @@ export const DepositAdd = (props: { className?: string, reserve: LendingReserve, return
-
+
How much would you like to deposit?
diff --git a/src/components/DepositInput/style.less b/src/components/DepositInput/style.less new file mode 100644 index 0000000..4955e41 --- /dev/null +++ b/src/components/DepositInput/style.less @@ -0,0 +1,3 @@ +.deposit-input-title { + font-size: 1.05rem; +} \ No newline at end of file diff --git a/src/components/UserLendingCard/index.tsx b/src/components/UserLendingCard/index.tsx index 1764f86..dcb7896 100644 --- a/src/components/UserLendingCard/index.tsx +++ b/src/components/UserLendingCard/index.tsx @@ -101,7 +101,7 @@ export const UserLendingCard = (props: { - + diff --git a/src/contexts/lending.tsx b/src/contexts/lending.tsx index 9837429..ed3054b 100644 --- a/src/contexts/lending.tsx +++ b/src/contexts/lending.tsx @@ -50,8 +50,6 @@ export const useLending = () => { .map(processAccount) .filter(item => item !== undefined); - console.log(accounts); - const toQuery = [ ...accounts.filter(acc => (acc?.info as LendingReserve).lendingMarket !== undefined) .map(acc => [ diff --git a/src/routes.tsx b/src/routes.tsx index 6c71eeb..6e0ce8d 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -10,10 +10,11 @@ import { AppLayout } from "./components/Layout"; import { HomeView, DepositView, - DepositAddView, + DepositReserveView, BorrowView, ReserveView, DashboardView, + BorrowReserveView, } from './views'; export function Routes() { @@ -31,8 +32,9 @@ export function Routes() { } /> } /> } /> - } /> + } /> } /> + } /> diff --git a/src/views/borrowReserve/index.tsx b/src/views/borrowReserve/index.tsx new file mode 100644 index 0000000..cb06950 --- /dev/null +++ b/src/views/borrowReserve/index.tsx @@ -0,0 +1,42 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from '../../hooks'; +import { LendingReserve, LendingReserveParser } from "../../models/lending"; +import { TokenIcon } from "../../components/TokenIcon"; +import { formatNumber } from "../../utils/utils"; +import { Button, Card } from "antd"; +import { useParams } from "react-router-dom"; +import { cache, useAccount } from "../../contexts/accounts"; +import { NumericInput } from "../../components/Input/numeric"; +import { useConnection } from "../../contexts/connection"; +import { useWallet } from "../../contexts/wallet"; +import { deposit } from '../../actions/deposit'; +import './style.less'; + +import { BorrowInput } from '../../components/BorrowInput'; +import { SideReserveOverview, SideReserveOverviewMode } from '../../components/SideReserveOverview'; + +export const BorrowReserveView = () => { + const connection = useConnection(); + const { wallet } = useWallet(); + const { id } = useParams<{ id: string }>(); + const lendingReserve = useLendingReserve(id); + const reserve = lendingReserve?.info; + + if (!reserve || !lendingReserve) { + return null; + } + + return
+
+ + +
+
; +} \ No newline at end of file diff --git a/src/views/deposit/add/style.less b/src/views/borrowReserve/style.less similarity index 60% rename from src/views/deposit/add/style.less rename to src/views/borrowReserve/style.less index 9bd6390..92c08bc 100644 --- a/src/views/deposit/add/style.less +++ b/src/views/borrowReserve/style.less @@ -1,30 +1,30 @@ -.deposit-add { +.borrow-reserve { display: flex; flex-direction: column; flex: 1; } -.deposit-add-item { +.borrow-reserve-item { margin: 4px; } -.deposit-add-container { +.borrow-reserve-container { display: flex; flex-wrap: wrap; flex: 1; } -.deposit-add-item-left { +.borrow-reserve-item-left { flex: 60%; } -.deposit-add-item-right { +.borrow-reserve-item-right { flex: 30%; } /* Responsive layout - makes a one column layout instead of a two-column layout */ @media (max-width: 600px) { - .deposit-add-item-right, .deposit-add-item-left { + .borrow-reserve-item-right, .borrow-reserve-item-left { flex: 100%; } } \ No newline at end of file diff --git a/src/views/deposit/add/index.tsx b/src/views/deposit/add/index.tsx deleted file mode 100644 index f2a34e6..0000000 --- a/src/views/deposit/add/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../../hooks'; -import { LendingReserve, LendingReserveParser } from "../../../models/lending"; -import { TokenIcon } from "../../../components/TokenIcon"; -import { formatNumber } from "../../../utils/utils"; -import { Button, Card } from "antd"; -import { useParams } from "react-router-dom"; -import { cache, useAccount } from "../../../contexts/accounts"; -import { NumericInput } from "../../../components/Input/numeric"; -import { useConnection } from "../../../contexts/connection"; -import { useWallet } from "../../../contexts/wallet"; -import { deposit } from './../../../actions/deposit'; -import './style.less'; - -import { DepositAdd } from './../../../components/DepositAdd'; -import { DepositInfoLine } from './../../../components/DepositInfoLine'; -import { SideReserveOverview, SideReserveOverviewMode } from './../../../components/SideReserveOverview'; - -export const DepositAddView = () => { - const connection = useConnection(); - const { wallet } = useWallet(); - const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); - const reserve = lendingReserve?.info; - - if (!reserve || !lendingReserve) { - return null; - } - - return
- -
- - -
-
; -} \ No newline at end of file diff --git a/src/views/deposit/index.tsx b/src/views/deposit/index.tsx index 99eb0a2..68fddf6 100644 --- a/src/views/deposit/index.tsx +++ b/src/views/deposit/index.tsx @@ -1,2 +1 @@ -export * from './view'; -export * from './add'; \ No newline at end of file +export * from './view'; \ No newline at end of file diff --git a/src/views/depositReserve/index.tsx b/src/views/depositReserve/index.tsx new file mode 100644 index 0000000..403baa2 --- /dev/null +++ b/src/views/depositReserve/index.tsx @@ -0,0 +1,47 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from '../../hooks'; +import { LendingReserve, LendingReserveParser } from "../../models/lending"; +import { TokenIcon } from "../../components/TokenIcon"; +import { formatNumber } from "../../utils/utils"; +import { Button, Card } from "antd"; +import { useParams } from "react-router-dom"; +import { cache, useAccount } from "../../contexts/accounts"; +import { NumericInput } from "../../components/Input/numeric"; +import { useConnection } from "../../contexts/connection"; +import { useWallet } from "../../contexts/wallet"; +import { deposit } from '../../actions/deposit'; +import './style.less'; + +import { DepositInput } from '../../components/DepositInput'; +import { DepositInfoLine } from '../../components/DepositInfoLine'; +import { SideReserveOverview, SideReserveOverviewMode } from '../../components/SideReserveOverview'; + +export const DepositReserveView = () => { + const connection = useConnection(); + const { wallet } = useWallet(); + const { id } = useParams<{ id: string }>(); + const lendingReserve = useLendingReserve(id); + const reserve = lendingReserve?.info; + + if (!reserve || !lendingReserve) { + return null; + } + + return
+ +
+ + +
+
; +} \ No newline at end of file diff --git a/src/views/depositReserve/style.less b/src/views/depositReserve/style.less new file mode 100644 index 0000000..a3ed2a9 --- /dev/null +++ b/src/views/depositReserve/style.less @@ -0,0 +1,30 @@ +.deposit-reserve { + display: flex; + flex-direction: column; + flex: 1; +} + +.deposit-reserve-item { + margin: 4px; +} + +.deposit-reserve-container { + display: flex; + flex-wrap: wrap; + flex: 1; +} + +.deposit-reserve-item-left { + flex: 60%; +} + +.deposit-reserve-item-right { + flex: 30%; +} + +/* Responsive layout - makes a one column layout instead of a two-column layout */ +@media (max-width: 600px) { + .deposit-reserve-item-right, .deposit-reserve-item-left { + flex: 100%; + } +} \ No newline at end of file diff --git a/src/views/index.tsx b/src/views/index.tsx index b0e190c..fad48ab 100644 --- a/src/views/index.tsx +++ b/src/views/index.tsx @@ -1,5 +1,7 @@ export { HomeView } from './home'; export { BorrowView } from './borrow'; +export { BorrowReserveView } from './borrowReserve'; export { DashboardView } from './dashboard'; -export { DepositView, DepositAddView } from './deposit'; +export { DepositView } from './deposit'; +export { DepositReserveView } from './depositReserve'; export { ReserveView } from './reserve'; diff --git a/src/views/reserve/index.tsx b/src/views/reserve/index.tsx index 1f9b118..41ed7ed 100644 --- a/src/views/reserve/index.tsx +++ b/src/views/reserve/index.tsx @@ -12,7 +12,7 @@ import { useWallet } from "../../contexts/wallet"; import { deposit } from './../../actions/deposit'; import './style.less'; -import { DepositAdd } from './../../components/DepositAdd'; +import { DepositInput } from '../../components/DepositInput'; import { UserLendingCard } from './../../components/UserLendingCard'; import { ReserveStatus } from './../../components/ReserveStatus';