From 26418b97c073dd1b90f8da75b114f23a70cafc39 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 13 Apr 2021 14:08:17 -0500 Subject: [PATCH 01/58] make typeRoots consistent --- packages/bridge-sdk/tsconfig.json | 2 +- packages/bridge/tsconfig.json | 2 +- packages/common/tsconfig.json | 2 +- packages/lending/tsconfig.json | 2 +- packages/proposals/tsconfig.json | 2 +- packages/wallet-base/tsconfig.json | 2 +- packages/wallet-ledger/tsconfig.json | 2 +- tsconfig.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/bridge-sdk/tsconfig.json b/packages/bridge-sdk/tsconfig.json index 3f83e731..7420e1f7 100644 --- a/packages/bridge-sdk/tsconfig.json +++ b/packages/bridge-sdk/tsconfig.json @@ -16,7 +16,7 @@ "downlevelIteration": true, "noEmit": true, "jsx": "react", - "typeRoots": ["../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src"] } diff --git a/packages/bridge/tsconfig.json b/packages/bridge/tsconfig.json index 3f83e731..7420e1f7 100644 --- a/packages/bridge/tsconfig.json +++ b/packages/bridge/tsconfig.json @@ -16,7 +16,7 @@ "downlevelIteration": true, "noEmit": true, "jsx": "react", - "typeRoots": ["../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src"] } diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index bf84f446..d8b05952 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -16,7 +16,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "typeRoots": ["../../types/", "../../node_modules/@types", "../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src/**/*", "src/config/tokens.json"], "exclude": ["src/**/*.test.ts", "**/node_modules"] diff --git a/packages/lending/tsconfig.json b/packages/lending/tsconfig.json index 3f83e731..7420e1f7 100644 --- a/packages/lending/tsconfig.json +++ b/packages/lending/tsconfig.json @@ -16,7 +16,7 @@ "downlevelIteration": true, "noEmit": true, "jsx": "react", - "typeRoots": ["../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src"] } diff --git a/packages/proposals/tsconfig.json b/packages/proposals/tsconfig.json index 3f83e731..7420e1f7 100644 --- a/packages/proposals/tsconfig.json +++ b/packages/proposals/tsconfig.json @@ -16,7 +16,7 @@ "downlevelIteration": true, "noEmit": true, "jsx": "react", - "typeRoots": ["../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src"] } diff --git a/packages/wallet-base/tsconfig.json b/packages/wallet-base/tsconfig.json index bf84f446..d8b05952 100644 --- a/packages/wallet-base/tsconfig.json +++ b/packages/wallet-base/tsconfig.json @@ -16,7 +16,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "typeRoots": ["../../types/", "../../node_modules/@types", "../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src/**/*", "src/config/tokens.json"], "exclude": ["src/**/*.test.ts", "**/node_modules"] diff --git a/packages/wallet-ledger/tsconfig.json b/packages/wallet-ledger/tsconfig.json index bf84f446..d8b05952 100644 --- a/packages/wallet-ledger/tsconfig.json +++ b/packages/wallet-ledger/tsconfig.json @@ -16,7 +16,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "typeRoots": ["../../types/", "../../node_modules/@types", "../../types"] + "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, "include": ["src/**/*", "src/config/tokens.json"], "exclude": ["src/**/*.test.ts", "**/node_modules"] diff --git a/tsconfig.json b/tsconfig.json index 0adfb318..e9dd4441 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": true, - "typeRoots": ["types/", "node_modules/@types"] + "typeRoots": ["types", "node_modules/@types"] }, "include": ["src/**/*"] } From 0a048501baa90132105e7c7501db4fa0a2ef6cad Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 13 Apr 2021 14:08:41 -0500 Subject: [PATCH 02/58] config/tokens.json doesn't exist --- packages/common/tsconfig.json | 2 +- packages/wallet-base/tsconfig.json | 2 +- packages/wallet-ledger/tsconfig.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index d8b05952..4d65d0b5 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -18,6 +18,6 @@ "forceConsistentCasingInFileNames": true, "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, - "include": ["src/**/*", "src/config/tokens.json"], + "include": ["src/**/*"], "exclude": ["src/**/*.test.ts", "**/node_modules"] } diff --git a/packages/wallet-base/tsconfig.json b/packages/wallet-base/tsconfig.json index d8b05952..4d65d0b5 100644 --- a/packages/wallet-base/tsconfig.json +++ b/packages/wallet-base/tsconfig.json @@ -18,6 +18,6 @@ "forceConsistentCasingInFileNames": true, "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, - "include": ["src/**/*", "src/config/tokens.json"], + "include": ["src/**/*"], "exclude": ["src/**/*.test.ts", "**/node_modules"] } diff --git a/packages/wallet-ledger/tsconfig.json b/packages/wallet-ledger/tsconfig.json index d8b05952..4d65d0b5 100644 --- a/packages/wallet-ledger/tsconfig.json +++ b/packages/wallet-ledger/tsconfig.json @@ -18,6 +18,6 @@ "forceConsistentCasingInFileNames": true, "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, - "include": ["src/**/*", "src/config/tokens.json"], + "include": ["src/**/*"], "exclude": ["src/**/*.test.ts", "**/node_modules"] } From 91b6fffd572fca71daa0b356a941294858bc1441 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 13 Apr 2021 14:33:58 -0500 Subject: [PATCH 03/58] simplify parent paths --- packages/common/src/contexts/wallet.tsx | 4 ++-- packages/common/src/wallet-adapters/solong_adapter.tsx | 2 +- packages/lending/src/views/liquidate/index.tsx | 2 +- packages/lending/src/views/reserve/index.tsx | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/common/src/contexts/wallet.tsx b/packages/common/src/contexts/wallet.tsx index c6bf5e56..8e68fa7e 100644 --- a/packages/common/src/contexts/wallet.tsx +++ b/packages/common/src/contexts/wallet.tsx @@ -3,9 +3,9 @@ import { WalletAdapter } from "@solana/wallet-base"; import Wallet from "@project-serum/sol-wallet-adapter"; import { Button, Modal } from "antd"; import React, { useCallback, useContext, useEffect, useMemo, useState} from "react"; -import { notify } from "./../utils/notifications"; +import { notify } from "../utils/notifications"; import { useConnectionConfig } from "./connection"; -import { useLocalStorageState } from "./../utils/utils"; +import { useLocalStorageState } from "../utils/utils"; import { LedgerProvider } from "@solana/wallet-ledger"; import { SolongWalletAdapter } from "../wallet-adapters/solong"; import { PhantomWalletAdapter } from "../wallet-adapters/phantom"; diff --git a/packages/common/src/wallet-adapters/solong_adapter.tsx b/packages/common/src/wallet-adapters/solong_adapter.tsx index 3c3c61ed..aef7dd64 100644 --- a/packages/common/src/wallet-adapters/solong_adapter.tsx +++ b/packages/common/src/wallet-adapters/solong_adapter.tsx @@ -1,6 +1,6 @@ import EventEmitter from "eventemitter3"; import { PublicKey } from "@solana/web3.js"; -import { notify } from "./../utils/notifications"; +import { notify } from "../utils/notifications"; export class SolongAdapter extends EventEmitter { _publicKey: any; diff --git a/packages/lending/src/views/liquidate/index.tsx b/packages/lending/src/views/liquidate/index.tsx index e9da9314..04d03bd6 100644 --- a/packages/lending/src/views/liquidate/index.tsx +++ b/packages/lending/src/views/liquidate/index.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from "react"; import { GUTTER, LABELS } from "../../constants"; import { LiquidateItem } from "./item"; -import { useEnrichedLendingObligations } from "./../../hooks"; +import { useEnrichedLendingObligations } from "../../hooks"; import "./style.less"; import { Card, Col, Row, Statistic, Typography } from "antd"; import { BarChartStatistic } from "../../components/BarChartStatistic"; diff --git a/packages/lending/src/views/reserve/index.tsx b/packages/lending/src/views/reserve/index.tsx index d4294d72..d32ac85c 100644 --- a/packages/lending/src/views/reserve/index.tsx +++ b/packages/lending/src/views/reserve/index.tsx @@ -1,10 +1,10 @@ import React from "react"; -import { useLendingReserve } from "./../../hooks"; +import { useLendingReserve } from "../../hooks"; import { useParams } from "react-router-dom"; import "./style.less"; -import { UserLendingCard } from "./../../components/UserLendingCard"; -import { ReserveStatus } from "./../../components/ReserveStatus"; +import { UserLendingCard } from "../../components/UserLendingCard"; +import { ReserveStatus } from "../../components/ReserveStatus"; import { Col, Row } from "antd"; import { GUTTER } from "../../constants"; From c4be0d78e5c12e54d416af007e3c5d308a0f847e Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 22 Apr 2021 18:51:23 -0500 Subject: [PATCH 04/58] models --- packages/lending/src/models/dex/market.ts | 3 +- packages/lending/src/models/index.ts | 3 +- .../instructions/borrowObligationLiquidity.ts | 109 +++++++ .../depositObligationCollateral.ts | 71 +++++ .../depositReserveLiquidity.ts} | 51 ++-- .../lending/src/models/instructions/index.ts | 13 + .../models/instructions/initLendingMarket.ts | 54 ++++ .../src/models/instructions/initObligation.ts | 45 +++ .../src/models/instructions/initReserve.ts | 104 +++++++ .../src/models/instructions/instruction.ts | 15 + .../liquidateObligation.ts} | 69 ++--- .../instructions/redeemReserveCollateral.ts | 70 +++++ .../models/instructions/refreshObligation.ts | 51 ++++ .../src/models/instructions/refreshReserve.ts | 42 +++ .../instructions/repayObligationLiquidity.ts | 68 +++++ .../instructions/setLendingMarketOwner.ts | 46 +++ .../withdrawObligationCollateral.ts | 67 ++++ packages/lending/src/models/lending/borrow.ts | 149 --------- packages/lending/src/models/lending/index.ts | 7 - .../lending/src/models/lending/lending.ts | 11 - .../lending/src/models/lending/obligation.ts | 114 ------- packages/lending/src/models/lending/repay.ts | 91 ------ .../lending/src/models/lending/reserve.ts | 286 ------------------ .../lending/src/models/lending/withdraw.ts | 56 ---- packages/lending/src/models/pool.ts | 2 +- packages/lending/src/models/state/index.ts | 3 + .../lending/src/models/state/lastUpdate.ts | 6 + .../market.ts => state/lendingMarket.ts} | 19 +- .../lending/src/models/state/obligation.ts | 84 +++++ packages/lending/src/models/state/reserve.ts | 188 ++++++++++++ 30 files changed, 1104 insertions(+), 793 deletions(-) create mode 100644 packages/lending/src/models/instructions/borrowObligationLiquidity.ts create mode 100644 packages/lending/src/models/instructions/depositObligationCollateral.ts rename packages/lending/src/models/{lending/deposit.ts => instructions/depositReserveLiquidity.ts} (53%) create mode 100644 packages/lending/src/models/instructions/index.ts create mode 100644 packages/lending/src/models/instructions/initLendingMarket.ts create mode 100644 packages/lending/src/models/instructions/initObligation.ts create mode 100644 packages/lending/src/models/instructions/initReserve.ts create mode 100644 packages/lending/src/models/instructions/instruction.ts rename packages/lending/src/models/{lending/liquidate.ts => instructions/liquidateObligation.ts} (51%) create mode 100644 packages/lending/src/models/instructions/redeemReserveCollateral.ts create mode 100644 packages/lending/src/models/instructions/refreshObligation.ts create mode 100644 packages/lending/src/models/instructions/refreshReserve.ts create mode 100644 packages/lending/src/models/instructions/repayObligationLiquidity.ts create mode 100644 packages/lending/src/models/instructions/setLendingMarketOwner.ts create mode 100644 packages/lending/src/models/instructions/withdrawObligationCollateral.ts delete mode 100644 packages/lending/src/models/lending/borrow.ts delete mode 100644 packages/lending/src/models/lending/index.ts delete mode 100644 packages/lending/src/models/lending/lending.ts delete mode 100644 packages/lending/src/models/lending/obligation.ts delete mode 100644 packages/lending/src/models/lending/repay.ts delete mode 100644 packages/lending/src/models/lending/reserve.ts delete mode 100644 packages/lending/src/models/lending/withdraw.ts create mode 100644 packages/lending/src/models/state/index.ts create mode 100644 packages/lending/src/models/state/lastUpdate.ts rename packages/lending/src/models/{lending/market.ts => state/lendingMarket.ts} (77%) create mode 100644 packages/lending/src/models/state/obligation.ts create mode 100644 packages/lending/src/models/state/reserve.ts diff --git a/packages/lending/src/models/dex/market.ts b/packages/lending/src/models/dex/market.ts index 54aee094..68e582d0 100644 --- a/packages/lending/src/models/dex/market.ts +++ b/packages/lending/src/models/dex/market.ts @@ -1,6 +1,7 @@ import { contexts, ParsedAccountBase } from '@oyster/common'; import { Market, MARKETS, Orderbook } from '@project-serum/serum'; import { AccountInfo, PublicKey } from '@solana/web3.js'; + const { MintParser, cache } = contexts.Accounts; export const OrderBookParser = (id: PublicKey, acc: AccountInfo) => { @@ -39,7 +40,7 @@ export const DexMarketParser = ( } as ParsedAccountBase; cache.registerParser(details.info.baseMint, MintParser); - cache.registerParser(details.info.quoteMint, MintParser); + cache.registerParser(details.info.quoteTokenMint, MintParser); cache.registerParser(details.info.bids, OrderBookParser); cache.registerParser(details.info.asks, OrderBookParser); diff --git a/packages/lending/src/models/index.ts b/packages/lending/src/models/index.ts index fb37793e..e2e28868 100644 --- a/packages/lending/src/models/index.ts +++ b/packages/lending/src/models/index.ts @@ -1,3 +1,4 @@ -export * from './lending'; +export * from './instructions'; +export * from './state'; export * from './pool'; export * from './totals'; diff --git a/packages/lending/src/models/instructions/borrowObligationLiquidity.ts b/packages/lending/src/models/instructions/borrowObligationLiquidity.ts new file mode 100644 index 00000000..6903d9d3 --- /dev/null +++ b/packages/lending/src/models/instructions/borrowObligationLiquidity.ts @@ -0,0 +1,109 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { calculateUtilizationRatio, Reserve } from '../state/reserve'; +import { LendingInstruction } from './instruction'; + +/// Borrow liquidity from a reserve by depositing collateral tokens. Requires a refreshed +/// obligation and reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source borrow reserve liquidity supply SPL Token account. +/// 1. `[writable]` Destination liquidity token account. +/// Minted by borrow reserve liquidity mint. +/// 2. `[writable]` Borrow reserve account - refreshed. +/// 3. `[writable]` Borrow reserve liquidity fee receiver account. +/// Must be the fee account specified at InitReserve. +/// 4. `[writable]` Obligation account - refreshed. +/// 5. `[]` Lending market account. +/// 6. `[]` Derived lending market authority. +/// 7. `[signer]` Obligation owner. +/// 8. `[]` Clock sysvar. +/// 9. `[]` Token program id. +/// 10 `[optional, writable]` Host fee receiver account. +export const borrowObligationLiquidityInstruction = ( + liquidityAmount: number | BN, + sourceLiquidity: PublicKey, + destinationLiquidity: PublicKey, + borrowReserve: PublicKey, + borrowReserveLiquidityFeeReceiver: PublicKey, + obligation: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + obligationOwner: PublicKey, + hostFeeReceiver?: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('liquidityAmount'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.BorrowObligationLiquidity, + liquidityAmount: new BN(liquidityAmount), + }, + data, + ); + + const keys = [ + { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, + { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, + { pubkey: borrowReserve, isSigner: false, isWritable: true }, + { + pubkey: borrowReserveLiquidityFeeReceiver, + isSigner: false, + isWritable: true, + }, + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: obligationOwner, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + if (hostFeeReceiver) { + keys.push({ pubkey: hostFeeReceiver, isSigner: false, isWritable: true }); + } + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; + +// deposit APY utilization currentUtilizationRate * borrowAPY + +export const calculateBorrowAPY = (reserve: Reserve) => { + const currentUtilization = calculateUtilizationRatio(reserve); + const optimalUtilization = reserve.config.optimalUtilizationRate / 100; + + let borrowAPY; + if (optimalUtilization === 1.0 || currentUtilization < optimalUtilization) { + const normalizedFactor = currentUtilization / optimalUtilization; + const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; + const minBorrowRate = reserve.config.minBorrowRate / 100; + borrowAPY = + normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate; + } else { + const normalizedFactor = + (currentUtilization - optimalUtilization) / (1 - optimalUtilization); + const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; + const maxBorrowRate = reserve.config.maxBorrowRate / 100; + borrowAPY = + normalizedFactor * (maxBorrowRate - optimalBorrowRate) + + optimalBorrowRate; + } + + return borrowAPY; +}; diff --git a/packages/lending/src/models/instructions/depositObligationCollateral.ts b/packages/lending/src/models/instructions/depositObligationCollateral.ts new file mode 100644 index 00000000..d65bb175 --- /dev/null +++ b/packages/lending/src/models/instructions/depositObligationCollateral.ts @@ -0,0 +1,71 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from './../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Deposit collateral to an obligation. Requires a refreshed reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source collateral token account. +/// Minted by deposit reserve collateral mint. +/// $authority can transfer $collateral_amount. +/// 1. `[writable]` Destination deposit reserve collateral supply SPL Token account. +/// 2. `[]` Deposit reserve account - refreshed. +/// 3. `[writable]` Obligation account. +/// 4. `[]` Lending market account. +/// 5. `[]` Derived lending market authority. +/// 6. `[signer]` Obligation owner. +/// 7. `[signer]` User transfer authority ($authority). +/// 8. `[]` Clock sysvar. +/// 9. `[]` Token program id. +export const depositObligationCollateralInstruction = ( + collateralAmount: number | BN, + sourceCollateral: PublicKey, + destinationCollateral: PublicKey, + depositReserve: PublicKey, + obligation: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + obligationOwner: PublicKey, + transferAuthority: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('collateralAmount'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.DepositObligationCollateral, + collateralAmount: new BN(collateralAmount), + }, + data, + ); + + const keys = [ + { pubkey: sourceCollateral, isSigner: false, isWritable: true }, + { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: depositReserve, isSigner: false, isWritable: false }, + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: obligationOwner, isSigner: true, isWritable: false }, + { pubkey: transferAuthority, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/lending/deposit.ts b/packages/lending/src/models/instructions/depositReserveLiquidity.ts similarity index 53% rename from packages/lending/src/models/lending/deposit.ts rename to packages/lending/src/models/instructions/depositReserveLiquidity.ts index 61e962e5..8551f436 100644 --- a/packages/lending/src/models/lending/deposit.ts +++ b/packages/lending/src/models/instructions/depositReserveLiquidity.ts @@ -1,3 +1,4 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; import { PublicKey, SYSVAR_CLOCK_PUBKEY, @@ -5,36 +6,37 @@ import { } from '@solana/web3.js'; import BN from 'bn.js'; import * as BufferLayout from 'buffer-layout'; -import { calculateBorrowAPY } from './borrow'; -import { LendingInstruction } from './lending'; -import { calculateUtilizationRatio, LendingReserve } from './reserve'; -import { utils } from '@oyster/common'; import * as Layout from '../../utils/layout'; +import { calculateUtilizationRatio, Reserve } from '../state/reserve'; +import { calculateBorrowAPY } from './borrowObligationLiquidity'; +import { LendingInstruction } from './instruction'; -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; -/// Deposit liquidity into a reserve. The output is a collateral token representing ownership +/// Deposit liquidity into a reserve in exchange for collateral. Collateral represents a share /// of the reserve liquidity pool. /// -/// 0. `[writable]` Source liquidity token account. $authority can transfer $liquidity_amount +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source liquidity token account. +/// $authority can transfer $liquidity_amount. /// 1. `[writable]` Destination collateral token account. /// 2. `[writable]` Reserve account. /// 3. `[writable]` Reserve liquidity supply SPL Token account. /// 4. `[writable]` Reserve collateral SPL Token mint. /// 5. `[]` Lending market account. /// 6. `[]` Derived lending market authority. -/// 7. `[]` User transfer authority ($authority). -/// 8. `[]` Clock sysvar -/// 9. '[]` Token program id -export const depositInstruction = ( +/// 7. `[signer]` User transfer authority ($authority). +/// 8. `[]` Clock sysvar. +/// 9. `[]` Token program id. +export const depositReserveLiquidityInstruction = ( liquidityAmount: number | BN, - from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount - to: PublicKey, // Collateral output SPL Token account, + sourceLiquidity: PublicKey, + destinationCollateral: PublicKey, + reserve: PublicKey, + reserveLiquiditySupply: PublicKey, + reserveCollateralMint: PublicKey, lendingMarket: PublicKey, - reserveAuthority: PublicKey, + lendingMarketAuthority: PublicKey, transferAuthority: PublicKey, - reserveAccount: PublicKey, - reserveSupply: PublicKey, - collateralMint: PublicKey, ): TransactionInstruction => { const dataLayout = BufferLayout.struct([ BufferLayout.u8('instruction'), @@ -51,17 +53,18 @@ export const depositInstruction = ( ); const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - { pubkey: reserveAccount, isSigner: false, isWritable: true }, - { pubkey: reserveSupply, isSigner: false, isWritable: true }, - { pubkey: collateralMint, isSigner: false, isWritable: true }, + { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, + { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: reserve, isSigner: false, isWritable: true }, + { pubkey: reserveLiquiditySupply, isSigner: false, isWritable: true }, + { pubkey: reserveCollateralMint, isSigner: false, isWritable: true }, { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: reserveAuthority, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, { pubkey: transferAuthority, isSigner: true, isWritable: false }, { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, ]; + return new TransactionInstruction({ keys, programId: LENDING_PROGRAM_ID, @@ -69,7 +72,7 @@ export const depositInstruction = ( }); }; -export const calculateDepositAPY = (reserve: LendingReserve) => { +export const calculateDepositAPY = (reserve: Reserve) => { const currentUtilization = calculateUtilizationRatio(reserve); const borrowAPY = calculateBorrowAPY(reserve); diff --git a/packages/lending/src/models/instructions/index.ts b/packages/lending/src/models/instructions/index.ts new file mode 100644 index 00000000..38d8ce46 --- /dev/null +++ b/packages/lending/src/models/instructions/index.ts @@ -0,0 +1,13 @@ +export * from './borrowObligationLiquidity'; +export * from './depositObligationCollateral'; +export * from './depositReserveLiquidity'; +export * from './initLendingMarket'; +export * from './initObligation'; +export * from './initReserve'; +export * from './instruction'; +export * from './liquidateObligation'; +export * from './redeemReserveCollateral'; +export * from './refreshObligation'; +export * from './refreshReserve'; +export * from './repayObligationLiquidity'; +export * from './withdrawObligationCollateral'; diff --git a/packages/lending/src/models/instructions/initLendingMarket.ts b/packages/lending/src/models/instructions/initLendingMarket.ts new file mode 100644 index 00000000..377eabfa --- /dev/null +++ b/packages/lending/src/models/instructions/initLendingMarket.ts @@ -0,0 +1,54 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Initializes a new lending market. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Lending market account - uninitialized. +/// 1. `[]` Quote currency SPL Token mint. +/// 2. `[]` Rent sysvar. +/// 3. `[]` Token program id. +// InitLendingMarket { +// /// Owner authority which can add new reserves +// owner: Pubkey, +// }, +export const initLendingMarketInstruction = ( + owner: PublicKey, + lendingMarket: PublicKey, + quoteTokenMint: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.publicKey('owner'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.InitLendingMarket, + owner, + }, + data, + ); + + const keys = [ + { pubkey: lendingMarket, isSigner: false, isWritable: true }, + { pubkey: quoteTokenMint, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/initObligation.ts b/packages/lending/src/models/instructions/initObligation.ts new file mode 100644 index 00000000..bd9d3099 --- /dev/null +++ b/packages/lending/src/models/instructions/initObligation.ts @@ -0,0 +1,45 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import * as BufferLayout from 'buffer-layout'; +import { LendingInstruction } from './instruction'; + +/// Initializes a new lending market obligation. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Obligation account - uninitialized. +/// 1. `[]` Lending market account. +/// 2. `[signer]` Obligation owner. +/// 3. `[]` Clock sysvar. +/// 4. `[]` Rent sysvar. +/// 5. `[]` Token program id. +export const initObligationInstruction = ( + obligation: PublicKey, + lendingMarket: PublicKey, + obligationOwner: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode({ instruction: LendingInstruction.InitObligation }, data); + + const keys = [ + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: obligationOwner, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/initReserve.ts b/packages/lending/src/models/instructions/initReserve.ts new file mode 100644 index 00000000..5b5e7142 --- /dev/null +++ b/packages/lending/src/models/instructions/initReserve.ts @@ -0,0 +1,104 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Initializes a new lending market reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source liquidity token account. +/// $authority can transfer $liquidity_amount. +/// 1. `[writable]` Destination collateral token account - uninitialized. +/// 2. `[writable]` Reserve account - uninitialized. +/// 3. `[]` Reserve liquidity SPL Token mint. +/// 4. `[writable]` Reserve liquidity supply SPL Token account - uninitialized. +/// 5. `[writable]` Reserve liquidity fee receiver - uninitialized. +/// 6. `[writable]` Reserve collateral SPL Token mint - uninitialized. +/// 7. `[writable]` Reserve collateral token supply - uninitialized. +/// 8. `[]` Quote currency SPL Token mint. +/// 9. `[]` Lending market account. +/// 10 `[]` Derived lending market authority. +/// 11 `[signer]` Lending market owner. +/// 12 `[signer]` User transfer authority ($authority). +/// 13 `[]` Clock sysvar. +/// 13 `[]` Rent sysvar. +/// 14 `[]` Token program id. +/// 15 `[optional]` Reserve liquidity aggregator account. +/// Not required for quote currency reserves. +/// Must match base and quote currency mint, and quote currency decimals. +// InitReserve { +// /// Initial amount of liquidity to deposit into the new reserve +// liquidity_amount: u64, +// /// Reserve configuration values +// config: ReserveConfig, +// }, +export const initReserveInstruction = ( + liquidityAmount: number | BN, + // @FIXME: reserve config + maxUtilizationRate: number, + sourceLiquidity: PublicKey, + destinationCollateral: PublicKey, + reserve: PublicKey, + liquidityMint: PublicKey, + liquiditySupply: PublicKey, + liquidityFeeReceiver: PublicKey, + collateralMint: PublicKey, + collateralSupply: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + lendingMarketOwner: PublicKey, + transferAuthority: PublicKey, + aggregator?: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('liquidityAmount'), + // @FIXME: reserve config + BufferLayout.u8('maxUtilizationRate'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.InitReserve, + liquidityAmount: new BN(liquidityAmount), + maxUtilizationRate, + }, + data, + ); + + const keys = [ + { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, + { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: reserve, isSigner: false, isWritable: true }, + { pubkey: liquidityMint, isSigner: false, isWritable: false }, + { pubkey: liquiditySupply, isSigner: false, isWritable: true }, + { pubkey: liquidityFeeReceiver, isSigner: false, isWritable: true }, + { pubkey: collateralMint, isSigner: false, isWritable: true }, + { pubkey: collateralSupply, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: true }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: transferAuthority, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + if (aggregator) { + keys.push({ pubkey: aggregator, isSigner: false, isWritable: false }); + } + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/instruction.ts b/packages/lending/src/models/instructions/instruction.ts new file mode 100644 index 00000000..d342c072 --- /dev/null +++ b/packages/lending/src/models/instructions/instruction.ts @@ -0,0 +1,15 @@ +export enum LendingInstruction { + InitLendingMarket = 0, + SetLendingMarketOwner = 1, + InitReserve = 2, + RefreshReserve = 3, + DepositReserveLiquidity = 4, + RedeemReserveCollateral = 5, + InitObligation = 6, + RefreshObligation = 7, + DepositObligationCollateral = 8, + WithdrawObligationCollateral = 9, + BorrowObligationLiquidity = 10, + RepayObligationLiquidity = 11, + LiquidateObligation = 12, +} diff --git a/packages/lending/src/models/lending/liquidate.ts b/packages/lending/src/models/instructions/liquidateObligation.ts similarity index 51% rename from packages/lending/src/models/lending/liquidate.ts rename to packages/lending/src/models/instructions/liquidateObligation.ts index 73012440..b16e6cc3 100644 --- a/packages/lending/src/models/lending/liquidate.ts +++ b/packages/lending/src/models/instructions/liquidateObligation.ts @@ -1,48 +1,46 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction, } from '@solana/web3.js'; import BN from 'bn.js'; -import { LendingInstruction } from './lending'; import * as BufferLayout from 'buffer-layout'; -import { utils } from '@oyster/common'; import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; -/// Purchase collateral tokens at a discount rate if the chosen obligation is unhealthy. +/// Repay borrowed liquidity to a reserve to receive collateral at a discount from an unhealthy +/// obligation. Requires a refreshed obligation and reserves. /// -/// 0. `[writable]` Source liquidity token account, minted by repay reserve liquidity mint -/// $authority can transfer $collateral_amount -/// 1. `[writable]` Destination collateral token account, minted by withdraw reserve collateral mint -/// 2. `[]` Repay reserve account. -/// 3. `[writable]` Repay reserve liquidity supply SPL Token account -/// 4. `[writable]` Withdraw reserve account. -/// 5. `[writable]` Withdraw reserve collateral supply SPL Token account -/// 6. `[writable]` Obligation - initialized +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source liquidity token account. +/// Minted by repay reserve liquidity mint. +/// $authority can transfer $liquidity_amount. +/// 1. `[writable]` Destination collateral token account. +/// Minted by withdraw reserve collateral mint. +/// 2. `[writable]` Repay reserve account - refreshed. +/// 3. `[writable]` Repay reserve liquidity supply SPL Token account. +/// 4. `[]` Withdraw reserve account - refreshed. +/// 5. `[writable]` Withdraw reserve collateral supply SPL Token account. +/// 6. `[writable]` Obligation account - refreshed. /// 7. `[]` Lending market account. /// 8. `[]` Derived lending market authority. -/// 9. `[]` User transfer authority ($authority). -/// 10 `[]` Dex market -/// 11 `[]` Dex market order book side -/// 12 `[]` Temporary memory -/// 13 `[]` Clock sysvar -/// 14 `[]` Token program id -export const liquidateInstruction = ( +/// 9. `[signer]` User transfer authority ($authority). +/// 10 `[]` Clock sysvar. +/// 11 `[]` Token program id. +export const liquidateObligationInstruction = ( liquidityAmount: number | BN, - from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount - to: PublicKey, // Collateral output SPL Token account, - repayReserveAccount: PublicKey, + sourceLiquidity: PublicKey, + destinationCollateral: PublicKey, + repayReserve: PublicKey, repayReserveLiquiditySupply: PublicKey, withdrawReserve: PublicKey, withdrawReserveCollateralSupply: PublicKey, obligation: PublicKey, lendingMarket: PublicKey, - authority: PublicKey, + lendingMarketAuthority: PublicKey, transferAuthority: PublicKey, - dexMarket: PublicKey, - dexOrderBookSide: PublicKey, - memory: PublicKey, ): TransactionInstruction => { const dataLayout = BufferLayout.struct([ BufferLayout.u8('instruction'), @@ -59,33 +57,24 @@ export const liquidateInstruction = ( ); const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - - { pubkey: repayReserveAccount, isSigner: false, isWritable: true }, + { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, + { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: repayReserve, isSigner: false, isWritable: true }, { pubkey: repayReserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: withdrawReserve, isSigner: false, isWritable: false }, { pubkey: withdrawReserveCollateralSupply, isSigner: false, isWritable: true, }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: authority, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, { pubkey: transferAuthority, isSigner: true, isWritable: false }, - - { pubkey: dexMarket, isSigner: false, isWritable: false }, - { pubkey: dexOrderBookSide, isSigner: false, isWritable: false }, - - { pubkey: memory, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, ]; + return new TransactionInstruction({ keys, programId: LENDING_PROGRAM_ID, diff --git a/packages/lending/src/models/instructions/redeemReserveCollateral.ts b/packages/lending/src/models/instructions/redeemReserveCollateral.ts new file mode 100644 index 00000000..5c819714 --- /dev/null +++ b/packages/lending/src/models/instructions/redeemReserveCollateral.ts @@ -0,0 +1,70 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Redeem collateral from a reserve in exchange for liquidity. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source collateral token account. +/// $authority can transfer $collateral_amount. +/// 1. `[writable]` Destination liquidity token account. +/// 2. `[writable]` Reserve account. +/// 3. `[writable]` Reserve collateral SPL Token mint. +/// 4. `[writable]` Reserve liquidity supply SPL Token account. +/// 5. `[]` Lending market account. +/// 6. `[]` Derived lending market authority. +/// 7. `[signer]` User transfer authority ($authority). +/// 8. `[]` Clock sysvar. +/// 9. `[]` Token program id. +export const redeemReserveCollateralInstruction = ( + collateralAmount: number | BN, + sourceCollateral: PublicKey, + destinationLiquidity: PublicKey, + reserve: PublicKey, + reserveCollateralMint: PublicKey, + reserveLiquiditySupply: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + transferAuthority: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('collateralAmount'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.RedeemReserveCollateral, + collateralAmount: new BN(collateralAmount), + }, + data, + ); + + const keys = [ + { pubkey: sourceCollateral, isSigner: false, isWritable: true }, + { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, + { pubkey: reserve, isSigner: false, isWritable: true }, + { pubkey: reserveCollateralMint, isSigner: false, isWritable: true }, + { pubkey: reserveLiquiditySupply, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: transferAuthority, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/refreshObligation.ts b/packages/lending/src/models/instructions/refreshObligation.ts new file mode 100644 index 00000000..7ab24461 --- /dev/null +++ b/packages/lending/src/models/instructions/refreshObligation.ts @@ -0,0 +1,51 @@ +import { LENDING_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BufferLayout from 'buffer-layout'; +import { LendingInstruction } from './instruction'; + +/// Refresh an obligation's accrued interest and collateral and liquidity prices. Requires +/// refreshed reserves, as all obligation collateral deposit reserves in order, followed by all +/// liquidity borrow reserves in order. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Obligation account. +/// 1. `[]` Clock sysvar. +/// .. `[]` Collateral deposit reserve accounts - refreshed, all, in order. +/// .. `[]` Liquidity borrow reserve accounts - refreshed, all, in order. +export const refreshObligationInstruction = ( + obligation: PublicKey, + depositReserves: PublicKey[], + borrowReserves: PublicKey[], +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { instruction: LendingInstruction.RefreshObligation }, + data, + ); + + const keys = [ + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + ]; + + for (const depositReserve of depositReserves) { + keys.push({ pubkey: depositReserve, isSigner: false, isWritable: false }); + } + + for (const borrowReserve of borrowReserves) { + keys.push({ pubkey: borrowReserve, isSigner: false, isWritable: false }); + } + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/refreshReserve.ts b/packages/lending/src/models/instructions/refreshReserve.ts new file mode 100644 index 00000000..3e74c96d --- /dev/null +++ b/packages/lending/src/models/instructions/refreshReserve.ts @@ -0,0 +1,42 @@ +import { LENDING_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import * as BufferLayout from 'buffer-layout'; +import { LendingInstruction } from './instruction'; + +/// Accrue interest and update market price of liquidity on a reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Reserve account. +/// 1. `[]` Clock sysvar. +/// 2. `[optional]` Reserve liquidity aggregator account. +/// Required if the reserve currency is not the lending market quote +/// currency. +export const refreshReserveInstruction = ( + reserve: PublicKey, + aggregator?: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode({ instruction: LendingInstruction.RefreshReserve }, data); + + const keys = [ + { pubkey: reserve, isSigner: false, isWritable: true }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + ]; + + if (aggregator) { + keys.push({ pubkey: aggregator, isSigner: false, isWritable: false }); + } + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/repayObligationLiquidity.ts b/packages/lending/src/models/instructions/repayObligationLiquidity.ts new file mode 100644 index 00000000..f01952e9 --- /dev/null +++ b/packages/lending/src/models/instructions/repayObligationLiquidity.ts @@ -0,0 +1,68 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Repay borrowed liquidity to a reserve. Requires a refreshed obligation and reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source liquidity token account. +/// Minted by repay reserve liquidity mint. +/// $authority can transfer $liquidity_amount. +/// 1. `[writable]` Destination repay reserve liquidity supply SPL Token account. +/// 2. `[writable]` Repay reserve account - refreshed. +/// 3. `[writable]` Obligation account - refreshed. +/// 4. `[]` Lending market account. +/// 5. `[]` Derived lending market authority. +/// 6. `[signer]` User transfer authority ($authority). +/// 7. `[]` Clock sysvar. +/// 8. `[]` Token program id. +export const repayObligationLiquidityInstruction = ( + liquidityAmount: number | BN, + sourceLiquidity: PublicKey, + destinationLiquidity: PublicKey, + repayReserve: PublicKey, + obligation: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + transferAuthority: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('liquidityAmount'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.RepayObligationLiquidity, + liquidityAmount: new BN(liquidityAmount), + }, + data, + ); + + const keys = [ + { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, + { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, + { pubkey: repayReserve, isSigner: false, isWritable: true }, + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: transferAuthority, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/setLendingMarketOwner.ts b/packages/lending/src/models/instructions/setLendingMarketOwner.ts new file mode 100644 index 00000000..edd28163 --- /dev/null +++ b/packages/lending/src/models/instructions/setLendingMarketOwner.ts @@ -0,0 +1,46 @@ +import { LENDING_PROGRAM_ID } from '@oyster/common'; +import { PublicKey, TransactionInstruction } from '@solana/web3.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Sets the new owner of a lending market. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Lending market account. +/// 1. `[signer]` Current owner. +// SetLendingMarketOwner { +// /// The new owner +// new_owner: Pubkey, +// }, +export const setLendingMarketOwnerInstruction = ( + newOwner: PublicKey, + lendingMarket: PublicKey, + currentOwner: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.publicKey('newOwner'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.SetLendingMarketOwner, + newOwner, + }, + data, + ); + + const keys = [ + { pubkey: lendingMarket, isSigner: false, isWritable: true }, + { pubkey: currentOwner, isSigner: true, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/instructions/withdrawObligationCollateral.ts b/packages/lending/src/models/instructions/withdrawObligationCollateral.ts new file mode 100644 index 00000000..a5120094 --- /dev/null +++ b/packages/lending/src/models/instructions/withdrawObligationCollateral.ts @@ -0,0 +1,67 @@ +import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from './../../utils/layout'; +import { LendingInstruction } from './instruction'; + +/// Withdraw collateral from an obligation. Requires a refreshed obligation and reserve. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Source withdraw reserve collateral supply SPL Token account. +/// 1. `[writable]` Destination collateral token account. +/// Minted by withdraw reserve collateral mint. +/// 2. `[]` Withdraw reserve account - refreshed. +/// 3. `[writable]` Obligation account - refreshed. +/// 4. `[]` Lending market account. +/// 5. `[]` Derived lending market authority. +/// 6. `[signer]` Obligation owner. +/// 7. `[]` Clock sysvar. +/// 8. `[]` Token program id. +export const withdrawObligationCollateralInstruction = ( + collateralAmount: number | BN, + sourceCollateral: PublicKey, + destinationCollateral: PublicKey, + withdrawReserve: PublicKey, + obligation: PublicKey, + lendingMarket: PublicKey, + lendingMarketAuthority: PublicKey, + obligationOwner: PublicKey, +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + Layout.uint64('collateralAmount'), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.WithdrawObligationCollateral, + collateralAmount: new BN(collateralAmount), + }, + data, + ); + + const keys = [ + { pubkey: sourceCollateral, isSigner: false, isWritable: true }, + { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: withdrawReserve, isSigner: false, isWritable: false }, + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, + { pubkey: obligationOwner, isSigner: true, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: LENDING_PROGRAM_ID, + data, + }); +}; diff --git a/packages/lending/src/models/lending/borrow.ts b/packages/lending/src/models/lending/borrow.ts deleted file mode 100644 index 83d9f892..00000000 --- a/packages/lending/src/models/lending/borrow.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './lending'; -import { calculateUtilizationRatio, LendingReserve } from './reserve'; -import { utils } from '@oyster/common'; -import * as Layout from '../../utils/layout'; -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; -export enum BorrowAmountType { - LiquidityBorrowAmount = 0, - CollateralDepositAmount = 1, -} - -/// Borrow tokens from a reserve by depositing collateral tokens. The number of borrowed tokens -/// is calculated by market price. The debt obligation is tokenized. -/// -/// 0. `[writable]` Source collateral token account, minted by deposit reserve collateral mint, -/// $authority can transfer $collateral_amount -/// 1. `[writable]` Destination liquidity token account, minted by borrow reserve liquidity mint -/// 2. `[]` Deposit reserve account. -/// 3. `[writable]` Deposit reserve collateral supply SPL Token account -/// 4. `[writable]` Borrow reserve account. -/// 5. `[writable]` Borrow reserve liquidity supply SPL Token account -/// 6. `[writable]` Obligation -/// 7. `[writable]` Obligation token mint -/// 8. `[writable]` Obligation token output -/// 8 `[]` Lending market account. -/// 10 `[]` Derived lending market authority. -/// 11 `[]` User transfer authority ($authority). -/// 12 `[]` Dex market -/// 13 `[]` Dex market order book side -/// 14 `[]` Temporary memory -/// 15 `[]` Clock sysvar -/// 16 '[]` Token program id -export const borrowInstruction = ( - amount: number | BN, - amountType: BorrowAmountType, - from: PublicKey, // Collateral input SPL Token account. $authority can transfer $collateralAmount - to: PublicKey, // Liquidity output SPL Token account, - depositReserve: PublicKey, - depositReserveCollateralSupply: PublicKey, - ownerFeeReceiver: PublicKey, - - borrowReserve: PublicKey, - borrowReserveLiquiditySupply: PublicKey, - - obligation: PublicKey, - obligationMint: PublicKey, - obligationTokenOutput: PublicKey, - - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, - - dexMarket: PublicKey, - dexOrderBookSide: PublicKey, - - memory: PublicKey, - - hostFeeReceiver?: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('amount'), - BufferLayout.u8('amountType'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.BorrowLiquidity, - amount: new BN(amount), - amountType, - }, - data, - ); - - const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - { pubkey: depositReserve, isSigner: false, isWritable: false }, - { - pubkey: depositReserveCollateralSupply, - isSigner: false, - isWritable: true, - }, - { pubkey: ownerFeeReceiver, isSigner: false, isWritable: true }, - - { pubkey: borrowReserve, isSigner: false, isWritable: true }, - { - pubkey: borrowReserveLiquiditySupply, - isSigner: false, - isWritable: true, - }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: obligationMint, isSigner: false, isWritable: true }, - { pubkey: obligationTokenOutput, isSigner: false, isWritable: true }, - - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - - { pubkey: dexMarket, isSigner: false, isWritable: false }, - { pubkey: dexOrderBookSide, isSigner: false, isWritable: false }, - { pubkey: memory, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - if (hostFeeReceiver) { - keys.push({ pubkey: hostFeeReceiver, isSigner: false, isWritable: true }); - } - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; - -// deposit APY utilization currentUtilizationRate * borrowAPY - -export const calculateBorrowAPY = (reserve: LendingReserve) => { - const currentUtilization = calculateUtilizationRatio(reserve); - const optimalUtilization = reserve.config.optimalUtilizationRate / 100; - - let borrowAPY; - if (optimalUtilization === 1.0 || currentUtilization < optimalUtilization) { - const normalizedFactor = currentUtilization / optimalUtilization; - const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; - const minBorrowRate = reserve.config.minBorrowRate / 100; - borrowAPY = - normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate; - } else { - const normalizedFactor = - (currentUtilization - optimalUtilization) / (1 - optimalUtilization); - const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; - const maxBorrowRate = reserve.config.maxBorrowRate / 100; - borrowAPY = - normalizedFactor * (maxBorrowRate - optimalBorrowRate) + - optimalBorrowRate; - } - - return borrowAPY; -}; diff --git a/packages/lending/src/models/lending/index.ts b/packages/lending/src/models/lending/index.ts deleted file mode 100644 index f0c9128a..00000000 --- a/packages/lending/src/models/lending/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './market'; -export * from './reserve'; -export * from './obligation'; -export * from './lending'; -export * from './borrow'; -export * from './deposit'; -export * from './withdraw'; diff --git a/packages/lending/src/models/lending/lending.ts b/packages/lending/src/models/lending/lending.ts deleted file mode 100644 index e6c4334b..00000000 --- a/packages/lending/src/models/lending/lending.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum LendingInstruction { - InitLendingMarket = 0, - InitReserve = 1, - InitObligation = 2, - DepositReserveLiquidity = 3, - WithdrawReserveLiquidity = 4, - BorrowLiquidity = 5, - RepayOblogationLiquidity = 6, - LiquidateObligation = 7, - AccrueReserveInterest = 8, -} diff --git a/packages/lending/src/models/lending/obligation.ts b/packages/lending/src/models/lending/obligation.ts deleted file mode 100644 index 91a9f739..00000000 --- a/packages/lending/src/models/lending/obligation.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - AccountInfo, - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from '.'; -import { utils } from '@oyster/common'; -import * as Layout from '../../utils/layout'; - -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; - -export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct( - [ - BufferLayout.u8('version'), - /// Amount of collateral tokens deposited for this obligation - Layout.uint64('depositedCollateral'), - /// Reserve which collateral tokens were deposited into - Layout.publicKey('collateralReserve'), - /// Borrow rate used for calculating interest. - Layout.uint128('cumulativeBorrowRateWad'), - /// Amount of tokens borrowed for this obligation plus interest - Layout.uint128('borrowAmountWad'), - /// Reserve which tokens were borrowed from - Layout.publicKey('borrowReserve'), - /// Mint address of the tokens for this obligation - Layout.publicKey('tokenMint'), - - // extra space for future contract changes - BufferLayout.blob(128, 'padding'), - ], -); - -export const isLendingObligation = (info: AccountInfo) => { - return info.data.length === LendingObligationLayout.span; -}; - -export interface LendingObligation { - version: number; - - depositedCollateral: BN; - collateralReserve: PublicKey; - cumulativeBorrowRateWad: BN; // decimals - borrowAmountWad: BN; // decimals - borrowReserve: PublicKey; - tokenMint: PublicKey; -} - -export const LendingObligationParser = ( - pubKey: PublicKey, - info: AccountInfo, -) => { - const buffer = Buffer.from(info.data); - const data = LendingObligationLayout.decode(buffer); - - const details = { - pubkey: pubKey, - account: { - ...info, - }, - info: data, - }; - - return details; -}; - -export const healthFactorToRiskColor = (health: number) => { - return ''; -}; - -export const initObligationInstruction = ( - depositReserve: PublicKey, - borrowReserve: PublicKey, - obligation: PublicKey, - obligationMint: PublicKey, - obligationTokenOutput: PublicKey, - obligationTokenOwner: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.InitObligation, - }, - data, - ); - - const keys = [ - { pubkey: depositReserve, isSigner: false, isWritable: false }, - { pubkey: borrowReserve, isSigner: false, isWritable: false }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: obligationMint, isSigner: false, isWritable: true }, - { pubkey: obligationTokenOutput, isSigner: false, isWritable: true }, - { pubkey: obligationTokenOwner, isSigner: false, isWritable: false }, - - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/lending/repay.ts b/packages/lending/src/models/lending/repay.ts deleted file mode 100644 index 5447198a..00000000 --- a/packages/lending/src/models/lending/repay.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import { LendingInstruction } from './lending'; -import * as BufferLayout from 'buffer-layout'; -import { utils } from '@oyster/common'; -import * as Layout from '../../utils/layout'; - -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; - -/// Repay loaned tokens to a reserve and receive collateral tokens. The obligation balance -/// will be recalculated for interest. -/// -/// 0. `[writable]` Source liquidity token account, minted by repay reserve liquidity mint -/// $authority can transfer $collateral_amount -/// 1. `[writable]` Destination collateral token account, minted by withdraw reserve collateral mint -/// 2. `[writable]` Repay reserve account. -/// 3. `[writable]` Repay reserve liquidity supply SPL Token account -/// 4. `[]` Withdraw reserve account. -/// 5. `[writable]` Withdraw reserve collateral supply SPL Token account -/// 6. `[writable]` Obligation - initialized -/// 7. `[writable]` Obligation token mint -/// 8. `[writable]` Obligation token input, $authority can transfer calculated amount -/// 9. `[]` Lending market account. -/// 10 `[]` Derived lending market authority. -/// 11 `[]` User transfer authority ($authority). -/// 12 `[]` Clock sysvar -/// 13 `[]` Token program id -export const repayInstruction = ( - liquidityAmount: number | BN, - from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount - to: PublicKey, // Collateral output SPL Token account, - repayReserveAccount: PublicKey, - repayReserveLiquiditySupply: PublicKey, - withdrawReserve: PublicKey, - withdrawReserveCollateralSupply: PublicKey, - obligation: PublicKey, - obligationMint: PublicKey, - obligationInput: PublicKey, - lendingMarket: PublicKey, - authority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.RepayOblogationLiquidity, - liquidityAmount: new BN(liquidityAmount), - }, - data, - ); - - const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - - { pubkey: repayReserveAccount, isSigner: false, isWritable: true }, - { pubkey: repayReserveLiquiditySupply, isSigner: false, isWritable: true }, - - { pubkey: withdrawReserve, isSigner: false, isWritable: false }, - { - pubkey: withdrawReserveCollateralSupply, - isSigner: false, - isWritable: true, - }, - - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: obligationMint, isSigner: false, isWritable: true }, - { pubkey: obligationInput, isSigner: false, isWritable: true }, - - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: authority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/lending/reserve.ts b/packages/lending/src/models/lending/reserve.ts deleted file mode 100644 index 9d26741d..00000000 --- a/packages/lending/src/models/lending/reserve.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { - AccountInfo, - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './lending'; -import { utils } from '@oyster/common'; -import * as Layout from '../../utils/layout'; - -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID, wadToLamports } = utils; - -export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct( - [ - BufferLayout.u8('version'), - Layout.uint64('lastUpdateSlot'), - - Layout.publicKey('lendingMarket'), - Layout.publicKey('liquidityMint'), - BufferLayout.u8('liquidityMintDecimals'), - Layout.publicKey('liquiditySupply'), - Layout.publicKey('collateralMint'), - Layout.publicKey('collateralSupply'), - - Layout.publicKey('collateralFeesReceiver'), - - // TODO: replace u32 option with generic quivalent - BufferLayout.u32('dexMarketOption'), - Layout.publicKey('dexMarket'), - - BufferLayout.struct( - [ - /// Optimal utilization rate as a percent - BufferLayout.u8('optimalUtilizationRate'), - /// 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'), - /// Min borrow APY - BufferLayout.u8('minBorrowRate'), - /// Optimal (utilization) borrow APY - BufferLayout.u8('optimalBorrowRate'), - /// Max borrow APY - BufferLayout.u8('maxBorrowRate'), - - BufferLayout.struct( - [ - /// Fee assessed on `BorrowReserveLiquidity`, expressed as a Wad. - /// Must be between 0 and 10^18, such that 10^18 = 1. A few examples for - /// clarity: - /// 1% = 10_000_000_000_000_000 - /// 0.01% (1 basis point) = 100_000_000_000_000 - /// 0.00001% (Aave borrow fee) = 100_000_000_000 - Layout.uint64('borrowFeeWad'), - - /// Amount of fee going to host account, if provided in liquidate and repay - BufferLayout.u8('hostFeePercentage'), - ], - 'fees', - ), - ], - 'config', - ), - - BufferLayout.struct( - [ - Layout.uint128('cumulativeBorrowRateWad'), - Layout.uint128('borrowedLiquidityWad'), - Layout.uint64('availableLiquidity'), - Layout.uint64('collateralMintSupply'), - ], - 'state', - ), - - // extra space for future contract changes - BufferLayout.blob(300, 'padding'), - ], -); - -export const isLendingReserve = (info: AccountInfo) => { - return info.data.length === LendingReserveLayout.span; -}; - -export interface LendingReserve { - version: number; - - lastUpdateSlot: BN; - - lendingMarket: PublicKey; - liquiditySupply: PublicKey; - liquidityMint: PublicKey; - collateralMint: PublicKey; - collateralSupply: PublicKey; - collateralFeesReceiver: PublicKey; - - dexMarketOption: number; - dexMarket: PublicKey; - - config: { - optimalUtilizationRate: number; - loanToValueRatio: number; - liquidationBonus: number; - liquidationThreshold: number; - minBorrowRate: number; - optimalBorrowRate: number; - maxBorrowRate: number; - - fees: { - borrowFeeWad: BN; - hostFeePercentage: number; - }; - }; - - state: { - cumulativeBorrowRateWad: BN; - borrowedLiquidityWad: BN; - - availableLiquidity: BN; - collateralMintSupply: BN; - }; -} - -export const LendingReserveParser = ( - pubKey: PublicKey, - info: AccountInfo, -) => { - const buffer = Buffer.from(info.data); - const data = LendingReserveLayout.decode(buffer) as LendingReserve; - - if (data.lastUpdateSlot.toNumber() === 0) { - return; - } - - const details = { - pubkey: pubKey, - account: { - ...info, - }, - info: data, - }; - - return details; -}; - -export const initReserveInstruction = ( - liquidityAmount: number | BN, - maxUtilizationRate: number, - - from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount - to: PublicKey, // Collateral output SPL Token account, - - reserveAccount: PublicKey, - liquidityMint: PublicKey, - liquiditySupply: PublicKey, - collateralMint: PublicKey, - collateralSupply: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, - - dexMarket: PublicKey, // TODO: optional -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - BufferLayout.u8('maxUtilizationRate'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.InitReserve, // Init reserve instruction - liquidityAmount: new BN(liquidityAmount), - maxUtilizationRate: maxUtilizationRate, - }, - data, - ); - - const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - { pubkey: reserveAccount, isSigner: false, isWritable: true }, - { pubkey: liquidityMint, isSigner: false, isWritable: false }, - { pubkey: liquiditySupply, isSigner: false, isWritable: true }, - { pubkey: collateralMint, isSigner: false, isWritable: true }, - { pubkey: collateralSupply, isSigner: false, isWritable: true }, - - // NOTE: Why lending market needs to be a signer? - { pubkey: lendingMarket, isSigner: true, isWritable: true }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - - // optionals - { pubkey: dexMarket, isSigner: false, isWritable: false }, - ]; - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; - -export const accrueInterestInstruction = ( - ...reserveAccount: PublicKey[] -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.AccrueReserveInterest, - }, - data, - ); - - const keys = [ - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - ...reserveAccount.map(reserve => ({ - pubkey: reserve, - isSigner: false, - isWritable: true, - })), - ]; - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; - -export const calculateUtilizationRatio = (reserve: LendingReserve) => { - const totalBorrows = wadToLamports( - reserve.state.borrowedLiquidityWad, - ).toNumber(); - const currentUtilization = - totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows); - - return currentUtilization; -}; - -export const reserveMarketCap = (reserve?: LendingReserve) => { - const available = reserve?.state.availableLiquidity.toNumber() || 0; - const borrowed = wadToLamports( - reserve?.state.borrowedLiquidityWad, - ).toNumber(); - const total = available + borrowed; - - return total; -}; - -export const collateralExchangeRate = (reserve?: LendingReserve) => { - return ( - (reserve?.state.collateralMintSupply.toNumber() || 1) / - reserveMarketCap(reserve) - ); -}; - -export const collateralToLiquidity = ( - collateralAmount: BN | number, - reserve?: LendingReserve, -) => { - const amount = - typeof collateralAmount === 'number' - ? collateralAmount - : collateralAmount.toNumber(); - return Math.floor(amount / collateralExchangeRate(reserve)); -}; - -export const liquidityToCollateral = ( - liquidityAmount: BN | number, - reserve?: LendingReserve, -) => { - const amount = - typeof liquidityAmount === 'number' - ? liquidityAmount - : liquidityAmount.toNumber(); - return Math.floor(amount * collateralExchangeRate(reserve)); -}; diff --git a/packages/lending/src/models/lending/withdraw.ts b/packages/lending/src/models/lending/withdraw.ts deleted file mode 100644 index 8eac7bf6..00000000 --- a/packages/lending/src/models/lending/withdraw.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './lending'; -import { utils } from '@oyster/common'; -import * as Layout from '../../utils/layout'; - -const { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } = utils; - -export const withdrawInstruction = ( - collateralAmount: number | BN, - from: PublicKey, // Collateral input SPL Token account. $authority can transfer $liquidity_amount - to: PublicKey, // Liquidity output SPL Token account, - reserveAccount: PublicKey, - collateralMint: PublicKey, - reserveSupply: PublicKey, - lendingMarket: PublicKey, - authority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('collateralAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.WithdrawReserveLiquidity, - collateralAmount: new BN(collateralAmount), - }, - data, - ); - - const keys = [ - { pubkey: from, isSigner: false, isWritable: true }, - { pubkey: to, isSigner: false, isWritable: true }, - { pubkey: reserveAccount, isSigner: false, isWritable: true }, - { pubkey: collateralMint, isSigner: false, isWritable: true }, - { pubkey: reserveSupply, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: authority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/pool.ts b/packages/lending/src/models/pool.ts index db02b5e0..8324e489 100644 --- a/packages/lending/src/models/pool.ts +++ b/packages/lending/src/models/pool.ts @@ -1,5 +1,5 @@ -import { PublicKey } from '@solana/web3.js'; import { TokenAccount } from '@oyster/common'; +import { PublicKey } from '@solana/web3.js'; export const DEFAULT_DENOMINATOR = 10_000; diff --git a/packages/lending/src/models/state/index.ts b/packages/lending/src/models/state/index.ts new file mode 100644 index 00000000..a49bbc76 --- /dev/null +++ b/packages/lending/src/models/state/index.ts @@ -0,0 +1,3 @@ +export * from './lendingMarket'; +export * from './reserve'; +export * from './obligation'; diff --git a/packages/lending/src/models/state/lastUpdate.ts b/packages/lending/src/models/state/lastUpdate.ts new file mode 100644 index 00000000..b34660a8 --- /dev/null +++ b/packages/lending/src/models/state/lastUpdate.ts @@ -0,0 +1,6 @@ +import BN from 'bn.js'; + +export interface LastUpdate { + slot: BN; + stale: boolean; +} diff --git a/packages/lending/src/models/lending/market.ts b/packages/lending/src/models/state/lendingMarket.ts similarity index 77% rename from packages/lending/src/models/lending/market.ts rename to packages/lending/src/models/state/lendingMarket.ts index 9b5f1a2c..4b85c84a 100644 --- a/packages/lending/src/models/lending/market.ts +++ b/packages/lending/src/models/state/lendingMarket.ts @@ -7,19 +7,17 @@ export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.s BufferLayout.u8('version'), BufferLayout.u8('bumpSeed'), Layout.publicKey('owner'), - Layout.publicKey('quoteMint'), + Layout.publicKey('quoteTokenMint'), Layout.publicKey('tokenProgramId'), - // extra space for future contract changes - BufferLayout.blob(62, 'padding'), + BufferLayout.blob(128, 'padding'), ], ); export interface LendingMarket { version: number; - isInitialized: boolean; - quoteMint: PublicKey; + quoteTokenMint: PublicKey; tokenProgramId: PublicKey; } @@ -28,22 +26,19 @@ export const isLendingMarket = (info: AccountInfo) => { }; export const LendingMarketParser = ( - pubKey: PublicKey, + pubkey: PublicKey, info: AccountInfo, ) => { const buffer = Buffer.from(info.data); - const data = LendingMarketLayout.decode(buffer); + const lendingMarket = LendingMarketLayout.decode(buffer) as LendingMarket; const details = { - pubkey: pubKey, + pubkey, account: { ...info, }, - info: data, + info: lendingMarket, }; return details; }; - -// TODO: -// create instructions for init diff --git a/packages/lending/src/models/state/obligation.ts b/packages/lending/src/models/state/obligation.ts new file mode 100644 index 00000000..dbbfd7fb --- /dev/null +++ b/packages/lending/src/models/state/obligation.ts @@ -0,0 +1,84 @@ +import { AccountInfo, PublicKey } from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LastUpdate } from './lastUpdate'; + +// @FIXME: obligation packing +export const ObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct( + [ + BufferLayout.u8('version'), + /// Amount of collateral tokens deposited for this obligation + Layout.uint64('collateral.depositedAmount'), + /// Reserve which collateral tokens were deposited into + Layout.publicKey('depositReserve'), + /// Borrow rate used for calculating interest. + Layout.uint128('cumulativeBorrowRateWads'), + /// Amount of tokens borrowed for this obligation plus interest + Layout.uint128('borrowedAmountWads'), + /// Reserve which tokens were borrowed from + Layout.publicKey('borrowReserve'), + + // extra space for future contract changes + BufferLayout.blob(128, 'padding'), + ], +); + +export const isObligation = (info: AccountInfo) => { + return info.data.length === ObligationLayout.span; +}; + +export interface Obligation { + version: number; + lastUpdate: LastUpdate; + lendingMarket: PublicKey; + owner: PublicKey; + // @FIXME: check usages + deposits: ObligationCollateral[]; + // @FIXME: check usages + borrows: ObligationLiquidity[]; + depositedValue: BN; // decimals + borrowedValue: BN; // decimals + loanToValueRatio: BN; // decimals + liquidationThreshold: BN; // decimals +} + +export interface ObligationCollateral { + depositReserve: PublicKey; + depositedAmount: BN; + marketValue: BN; // decimals +} + +export interface ObligationLiquidity { + borrowReserve: PublicKey; + cumulativeBorrowRateWads: BN; // decimals + borrowedAmountWads: BN; // decimals + marketValue: BN; // decimals +} + +export const ObligationParser = ( + pubkey: PublicKey, + info: AccountInfo, +) => { + const buffer = Buffer.from(info.data); + const obligation = ObligationLayout.decode(buffer) as Obligation; + + if (obligation.lastUpdate.slot.isZero()) { + return; + } + + const details = { + pubkey, + account: { + ...info, + }, + info: obligation, + }; + + return details; +}; + +// @TODO: implement +export const healthFactorToRiskColor = (health: number) => { + return ''; +}; diff --git a/packages/lending/src/models/state/reserve.ts b/packages/lending/src/models/state/reserve.ts new file mode 100644 index 00000000..ffa1af06 --- /dev/null +++ b/packages/lending/src/models/state/reserve.ts @@ -0,0 +1,188 @@ +import { wadToLamports } from '@oyster/common'; +import { AccountInfo, PublicKey } from '@solana/web3.js'; +import BN from 'bn.js'; +import * as BufferLayout from 'buffer-layout'; +import * as Layout from '../../utils/layout'; +import { LastUpdate } from './lastUpdate'; + +export const ReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct( + [ + BufferLayout.u8('version'), + + BufferLayout.struct( + [Layout.uint64('lastUpdateSlot'), BufferLayout.u8('lastUpdateStale')], + 'lastUpdate', + ), + + Layout.publicKey('lendingMarket'), + + BufferLayout.struct( + [ + Layout.publicKey('liquidityMint'), + BufferLayout.u8('liquidityMintDecimals'), + Layout.publicKey('liquiditySupply'), + Layout.publicKey('liquidityFeeReceiver'), + Layout.uint128('cumulativeBorrowRateWads'), + Layout.uint128('borrowedAmountWads'), + Layout.uint64('availableAmount'), + ], + 'liquidity', + ), + + BufferLayout.struct( + [ + Layout.publicKey('collateralMint'), + Layout.uint64('collateralMintAmount'), + Layout.publicKey('collateralSupply'), + ], + 'collateral', + ), + + // TODO: replace u32 option with generic equivalent + BufferLayout.u32('aggregatorOption'), + Layout.publicKey('aggregator'), + + BufferLayout.struct( + [ + BufferLayout.u8('optimalUtilizationRate'), + BufferLayout.u8('loanToValueRatio'), + BufferLayout.u8('liquidationBonus'), + BufferLayout.u8('liquidationThreshold'), + BufferLayout.u8('minBorrowRate'), + BufferLayout.u8('optimalBorrowRate'), + BufferLayout.u8('maxBorrowRate'), + BufferLayout.struct( + [Layout.uint64('borrowFeeWad'), BufferLayout.u8('hostFeePercentage')], + 'fees', + ), + ], + 'config', + ), + + // extra space for future contract changes + BufferLayout.blob(256, 'padding'), + ], +); + +export const isReserve = (info: AccountInfo) => { + return info.data.length === ReserveLayout.span; +}; + +export interface Reserve { + version: number; + lastUpdate: LastUpdate; + lendingMarket: PublicKey; + liquidity: ReserveLiquidity; + collateral: ReserveCollateral; + config: ReserveConfig; +} + +export interface ReserveLiquidity { + mint: PublicKey; + mintDecimals: number; + supply: PublicKey; + feeReceiver: PublicKey; + + // @FIXME: aggregator option + aggregatorOption: number; + aggregator: PublicKey; + + cumulativeBorrowRateWads: BN; + marketPrice: BN; + availableAmount: BN; + borrowedAmountWads: BN; +} + +export interface ReserveCollateral { + mint: PublicKey; + mintAmount: BN; + supply: PublicKey; +} + +// @FIXME: use BigNumber +export interface ReserveConfig { + optimalUtilizationRate: number; + loanToValueRatio: number; + liquidationBonus: number; + liquidationThreshold: number; + minBorrowRate: number; + optimalBorrowRate: number; + maxBorrowRate: number; + fees: { + borrowFeeWad: BN; + hostFeePercentage: number; + }; +} + +export const ReserveParser = (pubkey: PublicKey, info: AccountInfo) => { + const buffer = Buffer.from(info.data); + const reserve = ReserveLayout.decode(buffer) as Reserve; + + if (reserve.lastUpdate.slot.isZero()) { + return; + } + + const details = { + pubkey, + account: { + ...info, + }, + info: reserve, + }; + + return details; +}; + +export const calculateUtilizationRatio = (reserve: Reserve) => { + // @FIXME: use BigNumber + const totalBorrows = wadToLamports( + reserve.liquidity.borrowedAmountWads, + ).toNumber(); + const currentUtilization = + totalBorrows / + (reserve.liquidity.availableAmount.toNumber() + totalBorrows); + + return currentUtilization; +}; + +export const reserveMarketCap = (reserve?: Reserve) => { + // @FIXME: use BigNumber + const available = reserve?.liquidity.availableAmount.toNumber() || 0; + const borrowed = wadToLamports( + reserve?.liquidity.borrowedAmountWads, + ).toNumber(); + const total = available + borrowed; + + return total; +}; + +export const collateralExchangeRate = (reserve?: Reserve) => { + // @FIXME: use BigNumber + return ( + (reserve?.collateral.mintAmount.toNumber() || 1) / reserveMarketCap(reserve) + ); +}; + +export const collateralToLiquidity = ( + collateralAmount: BN | number, + reserve?: Reserve, +) => { + // @FIXME: use BigNumber + const amount = + typeof collateralAmount === 'number' + ? collateralAmount + : collateralAmount.toNumber(); + return Math.floor(amount / collateralExchangeRate(reserve)); +}; + +export const liquidityToCollateral = ( + liquidityAmount: BN | number, + reserve?: Reserve, +) => { + // @FIXME: use BigNumber + const amount = + typeof liquidityAmount === 'number' + ? liquidityAmount + : liquidityAmount.toNumber(); + return Math.floor(amount * collateralExchangeRate(reserve)); +}; From b9a71a423a29c91ab5986257e058e5bd29dfb173 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 22 Apr 2021 18:51:41 -0500 Subject: [PATCH 05/58] actions --- packages/lending/src/actions/borrow.tsx | 293 ------------------ .../src/actions/borrowObligationLiquidity.tsx | 157 ++++++++++ .../{obligation.tsx => createAccount.tsx} | 11 +- .../lending/src/actions/createObligation.tsx | 18 ++ .../lending/src/actions/createReserve.tsx | 18 ++ ...eposit.tsx => depositReserveLiquidity.tsx} | 130 ++++---- packages/lending/src/actions/index.ts | 12 +- ...{liquidate.tsx => liquidateObligation.tsx} | 108 ++++--- ...thdraw.tsx => redeemReserveCollateral.tsx} | 69 +++-- packages/lending/src/actions/repay.tsx | 157 ---------- .../src/actions/repayObligationLiquidity.tsx | 124 ++++++++ 11 files changed, 493 insertions(+), 604 deletions(-) delete mode 100644 packages/lending/src/actions/borrow.tsx create mode 100644 packages/lending/src/actions/borrowObligationLiquidity.tsx rename packages/lending/src/actions/{obligation.tsx => createAccount.tsx} (69%) create mode 100644 packages/lending/src/actions/createObligation.tsx create mode 100644 packages/lending/src/actions/createReserve.tsx rename packages/lending/src/actions/{deposit.tsx => depositReserveLiquidity.tsx} (50%) rename packages/lending/src/actions/{liquidate.tsx => liquidateObligation.tsx} (60%) rename packages/lending/src/actions/{withdraw.tsx => redeemReserveCollateral.tsx} (60%) delete mode 100644 packages/lending/src/actions/repay.tsx create mode 100644 packages/lending/src/actions/repayObligationLiquidity.tsx diff --git a/packages/lending/src/actions/borrow.tsx b/packages/lending/src/actions/borrow.tsx deleted file mode 100644 index a37cf2f6..00000000 --- a/packages/lending/src/actions/borrow.tsx +++ /dev/null @@ -1,293 +0,0 @@ -import { - Account, - Connection, - PublicKey, - TransactionInstruction, -} from '@solana/web3.js'; -import { - contexts, - utils, - actions, - models, - TokenAccount, - ParsedAccount, -} from '@oyster/common'; - -import { - accrueInterestInstruction, - LendingReserve, -} from './../models/lending/reserve'; -import { AccountLayout, MintInfo, MintLayout } from '@solana/spl-token'; - -import { createUninitializedObligation } from './obligation'; - -import { - LendingObligationLayout, - borrowInstruction, - LendingMarket, - BorrowAmountType, - LendingObligation, - initObligationInstruction, -} from '../models'; - -const { approve } = models; -const { toLamports, LENDING_PROGRAM_ID, LEND_HOST_FEE_ADDRESS, notify } = utils; -const { cache, MintParser } = contexts.Accounts; -const { sendTransaction } = contexts.Connection; -const { - createTempMemoryAccount, - createUninitializedAccount, - createUninitializedMint, - ensureSplAccount, - findOrCreateAccountByMint, -} = actions; - -export const borrow = async ( - connection: Connection, - wallet: any, - - from: TokenAccount, - amount: number, - amountType: BorrowAmountType, - - borrowReserve: ParsedAccount, - - depositReserve: ParsedAccount, - - existingObligation?: ParsedAccount, - - obligationAccount?: PublicKey, -) => { - notify({ - message: 'Borrowing funds...', - description: 'Please review transactions to approve.', - type: 'warn', - }); - - let signers: Account[] = []; - let instructions: TransactionInstruction[] = []; - let cleanupInstructions: TransactionInstruction[] = []; - let finalCleanupInstructions: TransactionInstruction[] = []; - - const [authority] = await PublicKey.findProgramAddress( - [depositReserve.info.lendingMarket.toBuffer()], - LENDING_PROGRAM_ID, - ); - - const accountRentExempt = await connection.getMinimumBalanceForRentExemption( - AccountLayout.span, - ); - - const obligation = existingObligation - ? existingObligation.pubkey - : createUninitializedObligation( - instructions, - wallet.publicKey, - await connection.getMinimumBalanceForRentExemption( - LendingObligationLayout.span, - ), - signers, - ); - - const obligationMint = existingObligation - ? existingObligation.info.tokenMint - : createUninitializedMint( - instructions, - wallet.publicKey, - await connection.getMinimumBalanceForRentExemption(MintLayout.span), - signers, - ); - - const obligationTokenOutput = obligationAccount - ? obligationAccount - : createUninitializedAccount( - instructions, - wallet.publicKey, - accountRentExempt, - signers, - ); - - if (!obligationAccount) { - instructions.push( - initObligationInstruction( - depositReserve.pubkey, - borrowReserve.pubkey, - obligation, - obligationMint, - obligationTokenOutput, - wallet.publicKey, - depositReserve.info.lendingMarket, - authority, - ), - ); - } - - // Creates host fee account if it doesn't exsist - let hostFeeReceiver = LEND_HOST_FEE_ADDRESS - ? findOrCreateAccountByMint( - wallet.publicKey, - LEND_HOST_FEE_ADDRESS, - instructions, - [], - accountRentExempt, - depositReserve.info.collateralMint, - signers, - ) - : undefined; - - let amountLamports: number = 0; - let fromLamports: number = 0; - if (amountType === BorrowAmountType.LiquidityBorrowAmount) { - // approve max transfer - // TODO: improve contrain by using dex market data - const approvedAmount = from.info.amount.toNumber(); - - fromLamports = approvedAmount - accountRentExempt; - - const mint = (await cache.query( - connection, - borrowReserve.info.liquidityMint, - MintParser, - )) as ParsedAccount; - - amountLamports = toLamports(amount, mint?.info); - } else if (amountType === BorrowAmountType.CollateralDepositAmount) { - const mint = (await cache.query( - connection, - depositReserve.info.collateralMint, - MintParser, - )) as ParsedAccount; - amountLamports = toLamports(amount, mint?.info); - fromLamports = amountLamports; - } - - const fromAccount = ensureSplAccount( - instructions, - finalCleanupInstructions, - from, - wallet.publicKey, - fromLamports + accountRentExempt, - signers, - ); - - let toAccount = await findOrCreateAccountByMint( - wallet.publicKey, - wallet.publicKey, - instructions, - finalCleanupInstructions, - accountRentExempt, - borrowReserve.info.liquidityMint, - signers, - ); - - if (instructions.length > 0) { - // create all accounts in one transaction - let { txid } = await sendTransaction(connection, wallet, instructions, [ - ...signers, - ]); - - notify({ - message: 'Obligation accounts created', - description: `Transaction ${txid}`, - type: 'success', - }); - } - - notify({ - message: 'Borrowing funds...', - description: 'Please review transactions to approve.', - type: 'warn', - }); - - signers = []; - instructions = []; - cleanupInstructions = [...finalCleanupInstructions]; - - // create approval for transfer transactions - const transferAuthority = approve( - instructions, - cleanupInstructions, - fromAccount, - wallet.publicKey, - fromLamports, - false, - ); - signers.push(transferAuthority); - - const dexMarketAddress = borrowReserve.info.dexMarketOption - ? borrowReserve.info.dexMarket - : depositReserve.info.dexMarket; - const dexMarket = cache.get(dexMarketAddress); - - if (!dexMarket) { - throw new Error(`Dex market doesn't exist.`); - } - - const market = cache.get( - depositReserve.info.lendingMarket, - ) as ParsedAccount; - const dexOrderBookSide = market.info.quoteMint.equals( - depositReserve.info.liquidityMint, - ) - ? dexMarket?.info.asks - : dexMarket?.info.bids; - - const memory = createTempMemoryAccount( - instructions, - wallet.publicKey, - signers, - LENDING_PROGRAM_ID, - ); - - instructions.push( - accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey), - ); - // borrow - instructions.push( - borrowInstruction( - amountLamports, - amountType, - fromAccount, - toAccount, - depositReserve.pubkey, - depositReserve.info.collateralSupply, - depositReserve.info.collateralFeesReceiver, - - borrowReserve.pubkey, - borrowReserve.info.liquiditySupply, - - obligation, - obligationMint, - obligationTokenOutput, - - depositReserve.info.lendingMarket, - authority, - transferAuthority.publicKey, - - dexMarketAddress, - dexOrderBookSide, - - memory, - - hostFeeReceiver, - ), - ); - try { - let { txid } = await sendTransaction( - connection, - wallet, - instructions.concat(cleanupInstructions), - signers, - true, - ); - - notify({ - message: 'Funds borrowed.', - type: 'success', - description: `Transaction - ${txid}`, - }); - } catch (ex) { - console.error(ex); - throw new Error(); - } -}; diff --git a/packages/lending/src/actions/borrowObligationLiquidity.tsx b/packages/lending/src/actions/borrowObligationLiquidity.tsx new file mode 100644 index 00000000..431abd92 --- /dev/null +++ b/packages/lending/src/actions/borrowObligationLiquidity.tsx @@ -0,0 +1,157 @@ +import { + contexts, + findOrCreateAccountByMint, + LEND_HOST_FEE_ADDRESS, + LENDING_PROGRAM_ID, + notify, + ParsedAccount, + toLamports, +} from '@oyster/common'; +import { AccountLayout, MintInfo } from '@solana/spl-token'; +import { + Account, + Connection, + PublicKey, + TransactionInstruction, +} from '@solana/web3.js'; +import { + borrowObligationLiquidityInstruction, + Obligation, + refreshObligationInstruction, + refreshReserveInstruction, + Reserve, +} from '../models'; + +const { cache, MintParser } = contexts.Accounts; +const { sendTransaction } = contexts.Connection; + +// @FIXME +export const borrowObligationLiquidity = async ( + connection: Connection, + wallet: any, + liquidityAmount: number, + borrowReserve: ParsedAccount, + obligation: ParsedAccount, +) => { + notify({ + message: 'Borrowing funds...', + description: 'Please review transactions to approve.', + type: 'warn', + }); + + let signers: Account[] = []; + let instructions: TransactionInstruction[] = []; + let cleanupInstructions: TransactionInstruction[] = []; + let finalCleanupInstructions: TransactionInstruction[] = []; + + const [lendingMarketAuthority] = await PublicKey.findProgramAddress( + [borrowReserve.info.lendingMarket.toBuffer()], + LENDING_PROGRAM_ID, + ); + + const accountRentExempt = await connection.getMinimumBalanceForRentExemption( + AccountLayout.span, + ); + + // Creates host fee account if it doesn't exist + let hostFeeReceiver = LEND_HOST_FEE_ADDRESS + ? findOrCreateAccountByMint( + wallet.publicKey, + LEND_HOST_FEE_ADDRESS, + instructions, + [], + accountRentExempt, + borrowReserve.info.liquidity.mint, + signers, + ) + : undefined; + + const mint = (await cache.query( + connection, + borrowReserve.info.liquidity.mint, + MintParser, + )) as ParsedAccount; + + // @TODO: handle 100% -> u64::MAX + const amountLamports = toLamports(liquidityAmount, mint?.info); + + let destinationLiquidity = await findOrCreateAccountByMint( + wallet.publicKey, + wallet.publicKey, + instructions, + finalCleanupInstructions, + accountRentExempt, + borrowReserve.info.liquidity.mint, + signers, + ); + + if (instructions.length > 0) { + // create all accounts in one transaction + let { txid } = await sendTransaction(connection, wallet, instructions, [ + ...signers, + ]); + + notify({ + // @TODO: change message + message: 'Obligation accounts created', + description: `Transaction ${txid}`, + type: 'success', + }); + } + + notify({ + message: 'Borrowing funds...', + description: 'Please review transactions to approve.', + type: 'warn', + }); + + // @FIXME: signers + signers = []; + instructions = []; + cleanupInstructions = [...finalCleanupInstructions]; + + instructions.push( + refreshReserveInstruction( + borrowReserve.pubkey, + borrowReserve.info.liquidity.aggregatorOption + ? borrowReserve.info.liquidity.aggregator + : undefined, + ), + refreshObligationInstruction( + obligation.pubkey, + obligation.info.deposits.map((collateral) => collateral.depositReserve), + obligation.info.borrows.map((liquidity) => liquidity.borrowReserve), + ), + borrowObligationLiquidityInstruction( + amountLamports, + borrowReserve.info.liquidity.supply, + destinationLiquidity, + borrowReserve.pubkey, + borrowReserve.info.liquidity.feeReceiver, + obligation.pubkey, + borrowReserve.info.lendingMarket, + lendingMarketAuthority, + obligation.info.owner, + hostFeeReceiver, + ), + ); + + try { + let { txid } = await sendTransaction( + connection, + wallet, + instructions.concat(cleanupInstructions), + signers, + true, + ); + + notify({ + message: 'Funds borrowed.', + type: 'success', + description: `Transaction - ${txid}`, + }); + } catch (ex) { + console.error(ex); + throw new Error(); + } +}; diff --git a/packages/lending/src/actions/obligation.tsx b/packages/lending/src/actions/createAccount.tsx similarity index 69% rename from packages/lending/src/actions/obligation.tsx rename to packages/lending/src/actions/createAccount.tsx index 4543ff37..6ef61e93 100644 --- a/packages/lending/src/actions/obligation.tsx +++ b/packages/lending/src/actions/createAccount.tsx @@ -1,25 +1,26 @@ +import { LENDING_PROGRAM_ID } from '@oyster/common'; import { Account, PublicKey, SystemProgram, TransactionInstruction, } from '@solana/web3.js'; -import { utils } from '@oyster/common'; -import { LendingObligationLayout } from '../models'; -const { LENDING_PROGRAM_ID } = utils; -export function createUninitializedObligation( + +export function createAccount( instructions: TransactionInstruction[], payer: PublicKey, amount: number, signers: Account[], + space: number, ) { const account = new Account(); + instructions.push( SystemProgram.createAccount({ fromPubkey: payer, newAccountPubkey: account.publicKey, lamports: amount, - space: LendingObligationLayout.span, + space, programId: LENDING_PROGRAM_ID, }), ); diff --git a/packages/lending/src/actions/createObligation.tsx b/packages/lending/src/actions/createObligation.tsx new file mode 100644 index 00000000..dda8a029 --- /dev/null +++ b/packages/lending/src/actions/createObligation.tsx @@ -0,0 +1,18 @@ +import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { ObligationLayout } from '../models'; +import { createAccount } from './createAccount'; + +export function createObligation( + instructions: TransactionInstruction[], + payer: PublicKey, + amount: number, + signers: Account[], +) { + return createAccount( + instructions, + payer, + amount, + signers, + ObligationLayout.span, + ); +} diff --git a/packages/lending/src/actions/createReserve.tsx b/packages/lending/src/actions/createReserve.tsx new file mode 100644 index 00000000..71e8ec98 --- /dev/null +++ b/packages/lending/src/actions/createReserve.tsx @@ -0,0 +1,18 @@ +import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { ReserveLayout } from '../models'; +import { createAccount } from './createAccount'; + +export function createReserve( + instructions: TransactionInstruction[], + payer: PublicKey, + amount: number, + signers: Account[], +) { + return createAccount( + instructions, + payer, + amount, + signers, + ReserveLayout.span, + ); +} diff --git a/packages/lending/src/actions/deposit.tsx b/packages/lending/src/actions/depositReserveLiquidity.tsx similarity index 50% rename from packages/lending/src/actions/deposit.tsx rename to packages/lending/src/actions/depositReserveLiquidity.tsx index eb60ae99..9ca7abd5 100644 --- a/packages/lending/src/actions/deposit.tsx +++ b/packages/lending/src/actions/depositReserveLiquidity.tsx @@ -1,20 +1,26 @@ +import { + actions, + contexts, + LENDING_PROGRAM_ID, + models, + notify, + TokenAccount, +} from '@oyster/common'; +import { AccountLayout } from '@solana/spl-token'; import { Account, Connection, PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { contexts, utils, models, actions, TokenAccount } from '@oyster/common'; import { - accrueInterestInstruction, - depositInstruction, + depositReserveLiquidityInstruction, initReserveInstruction, - LendingReserve, -} from './../models/lending'; -import { AccountLayout } from '@solana/spl-token'; + refreshReserveInstruction, + Reserve, +} from '../models'; const { sendTransaction } = contexts.Connection; -const { LENDING_PROGRAM_ID, notify } = utils; const { createUninitializedAccount, ensureSplAccount, @@ -22,13 +28,14 @@ const { } = actions; const { approve } = models; -export const deposit = async ( - from: TokenAccount, - amountLamports: number, - reserve: LendingReserve, - reserveAddress: PublicKey, +// @FIXME: split up into deposit, and init which requires lending market owner +export const depositReserveLiquidity = async ( connection: Connection, wallet: any, + liquidityAmount: number, + source: TokenAccount, + reserve: Reserve, + reserveAddress: PublicKey, ) => { notify({ message: 'Depositing funds...', @@ -47,17 +54,17 @@ export const deposit = async ( AccountLayout.span, ); - const [authority] = await PublicKey.findProgramAddress( + const [lendingMarketAuthority] = await PublicKey.findProgramAddress( [reserve.lendingMarket.toBuffer()], // which account should be authority LENDING_PROGRAM_ID, ); - const fromAccount = ensureSplAccount( + const sourceLiquidityAccount = ensureSplAccount( instructions, cleanupInstructions, - from, + source, wallet.publicKey, - amountLamports + accountRentExempt, + liquidityAmount + accountRentExempt, signers, ); @@ -65,75 +72,80 @@ export const deposit = async ( const transferAuthority = approve( instructions, cleanupInstructions, - fromAccount, + sourceLiquidityAccount, wallet.publicKey, - amountLamports, + liquidityAmount, ); signers.push(transferAuthority); - 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, - ); - } + let destinationCollateralAccount: PublicKey = isInitalized + ? await findOrCreateAccountByMint( + wallet.publicKey, + wallet.publicKey, + instructions, + cleanupInstructions, + accountRentExempt, + reserve.collateral.mint, + signers, + ) + : createUninitializedAccount( + instructions, + wallet.publicKey, + accountRentExempt, + signers, + ); if (isInitalized) { - instructions.push(accrueInterestInstruction(reserveAddress)); - - // deposit instructions.push( - depositInstruction( - amountLamports, - fromAccount, - toAccount, + refreshReserveInstruction( + reserveAddress, + reserve.liquidity.aggregatorOption + ? reserve.liquidity.aggregator + : undefined, + ), + depositReserveLiquidityInstruction( + liquidityAmount, + sourceLiquidityAccount, + destinationCollateralAccount, + reserveAddress, + reserve.liquidity.supply, + reserve.collateral.mint, reserve.lendingMarket, - authority, + lendingMarketAuthority, transferAuthority.publicKey, - reserveAddress, - reserve.liquiditySupply, - reserve.collateralMint, ), ); } else { // TODO: finish reserve init + // @FIXME: reserve config const MAX_UTILIZATION_RATE = 80; instructions.push( initReserveInstruction( - amountLamports, + liquidityAmount, MAX_UTILIZATION_RATE, - fromAccount, - toAccount, + sourceLiquidityAccount, + destinationCollateralAccount, reserveAddress, - reserve.liquidityMint, - reserve.liquiditySupply, - reserve.collateralMint, - reserve.collateralSupply, + reserve.liquidity.mint, + reserve.liquidity.supply, + reserve.liquidity.feeReceiver, + reserve.collateral.mint, + reserve.collateral.supply, reserve.lendingMarket, - authority, + lendingMarketAuthority, + // @FIXME: lending market owner + lendingMarketOwner, transferAuthority.publicKey, - reserve.dexMarket, + reserve.liquidity.aggregatorOption + ? reserve.liquidity.aggregator + : undefined, ), ); } try { - let { txid } = await sendTransaction( + let { txid } = await sendTransaction( connection, wallet, instructions.concat(cleanupInstructions), diff --git a/packages/lending/src/actions/index.ts b/packages/lending/src/actions/index.ts index 8301458d..6ad00a6b 100644 --- a/packages/lending/src/actions/index.ts +++ b/packages/lending/src/actions/index.ts @@ -1,5 +1,7 @@ -export { borrow } from './borrow'; -export { deposit } from './deposit'; -export { repay } from './repay'; -export { withdraw } from './withdraw'; -export { liquidate } from './liquidate'; +export { borrowObligationLiquidity } from './borrowObligationLiquidity'; +export { depositReserveLiquidity } from './depositReserveLiquidity'; +export { repayObligationLiquidity } from './repayObligationLiquidity'; +export { redeemReserveCollateral } from './redeemReserveCollateral'; +export { liquidateObligation } from './liquidateObligation'; + +// @TODO: add actions for other instructions diff --git a/packages/lending/src/actions/liquidate.tsx b/packages/lending/src/actions/liquidateObligation.tsx similarity index 60% rename from packages/lending/src/actions/liquidate.tsx rename to packages/lending/src/actions/liquidateObligation.tsx index 4758ac98..274c2e95 100644 --- a/packages/lending/src/actions/liquidate.tsx +++ b/packages/lending/src/actions/liquidateObligation.tsx @@ -1,46 +1,42 @@ -import { - Account, - Connection, - PublicKey, - TransactionInstruction, -} from '@solana/web3.js'; import { contexts, - utils, - actions, + createTempMemoryAccount, + ensureSplAccount, + findOrCreateAccountByMint, + LENDING_PROGRAM_ID, models, + notify, ParsedAccount, TokenAccount, } from '@oyster/common'; -import { - accrueInterestInstruction, - LendingReserve, -} from './../models/lending/reserve'; -import { liquidateInstruction } from './../models/lending/liquidate'; import { AccountLayout } from '@solana/spl-token'; -import { LendingMarket, LendingObligation } from '../models'; +import { + Account, + Connection, + PublicKey, + TransactionInstruction, +} from '@solana/web3.js'; +import { + LendingMarket, + liquidateObligationInstruction, + Obligation, + refreshReserveInstruction, + Reserve, +} from '../models'; + const { cache } = contexts.Accounts; const { approve } = models; -const { - createTempMemoryAccount, - ensureSplAccount, - findOrCreateAccountByMint, -} = actions; const { sendTransaction } = contexts.Connection; -const { LENDING_PROGRAM_ID, notify } = utils; -export const liquidate = async ( +// @FIXME +export const liquidateObligation = async ( connection: Connection, wallet: any, - from: TokenAccount, // liquidity account - amountLamports: number, // in liquidty token (lamports) - - // which loan to repay - obligation: ParsedAccount, - - repayReserve: ParsedAccount, - - withdrawReserve: ParsedAccount, + liquidityAmount: number, + source: TokenAccount, + repayReserve: ParsedAccount, + withdrawReserve: ParsedAccount, + obligation: ParsedAccount, ) => { notify({ message: 'Repaying funds...', @@ -57,17 +53,17 @@ export const liquidate = async ( AccountLayout.span, ); - const [authority] = await PublicKey.findProgramAddress( + const [lendingMarketAuthority] = await PublicKey.findProgramAddress( [repayReserve.info.lendingMarket.toBuffer()], LENDING_PROGRAM_ID, ); - const fromAccount = ensureSplAccount( + const sourceAccount = ensureSplAccount( instructions, cleanupInstructions, - from, + source, wallet.publicKey, - amountLamports + accountRentExempt, + liquidityAmount + accountRentExempt, signers, ); @@ -75,9 +71,9 @@ export const liquidate = async ( const transferAuthority = approve( instructions, cleanupInstructions, - fromAccount, + sourceAccount, wallet.publicKey, - amountLamports, + liquidityAmount, ); signers.push(transferAuthority); @@ -88,16 +84,17 @@ export const liquidate = async ( instructions, cleanupInstructions, accountRentExempt, - withdrawReserve.info.collateralMint, + withdrawReserve.info.collateral.mint, signers, ); - const dexMarketAddress = repayReserve.info.dexMarketOption - ? repayReserve.info.dexMarket - : withdrawReserve.info.dexMarket; - const dexMarket = cache.get(dexMarketAddress); + // @FIXME: aggregator + const aggregatorAddress = repayReserve.info.liquidity.aggregatorOption + ? repayReserve.info.liquidity.aggregator + : withdrawReserve.info.liquidity.aggregator; + const aggregator = cache.get(aggregatorAddress); - if (!dexMarket) { + if (!aggregator) { throw new Error(`Dex market doesn't exist.`); } @@ -105,11 +102,11 @@ export const liquidate = async ( withdrawReserve.info.lendingMarket, ) as ParsedAccount; - const dexOrderBookSide = market.info.quoteMint.equals( - repayReserve.info.liquidityMint, + const dexOrderBookSide = market.info.quoteTokenMint.equals( + repayReserve.info.liquidity.mint, ) - ? dexMarket?.info.asks - : dexMarket?.info.bids; + ? aggregator?.info.asks + : aggregator?.info.bids; const memory = createTempMemoryAccount( instructions, @@ -119,25 +116,24 @@ export const liquidate = async ( ); instructions.push( - accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey), + // @FIXME: aggregator needed + refreshReserveInstruction(repayReserve.pubkey), + refreshReserveInstruction(withdrawReserve.pubkey), ); instructions.push( - liquidateInstruction( - amountLamports, - fromAccount, + liquidateObligationInstruction( + liquidityAmount, + sourceAccount, toAccount, repayReserve.pubkey, - repayReserve.info.liquiditySupply, + repayReserve.info.liquidity.supply, withdrawReserve.pubkey, - withdrawReserve.info.collateralSupply, + withdrawReserve.info.collateral.supply, obligation.pubkey, repayReserve.info.lendingMarket, - authority, + lendingMarketAuthority, transferAuthority.publicKey, - dexMarketAddress, - dexOrderBookSide, - memory, ), ); diff --git a/packages/lending/src/actions/withdraw.tsx b/packages/lending/src/actions/redeemReserveCollateral.tsx similarity index 60% rename from packages/lending/src/actions/withdraw.tsx rename to packages/lending/src/actions/redeemReserveCollateral.tsx index 9852e8a4..6128b926 100644 --- a/packages/lending/src/actions/withdraw.tsx +++ b/packages/lending/src/actions/redeemReserveCollateral.tsx @@ -1,28 +1,35 @@ +import { + contexts, + findOrCreateAccountByMint, + LENDING_PROGRAM_ID, + models, + notify, + TokenAccount, +} from '@oyster/common'; +import { AccountLayout } from '@solana/spl-token'; import { Account, Connection, PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { contexts, utils, actions, models, TokenAccount } from '@oyster/common'; import { - accrueInterestInstruction, - LendingReserve, - withdrawInstruction, -} from './../models/lending'; -import { AccountLayout } from '@solana/spl-token'; + redeemReserveCollateralInstruction, + refreshReserveInstruction, + Reserve, +} from '../models'; + const { approve } = models; -const { findOrCreateAccountByMint } = actions; const { sendTransaction } = contexts.Connection; -const { LENDING_PROGRAM_ID, notify } = utils; -export const withdraw = async ( - from: TokenAccount, // CollateralAccount - amountLamports: number, // in collateral token (lamports) - reserve: LendingReserve, - reserveAddress: PublicKey, +// @FIXME +export const redeemReserveCollateral = async ( connection: Connection, wallet: any, + collateralAmount: number, + source: TokenAccount, + reserve: Reserve, + reserveAddress: PublicKey, ) => { notify({ message: 'Withdrawing funds...', @@ -39,53 +46,57 @@ export const withdraw = async ( AccountLayout.span, ); - const [authority] = await PublicKey.findProgramAddress( + const [lendingMarketAuthority] = await PublicKey.findProgramAddress( [reserve.lendingMarket.toBuffer()], LENDING_PROGRAM_ID, ); - const fromAccount = from.pubkey; + const sourceCollateral = source.pubkey; // create approval for transfer transactions const transferAuthority = approve( instructions, cleanupInstructions, - fromAccount, + sourceCollateral, wallet.publicKey, - amountLamports, + collateralAmount, ); signers.push(transferAuthority); // get destination account - const toAccount = await findOrCreateAccountByMint( + const destinationLiquidity = await findOrCreateAccountByMint( wallet.publicKey, wallet.publicKey, instructions, cleanupInstructions, accountRentExempt, - reserve.liquidityMint, + reserve.liquidity.mint, signers, ); - instructions.push(accrueInterestInstruction(reserveAddress)); - instructions.push( - withdrawInstruction( - amountLamports, - fromAccount, - toAccount, + refreshReserveInstruction( + reserveAddress, + reserve.liquidity.aggregatorOption + ? reserve.liquidity.aggregator + : undefined, + ), + redeemReserveCollateralInstruction( + collateralAmount, + sourceCollateral, + destinationLiquidity, reserveAddress, - reserve.collateralMint, - reserve.liquiditySupply, + reserve.collateral.mint, + reserve.liquidity.supply, reserve.lendingMarket, - authority, + lendingMarketAuthority, transferAuthority.publicKey, ), ); try { - let { txid } = await sendTransaction( + let { txid } = await sendTransaction( connection, wallet, instructions.concat(cleanupInstructions), diff --git a/packages/lending/src/actions/repay.tsx b/packages/lending/src/actions/repay.tsx deleted file mode 100644 index 7e8ac77a..00000000 --- a/packages/lending/src/actions/repay.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { - Account, - Connection, - PublicKey, - TransactionInstruction, -} from '@solana/web3.js'; -import { - contexts, - utils, - actions, - models, - ParsedAccount, - TokenAccount, -} from '@oyster/common'; - -import { - accrueInterestInstruction, - LendingReserve, -} from './../models/lending/reserve'; -import { repayInstruction } from './../models/lending/repay'; -import { AccountLayout, Token, NATIVE_MINT } from '@solana/spl-token'; - -import { LendingObligation } from '../models'; -const { approve } = models; -const { createTokenAccount, findOrCreateAccountByMint } = actions; -const { sendTransaction } = contexts.Connection; -const { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID, notify } = utils; - -export const repay = async ( - from: TokenAccount, - repayAmount: number, - - // which loan to repay - obligation: ParsedAccount, - - obligationToken: TokenAccount, - - repayReserve: ParsedAccount, - - withdrawReserve: ParsedAccount, - - connection: Connection, - wallet: any, -) => { - notify({ - message: 'Repaying funds...', - description: 'Please review transactions to approve.', - type: 'warn', - }); - - // user from account - const signers: Account[] = []; - const instructions: TransactionInstruction[] = []; - const cleanupInstructions: TransactionInstruction[] = []; - - const accountRentExempt = await connection.getMinimumBalanceForRentExemption( - AccountLayout.span, - ); - - const [authority] = await PublicKey.findProgramAddress( - [repayReserve.info.lendingMarket.toBuffer()], - LENDING_PROGRAM_ID, - ); - - let fromAccount = from.pubkey; - if ( - wallet.publicKey.equals(fromAccount) && - repayReserve.info.liquidityMint.equals(NATIVE_MINT) - ) { - fromAccount = createTokenAccount( - instructions, - wallet.publicKey, - accountRentExempt + repayAmount, - NATIVE_MINT, - wallet.publicKey, - signers, - ); - cleanupInstructions.push( - Token.createCloseAccountInstruction( - TOKEN_PROGRAM_ID, - fromAccount, - wallet.publicKey, - wallet.publicKey, - [], - ), - ); - } - - // create approval for transfer transactions - const transferAuthority = approve( - instructions, - cleanupInstructions, - fromAccount, - wallet.publicKey, - repayAmount, - ); - signers.push(transferAuthority); - - // get destination account - const toAccount = await findOrCreateAccountByMint( - wallet.publicKey, - wallet.publicKey, - instructions, - cleanupInstructions, - accountRentExempt, - withdrawReserve.info.collateralMint, - signers, - ); - - // create approval for transfer transactions - approve( - instructions, - cleanupInstructions, - obligationToken.pubkey, - wallet.publicKey, - obligationToken.info.amount.toNumber(), - true, - // reuse transfer authority - transferAuthority.publicKey, - ); - - instructions.push( - accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey), - ); - - instructions.push( - repayInstruction( - repayAmount, - fromAccount, - toAccount, - repayReserve.pubkey, - repayReserve.info.liquiditySupply, - withdrawReserve.pubkey, - withdrawReserve.info.collateralSupply, - obligation.pubkey, - obligation.info.tokenMint, - obligationToken.pubkey, - repayReserve.info.lendingMarket, - authority, - transferAuthority.publicKey, - ), - ); - - let { txid } = await sendTransaction( - connection, - wallet, - instructions.concat(cleanupInstructions), - signers, - true, - ); - - notify({ - message: 'Funds repaid.', - type: 'success', - description: `Transaction - ${txid}`, - }); -}; diff --git a/packages/lending/src/actions/repayObligationLiquidity.tsx b/packages/lending/src/actions/repayObligationLiquidity.tsx new file mode 100644 index 00000000..bba6bd66 --- /dev/null +++ b/packages/lending/src/actions/repayObligationLiquidity.tsx @@ -0,0 +1,124 @@ +import { + contexts, + createTokenAccount, + LENDING_PROGRAM_ID, + models, + notify, + ParsedAccount, + TOKEN_PROGRAM_ID, + TokenAccount, +} from '@oyster/common'; +import { AccountLayout, NATIVE_MINT, Token } from '@solana/spl-token'; +import { + Account, + Connection, + PublicKey, + TransactionInstruction, +} from '@solana/web3.js'; +import { + Obligation, + refreshReserveInstruction, + repayObligationLiquidityInstruction, + Reserve, +} from '../models'; + +const { approve } = models; +const { sendTransaction } = contexts.Connection; + +// @FIXME +export const repayObligationLiquidity = async ( + connection: Connection, + wallet: any, + liquidityAmount: number, + source: TokenAccount, + repayReserve: ParsedAccount, + obligation: ParsedAccount, +) => { + notify({ + message: 'Repaying funds...', + description: 'Please review transactions to approve.', + type: 'warn', + }); + + // user from account + const signers: Account[] = []; + const instructions: TransactionInstruction[] = []; + const cleanupInstructions: TransactionInstruction[] = []; + + const accountRentExempt = await connection.getMinimumBalanceForRentExemption( + AccountLayout.span, + ); + + const [lendingMarketAuthority] = await PublicKey.findProgramAddress( + [repayReserve.info.lendingMarket.toBuffer()], + LENDING_PROGRAM_ID, + ); + + let sourceLiquidity = source.pubkey; + if ( + wallet.publicKey.equals(sourceLiquidity) && + repayReserve.info.liquidity.mint.equals(NATIVE_MINT) + ) { + sourceLiquidity = createTokenAccount( + instructions, + wallet.publicKey, + accountRentExempt + liquidityAmount, + NATIVE_MINT, + wallet.publicKey, + signers, + ); + cleanupInstructions.push( + Token.createCloseAccountInstruction( + TOKEN_PROGRAM_ID, + sourceLiquidity, + wallet.publicKey, + wallet.publicKey, + [], + ), + ); + } + + // create approval for transfer transactions + const transferAuthority = approve( + instructions, + cleanupInstructions, + sourceLiquidity, + wallet.publicKey, + liquidityAmount, + ); + + signers.push(transferAuthority); + + instructions.push( + refreshReserveInstruction( + repayReserve.pubkey, + repayReserve.info.liquidity.aggregatorOption + ? repayReserve.info.liquidity.aggregator + : undefined, + ), + repayObligationLiquidityInstruction( + liquidityAmount, + sourceLiquidity, + repayReserve.info.liquidity.mint, + repayReserve.pubkey, + obligation.pubkey, + repayReserve.info.lendingMarket, + lendingMarketAuthority, + transferAuthority.publicKey, + ), + ); + + let { txid } = await sendTransaction( + connection, + wallet, + instructions.concat(cleanupInstructions), + signers, + true, + ); + + notify({ + message: 'Funds repaid.', + type: 'success', + description: `Transaction - ${txid}`, + }); +}; From 1fd342b7a1a0072b4707b5577efc24c69c25c274 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 22 Apr 2021 18:51:50 -0500 Subject: [PATCH 06/58] hooks --- .../lending/src/hooks/useBorrowedAmount.ts | 18 +++-- .../lending/src/hooks/useBorrowingPower.ts | 12 ++-- .../lending/src/hooks/useCollateralBalance.ts | 22 +++--- .../hooks/useEnrichedLendingObligations.ts | 69 ++++++++++--------- .../lending/src/hooks/useLendingMarket.ts | 3 +- .../src/hooks/useLendingObligations.ts | 15 ++-- .../lending/src/hooks/useLendingReserves.ts | 26 ++++--- packages/lending/src/hooks/useUserBalance.ts | 4 +- packages/lending/src/hooks/useUserDeposits.ts | 37 +++++----- .../src/hooks/useUserObligationByReserve.ts | 45 +++++++----- .../lending/src/hooks/useUserObligations.ts | 4 +- 11 files changed, 135 insertions(+), 120 deletions(-) diff --git a/packages/lending/src/hooks/useBorrowedAmount.ts b/packages/lending/src/hooks/useBorrowedAmount.ts index b9a71a6c..12785361 100644 --- a/packages/lending/src/hooks/useBorrowedAmount.ts +++ b/packages/lending/src/hooks/useBorrowedAmount.ts @@ -1,13 +1,11 @@ -import { useEffect, useState } from 'react'; +import { contexts, fromLamports, wadToLamports } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; +import { useEffect, useState } from 'react'; +import { useLendingReserve } from './useLendingReserves'; import { useUserObligationByReserve } from './useUserObligationByReserve'; -import { MintInfo } from '@solana/spl-token'; -import { useLendingReserve } from './useLendingReserves'; -import { contexts, utils, ParsedAccount } from '@oyster/common'; const { cache, getMultipleAccounts, MintParser, useMint } = contexts.Accounts; const { useConnection } = contexts.Connection; -const { fromLamports, wadToLamports } = utils; export function useBorrowedAmount(address?: string | PublicKey) { const connection = useConnection(); @@ -20,7 +18,7 @@ export function useBorrowedAmount(address?: string | PublicKey) { health: 0, }); const reserve = useLendingReserve(address); - const liquidityMint = useMint(reserve?.info.liquidityMint); + const liquidityMint = useMint(reserve?.info.liquidity.mint); useEffect(() => { setBorrowedInfo({ @@ -35,6 +33,7 @@ export function useBorrowedAmount(address?: string | PublicKey) { // precache obligation mints const { keys, array } = await getMultipleAccounts( connection, + // @FIXME: obligation tokens userObligationsByReserve.map(item => item.obligation.info.tokenMint.toBase58(), ), @@ -58,21 +57,20 @@ export function useBorrowedAmount(address?: string | PublicKey) { userObligationsByReserve.forEach(item => { const borrowed = wadToLamports( - item.obligation.info.borrowAmountWad, + item.obligation.info.borrows[0].borrowedAmountWads, ).toNumber(); const owned = item.userAccounts.reduce( (amount, acc) => (amount += acc.info.amount.toNumber()), 0, ); - const obligationMint = cache.get( - item.obligation.info.tokenMint, - ) as ParsedAccount; + // @FIXME: obligation tokens result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber()); result.borrowedInUSD += item.obligation.info.borrowedInQuote; result.colateralInUSD += item.obligation.info.collateralInQuote; + // @FIXME: BigNumber liquidationThreshold = item.obligation.info.liquidationThreshold; }, 0); diff --git a/packages/lending/src/hooks/useBorrowingPower.ts b/packages/lending/src/hooks/useBorrowingPower.ts index 72c79f23..60c9f063 100644 --- a/packages/lending/src/hooks/useBorrowingPower.ts +++ b/packages/lending/src/hooks/useBorrowingPower.ts @@ -23,24 +23,24 @@ export function useBorrowingPower( const reserve = useLendingReserve(key); - const liquidityMint = reserve?.info.liquidityMint; + const liquidityMint = reserve?.info.liquidity.mint; const liquidityMintAddress = liquidityMint?.toBase58(); const market = useLendingMarket(reserve?.info.lendingMarket); - const quoteMintAddess = market?.info?.quoteMint?.toBase58(); + const quoteTokenMintAddess = market?.info?.quoteTokenMint?.toBase58(); // TODO: remove once cross-collateral is supported - const onlyQuoteAllowed = liquidityMintAddress !== quoteMintAddess; + const onlyQuoteAllowed = liquidityMintAddress !== quoteTokenMintAddess; const exclude = useMemo(() => new Set([key]), [key]); const inlcude = useMemo(() => { const quoteReserve = getLendingReserves().find( - r => r.info.liquidityMint.toBase58() === quoteMintAddess, + r => r.info.liquidity.mint.toBase58() === quoteTokenMintAddess, ); return onlyQuoteAllowed && quoteReserve ? new Set([quoteReserve.pubkey.toBase58()]) : undefined; - }, [onlyQuoteAllowed, quoteMintAddess]); + }, [onlyQuoteAllowed, quoteTokenMintAddess]); const { totalInQuote } = useUserDeposits(exclude, inlcude); @@ -53,7 +53,7 @@ export function useBorrowingPower( const utilization = totalDeposits === 0 ? 0 : loansValue / totalDeposits; // amounts already expressed as quite mint - if (liquidityMintAddress === quoteMintAddess) { + if (liquidityMintAddress === quoteTokenMintAddess) { return { borrowingPower: totalInQuote, totalInQuote, diff --git a/packages/lending/src/hooks/useCollateralBalance.ts b/packages/lending/src/hooks/useCollateralBalance.ts index e616b482..3d80ff69 100644 --- a/packages/lending/src/hooks/useCollateralBalance.ts +++ b/packages/lending/src/hooks/useCollateralBalance.ts @@ -1,20 +1,19 @@ +import { contexts, fromLamports } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; import { useMarkets } from '../contexts/market'; -import { LendingReserve, reserveMarketCap } from '../models/lending'; +import { Reserve, reserveMarketCap } from '../models'; import { useUserBalance } from './useUserBalance'; -import { contexts, utils } from '@oyster/common'; const { useMint } = contexts.Accounts; -const { fromLamports } = utils; export function useUserCollateralBalance( - reserve?: LendingReserve, + reserve?: Reserve, account?: PublicKey, ) { - const mint = useMint(reserve?.collateralMint); + const mint = useMint(reserve?.collateral.mint); const { balanceLamports: userBalance, accounts } = useUserBalance( - reserve?.collateralMint, + reserve?.collateral.mint, account, ); @@ -34,12 +33,12 @@ export function useUserCollateralBalance( useEffect(() => { const updateBalance = () => { setBalanceInUSD( - balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || ''), + balance * midPriceInUSD(reserve?.liquidity.mint?.toBase58() || ''), ); }; const dispose = marketEmitter.onMarket(args => { - if (args.ids.has(reserve?.dexMarket.toBase58() || '')) { + if (args.ids.has(reserve?.liquidity.aggregator.toBase58() || '')) { updateBalance(); } }); @@ -55,17 +54,18 @@ export function useUserCollateralBalance( balance, balanceLamports, balanceInUSD, - mint: reserve?.collateralMint, + mint: reserve?.collateral.mint, accounts, hasBalance: accounts.length > 0 && balance > 0, }; } export function calculateCollateralBalance( - reserve: LendingReserve, + reserve: Reserve, balanceLamports: number, ) { + // @FIXME: use BigNumber return ( reserveMarketCap(reserve) * - (balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1)) + (balanceLamports / (reserve?.collateral.mintAmount.toNumber() || 1)) ); } diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index 14a833de..759412cb 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -1,33 +1,36 @@ +import { + contexts, + fromLamports, + getTokenName, + ParsedAccount, + wadToLamports, +} from '@oyster/common'; +import { MintInfo } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; +import BN from 'bn.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { simulateMarketOrderFill, useMarkets } from '../contexts/market'; +import { collateralToLiquidity, Obligation, Reserve } from '../models'; import { useLendingObligations } from './useLendingObligations'; -import { - collateralToLiquidity, - LendingObligation, - LendingReserve, -} from '../models/lending'; import { useLendingReserves } from './useLendingReserves'; -import { MintInfo } from '@solana/spl-token'; -import { simulateMarketOrderFill, useMarkets } from '../contexts/market'; - -import { contexts, utils, ParsedAccount } from '@oyster/common'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; -const { fromLamports, wadToLamports, getTokenName } = utils; -interface EnrichedLendingObligationInfo extends LendingObligation { +// @FIXME: BigNumber +interface EnrichedLendingObligationInfo extends Obligation { ltv: number; health: number; borrowedInQuote: number; collateralInQuote: number; - liquidationThreshold: number; + liquidationThreshold: BN; repayName: string; collateralName: string; } +// @TODO: rework export interface EnrichedLendingObligation { - account: ParsedAccount; + account: ParsedAccount; info: EnrichedLendingObligationInfo; } @@ -41,7 +44,7 @@ export function useEnrichedLendingObligations() { return reserveAccounts.reduce((map, reserve) => { map.set(reserve.pubkey.toBase58(), reserve); return map; - }, new Map>()); + }, new Map>()); }, [reserveAccounts]); const enrichedFactory = useCallback(() => { @@ -54,25 +57,27 @@ export function useEnrichedLendingObligations() { .map(obligation => ({ obligation, reserve: availableReserves.get( - obligation.info.borrowReserve.toBase58(), - ) as ParsedAccount, - collateralReserve: availableReserves.get( - obligation.info.collateralReserve.toBase58(), - ) as ParsedAccount, + obligation.info.borrows[0].borrowReserve.toBase58(), + ) as ParsedAccount, + depositReserve: availableReserves.get( + obligation.info.deposits[0].depositReserve.toBase58(), + ) as ParsedAccount, })) // use obligations with reserves available .filter(item => item.reserve) // use reserves with borrow amount greater than zero .filter( item => - wadToLamports(item.obligation.info.borrowAmountWad).toNumber() > 0, + wadToLamports( + item.obligation.info.borrows[0].borrowedAmountWads, + ).toNumber() > 0, ) .map(item => { const obligation = item.obligation; const reserve = item.reserve.info; - const collateralReserve = item.reserve.info; + const depositReserve = item.reserve.info; const liquidityMint = cache.get( - reserve.liquidityMint, + reserve.liquidity.mint, ) as ParsedAccount; let ltv = 0; let health = 0; @@ -81,31 +86,32 @@ export function useEnrichedLendingObligations() { if (liquidityMint) { const collateralMint = cache.get( - item.collateralReserve.info.liquidityMint, + item.depositReserve.info.liquidity.mint, ); const collateral = fromLamports( collateralToLiquidity( - obligation.info.depositedCollateral, + obligation.info.deposits[0].depositedAmount, item.reserve.info, ), collateralMint?.info, ); const borrowed = wadToLamports( - obligation.info.borrowAmountWad, + obligation.info.borrows[0].borrowedAmountWads, ).toNumber(); const borrowedAmount = simulateMarketOrderFill( borrowed, item.reserve.info, - item.reserve.info.dexMarketOption - ? item.reserve.info.dexMarket - : item.collateralReserve.info.dexMarket, + // @FIXME: aggregator + item.reserve.info.liquidity.aggregatorOption + ? item.reserve.info.liquidity.aggregator + : item.depositReserve.info.liquidity.aggregator, true, ); - const liquidityMintAddress = item.reserve.info.liquidityMint.toBase58(); + const liquidityMintAddress = item.reserve.info.liquidity.mint.toBase58(); const liquidityMint = cache.get( liquidityMintAddress, ) as ParsedAccount; @@ -131,12 +137,13 @@ export function useEnrichedLendingObligations() { health, borrowedInQuote, collateralInQuote, + // @FIXME: BigNumber liquidationThreshold: item.reserve.info.config.liquidationThreshold, - repayName: getTokenName(tokenMap, reserve.liquidityMint), + repayName: getTokenName(tokenMap, reserve.liquidity.mint), collateralName: getTokenName( tokenMap, - collateralReserve.liquidityMint, + depositReserve.liquidity.mint, ), }, } as EnrichedLendingObligation; diff --git a/packages/lending/src/hooks/useLendingMarket.ts b/packages/lending/src/hooks/useLendingMarket.ts index 674a72fb..f5b33c13 100644 --- a/packages/lending/src/hooks/useLendingMarket.ts +++ b/packages/lending/src/hooks/useLendingMarket.ts @@ -1,7 +1,8 @@ import { contexts, ParsedAccount } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useState } from 'react'; -import { LendingMarketParser, LendingMarket } from '../models/lending'; +import { LendingMarket, LendingMarketParser } from '../models'; + const { cache } = contexts.Accounts; const getLendingMarkets = () => { diff --git a/packages/lending/src/hooks/useLendingObligations.ts b/packages/lending/src/hooks/useLendingObligations.ts index ae221b28..292dca52 100644 --- a/packages/lending/src/hooks/useLendingObligations.ts +++ b/packages/lending/src/hooks/useLendingObligations.ts @@ -1,14 +1,15 @@ +import { contexts, ParsedAccount } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useState } from 'react'; -import { LendingObligation, LendingObligationParser } from '../models/lending'; -import { contexts, ParsedAccount } from '@oyster/common'; +import { Obligation, ObligationParser } from '../models'; + const { cache } = contexts.Accounts; const getLendingObligations = () => { return cache - .byParser(LendingObligationParser) + .byParser(ObligationParser) .map(id => cache.get(id)) - .filter(acc => acc !== undefined) as ParsedAccount[]; + .filter(acc => acc !== undefined) as ParsedAccount[]; }; export function useLendingObligations() { @@ -16,7 +17,7 @@ export function useLendingObligations() { useEffect(() => { const dispose = cache.emitter.onCache(args => { - if (args.parser === LendingObligationParser) { + if (args.parser === ObligationParser) { setObligations(getLendingObligations()); } }); @@ -34,13 +35,13 @@ export function useLendingObligations() { export function useLendingObligation(address?: string | PublicKey) { const id = typeof address === 'string' ? address : address?.toBase58(); const [obligationAccount, setObligationAccount] = useState( - cache.get(id || '') as ParsedAccount, + cache.get(id || '') as ParsedAccount, ); useEffect(() => { const dispose = cache.emitter.onCache(args => { if (args.id === id) { - setObligationAccount(cache.get(id) as ParsedAccount); + setObligationAccount(cache.get(id) as ParsedAccount); } }); diff --git a/packages/lending/src/hooks/useLendingReserves.ts b/packages/lending/src/hooks/useLendingReserves.ts index 9cf9a975..4ebc01fe 100644 --- a/packages/lending/src/hooks/useLendingReserves.ts +++ b/packages/lending/src/hooks/useLendingReserves.ts @@ -1,29 +1,27 @@ +import { contexts, getTokenByName, ParsedAccount } from '@oyster/common'; +import { TokenInfo } from '@solana/spl-token-registry'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; -import { LendingReserve, LendingReserveParser } from '../models/lending'; - -import { contexts, utils, ParsedAccount } from '@oyster/common'; -import { TokenInfo } from '@solana/spl-token-registry'; +import { Reserve, ReserveParser } from '../models'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; -const { getTokenByName } = utils; export const getLendingReserves = () => { return cache - .byParser(LendingReserveParser) + .byParser(ReserveParser) .map(id => cache.get(id)) - .filter(acc => acc !== undefined) as ParsedAccount[]; + .filter(acc => acc !== undefined) as ParsedAccount[]; }; export function useLendingReserves() { const [reserveAccounts, setReserveAccounts] = useState< - ParsedAccount[] + ParsedAccount[] >(getLendingReserves()); useEffect(() => { const dispose = cache.emitter.onCache(args => { - if (args.parser === LendingReserveParser) { + if (args.parser === ReserveParser) { setReserveAccounts(getLendingReserves()); } }); @@ -46,7 +44,7 @@ export function useLendingReserve(address?: string | PublicKey) { const token: TokenInfo | null = getTokenByName(tokenMap, address); if (token) { const account = reserveAccounts.filter( - acc => acc.info.liquidityMint.toBase58() === token.address, + acc => acc.info.liquidity.mint.toBase58() === token.address, )[0]; if (account) { addressName = account.pubkey; @@ -59,14 +57,14 @@ export function useLendingReserve(address?: string | PublicKey) { [addressName], ); - const [reserveAccount, setReserveAccount] = useState< - ParsedAccount - >(cache.get(id || '') as ParsedAccount); + const [reserveAccount, setReserveAccount] = useState>( + cache.get(id || '') as ParsedAccount, + ); useEffect(() => { const dispose = cache.emitter.onCache(args => { if (args.id === id) { - setReserveAccount(cache.get(id) as ParsedAccount); + setReserveAccount(cache.get(id) as ParsedAccount); } }); diff --git a/packages/lending/src/hooks/useUserBalance.ts b/packages/lending/src/hooks/useUserBalance.ts index 84a4807e..71dcac50 100644 --- a/packages/lending/src/hooks/useUserBalance.ts +++ b/packages/lending/src/hooks/useUserBalance.ts @@ -1,11 +1,9 @@ +import { contexts, fromLamports, useUserAccounts } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; import { useMarkets } from '../contexts/market'; -import { contexts, utils, hooks } from '@oyster/common'; const { useMint } = contexts.Accounts; -const { useUserAccounts } = hooks; -const { fromLamports } = utils; export function useUserBalance( mintAddress?: PublicKey | string, diff --git a/packages/lending/src/hooks/useUserDeposits.ts b/packages/lending/src/hooks/useUserDeposits.ts index 2c6a24ff..f85643b0 100644 --- a/packages/lending/src/hooks/useUserDeposits.ts +++ b/packages/lending/src/hooks/useUserDeposits.ts @@ -1,21 +1,20 @@ -import { calculateDepositAPY, LendingReserve } from '../models/lending'; -import { useLendingReserves } from './useLendingReserves'; -import { useEffect, useMemo, useState } from 'react'; -import { useMarkets } from '../contexts/market'; -import { calculateCollateralBalance } from './useCollateralBalance'; -import { MintInfo } from '@solana/spl-token'; - import { contexts, - utils, + fromLamports, + getTokenName, ParsedAccount, TokenAccount, - hooks, + useUserAccounts, } from '@oyster/common'; +import { MintInfo } from '@solana/spl-token'; +import { useEffect, useMemo, useState } from 'react'; +import { useMarkets } from '../contexts/market'; +import { calculateDepositAPY, Reserve } from '../models'; +import { calculateCollateralBalance } from './useCollateralBalance'; +import { useLendingReserves } from './useLendingReserves'; + const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; -const { fromLamports, getTokenName } = utils; -const { useUserAccounts } = hooks; export interface UserDeposit { account: TokenAccount; @@ -26,7 +25,7 @@ export interface UserDeposit { name: string; precision: number; }; - reserve: ParsedAccount; + reserve: ParsedAccount; } export function useUserDeposits(exclude?: Set, include?: Set) { @@ -44,16 +43,16 @@ export function useUserDeposits(exclude?: Set, include?: Set) { } if (!include || include.has(id)) { - result.set(item.info.collateralMint.toBase58(), item); + result.set(item.info.collateral.mint.toBase58(), item); } return result; - }, new Map>()); + }, new Map>()); }, [reserveAccounts, exclude, include]); useEffect(() => { const activeMarkets = new Set( - reserveAccounts.map(r => r.info.dexMarket.toBase58()), + reserveAccounts.map(r => r.info.liquidity.aggregator.toBase58()), ); const userDepositsFactory = () => { @@ -62,10 +61,10 @@ export function useUserDeposits(exclude?: Set, include?: Set) { .map(item => { const reserve = reservesByCollateralMint.get( item?.info.mint.toBase58(), - ) as ParsedAccount; + ) as ParsedAccount; let collateralMint = cache.get( - reserve.info.collateralMint, + reserve.info.collateral.mint, ) as ParsedAccount; const amountLamports = calculateCollateralBalance( @@ -73,7 +72,7 @@ export function useUserDeposits(exclude?: Set, include?: Set) { item?.info.amount.toNumber(), ); const amount = fromLamports(amountLamports, collateralMint?.info); - const price = midPriceInUSD(reserve.info.liquidityMint.toBase58()); + const price = midPriceInUSD(reserve.info.liquidity.mint.toBase58()); const amountInQuote = price * amount; return { @@ -82,7 +81,7 @@ export function useUserDeposits(exclude?: Set, include?: Set) { amount, amountInQuote: amountInQuote, apy: calculateDepositAPY(reserve.info), - name: getTokenName(tokenMap, reserve.info.liquidityMint), + name: getTokenName(tokenMap, reserve.info.liquidity.mint), }, reserve, } as UserDeposit; diff --git a/packages/lending/src/hooks/useUserObligationByReserve.ts b/packages/lending/src/hooks/useUserObligationByReserve.ts index de127c6f..b075d37a 100644 --- a/packages/lending/src/hooks/useUserObligationByReserve.ts +++ b/packages/lending/src/hooks/useUserObligationByReserve.ts @@ -1,32 +1,43 @@ +import { PublicKey } from '@solana/web3.js'; import { useMemo } from 'react'; import { useUserObligations } from './useUserObligations'; -import { PublicKey } from '@solana/web3.js'; export function useUserObligationByReserve( borrowReserve?: string | PublicKey, - collateralReserve?: string | PublicKey, + depositReserve?: string | PublicKey, ) { const { userObligations } = useUserObligations(); const userObligationsByReserve = useMemo(() => { - const borrowId = + const borrowReservePubkey = typeof borrowReserve === 'string' ? borrowReserve : borrowReserve?.toBase58(); - const collateralId = - typeof collateralReserve === 'string' - ? collateralReserve - : collateralReserve?.toBase58(); - return userObligations.filter(item => - borrowId && collateralId - ? item.obligation.info.borrowReserve.toBase58() === borrowId && - item.obligation.info.collateralReserve.toBase58() === collateralId - : (borrowId && - item.obligation.info.borrowReserve.toBase58() === borrowId) || - (collateralId && - item.obligation.info.collateralReserve.toBase58() === collateralId), - ); - }, [borrowReserve, collateralReserve, userObligations]); + const depositReservePubkey = + typeof depositReserve === 'string' + ? depositReserve + : depositReserve?.toBase58(); + return userObligations.filter(item => { + // @FIXME: borrows and deposits may be empty + if (borrowReservePubkey && depositReservePubkey) { + return ( + item.obligation.info.borrows[0].borrowReserve.toBase58() === + borrowReservePubkey && + item.obligation.info.deposits[0].depositReserve.toBase58() === + depositReservePubkey + ); + } else { + return ( + (borrowReservePubkey && + item.obligation.info.borrows[0].borrowReserve.toBase58() === + borrowReservePubkey) || + (depositReservePubkey && + item.obligation.info.deposits[0].depositReserve.toBase58() === + depositReservePubkey) + ); + } + }); + }, [borrowReserve, depositReserve, userObligations]); return { userObligationsByReserve, diff --git a/packages/lending/src/hooks/useUserObligations.ts b/packages/lending/src/hooks/useUserObligations.ts index 2672eb41..fb2f00ef 100644 --- a/packages/lending/src/hooks/useUserObligations.ts +++ b/packages/lending/src/hooks/useUserObligations.ts @@ -1,6 +1,7 @@ +import { hooks, TokenAccount } from '@oyster/common'; import { useMemo } from 'react'; import { useEnrichedLendingObligations } from './useEnrichedLendingObligations'; -import { TokenAccount, hooks } from '@oyster/common'; + const { useUserAccounts } = hooks; export function useUserObligations() { @@ -20,6 +21,7 @@ export function useUserObligations() { return []; } + // @FIXME: obligation tokens return obligations .filter( acc => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined, From 0624bb1adb4cb81bd31b75591b82a2a281c83619 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 22 Apr 2021 18:51:58 -0500 Subject: [PATCH 07/58] components --- .../components/BarChartStatistic/index.tsx | 30 +++--- .../src/components/BorrowInput/index.tsx | 93 ++++++++----------- .../src/components/CollateralInput/index.tsx | 55 +++++------ .../components/CollateralSelector/index.tsx | 31 ++++--- .../src/components/DepositInfoLine/index.tsx | 20 ++-- .../src/components/DepositInput/index.tsx | 33 +++---- .../lending/src/components/Layout/index.tsx | 19 ++-- .../src/components/LiquidateInput/index.tsx | 54 +++++------ .../src/components/LoanInfoLine/index.tsx | 32 ++++--- .../components/NothingBorrowedPanel/index.tsx | 24 ++--- .../src/components/PoolPrice/index.tsx | 7 +- .../src/components/RepayInput/index.tsx | 85 ++++++++--------- .../src/components/ReserveStatus/index.tsx | 37 ++++---- .../ReserveUtilizationChart/index.tsx | 19 ++-- .../src/components/RiskSlider/index.tsx | 8 +- .../components/SideReserveOverview/index.tsx | 37 ++++---- .../src/components/SupplyOverview/index.tsx | 7 +- .../src/components/UserLendingCard/index.tsx | 36 +++---- .../src/components/WithdrawInput/index.tsx | 41 ++++---- 19 files changed, 336 insertions(+), 332 deletions(-) diff --git a/packages/lending/src/components/BarChartStatistic/index.tsx b/packages/lending/src/components/BarChartStatistic/index.tsx index fd7ab6b0..ad0bed61 100644 --- a/packages/lending/src/components/BarChartStatistic/index.tsx +++ b/packages/lending/src/components/BarChartStatistic/index.tsx @@ -1,5 +1,5 @@ -import { Statistic } from "antd"; -import React from "react"; +import { Statistic } from 'antd'; +import React from 'react'; export const BarChartStatistic = (props: { items: T[]; @@ -9,14 +9,14 @@ export const BarChartStatistic = (props: { getPct: (item: T) => number; }) => { const colors = [ - "#003f5c", - "#2f4b7c", - "#665191", - "#a05195", - "#d45087", - "#f95d6a", - "#ff7c43", - "#ffa600", + '#003f5c', + '#2f4b7c', + '#665191', + '#a05195', + '#d45087', + '#f95d6a', + '#ff7c43', + '#ffa600', ].reverse(); return ( @@ -25,12 +25,12 @@ export const BarChartStatistic = (props: { valueRender={() => (
{props.items.map((item, i) => ( @@ -38,7 +38,7 @@ export const BarChartStatistic = (props: { key={props.name(item)} title={props.name(item)} style={{ - overflow: "hidden", + overflow: 'hidden', width: `${100 * props.getPct(item)}%`, backgroundColor: (props.color && props.color(item)) || diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx index c1202833..1e0a45d1 100644 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ b/packages/lending/src/components/BorrowInput/index.tsx @@ -1,37 +1,34 @@ +import { + ActionConfirmation, + BackButton, + ConnectButton, + contexts, + ParsedAccount, +} from '@oyster/common'; +import { Card } from 'antd'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { borrowObligationLiquidity } from '../../actions'; +import { LABELS } from '../../constants'; +import { useMidPriceInUSD } from '../../contexts/market'; import { useSliderInput, useUserBalance, useUserDeposits, useUserObligationByReserve, } from '../../hooks'; -import { - BorrowAmountType, - LendingReserve, - LendingReserveParser, -} from '../../models'; -import { Card } from 'antd'; -import { - contexts, - ParsedAccount, - ConnectButton, - BackButton, - ActionConfirmation, -} from '@oyster/common'; - -import { borrow } from '../../actions'; -import './style.less'; -import { LABELS } from '../../constants'; +import { Reserve, ReserveParser } from '../../models'; import CollateralInput from '../CollateralInput'; -import { useMidPriceInUSD } from '../../contexts/market'; import { RiskSlider } from '../RiskSlider'; +import './style.less'; + const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; const { cache } = contexts.Accounts; export const BorrowInput = (props: { className?: string; - reserve: ParsedAccount; + reserve: ParsedAccount; }) => { const connection = useConnection(); const { wallet } = useWallet(); @@ -42,26 +39,26 @@ export const BorrowInput = (props: { const borrowReserve = props.reserve; - const [collateralReserveKey, setCollateralReserveKey] = useState(); + const [depositReserveKey, setCollateralReserveKey] = useState(); - const collateralReserve = useMemo(() => { + const depositReserve = useMemo(() => { const id: string = cache - .byParser(LendingReserveParser) - .find(acc => acc === collateralReserveKey) || ''; + .byParser(ReserveParser) + .find(acc => acc === depositReserveKey) || ''; - return cache.get(id) as ParsedAccount; - }, [collateralReserveKey]); + return cache.get(id) as ParsedAccount; + }, [depositReserveKey]); const borrowPrice = useMidPriceInUSD( - borrowReserve.info.liquidityMint.toBase58(), + borrowReserve.info.liquidity.mint.toBase58(), ).price; const collateralPrice = useMidPriceInUSD( - collateralReserve?.info.liquidityMint.toBase58(), + depositReserve?.info.liquidity.mint.toBase58(), )?.price; const include = useMemo( - () => new Set([collateralReserve?.pubkey.toBase58()]), - [collateralReserve], + () => new Set([depositReserve?.pubkey.toBase58()]), + [depositReserve], ); const exclude = useMemo(() => new Set([]), []); @@ -84,7 +81,7 @@ export const BorrowInput = (props: { const { value, setValue, pct } = useSliderInput(convert); useEffect(() => { - if (collateralReserve && lastTyped === 'collateral') { + if (depositReserve && lastTyped === 'collateral') { const ltv = borrowReserve.info.config.loanToValueRatio / 100; if (collateralValue) { @@ -98,7 +95,7 @@ export const BorrowInput = (props: { } }, [ lastTyped, - collateralReserve, + depositReserve, collateralPrice, borrowPrice, borrowReserve, @@ -107,7 +104,7 @@ export const BorrowInput = (props: { ]); useEffect(() => { - if (collateralReserve && lastTyped === 'borrow') { + if (depositReserve && lastTyped === 'borrow') { const ltv = borrowReserve.info.config.loanToValueRatio / 100; if (value) { @@ -121,7 +118,7 @@ export const BorrowInput = (props: { } }, [ lastTyped, - collateralReserve, + depositReserve, collateralPrice, borrowPrice, borrowReserve, @@ -130,13 +127,13 @@ export const BorrowInput = (props: { const { userObligationsByReserve } = useUserObligationByReserve( borrowReserve?.pubkey, - collateralReserve?.pubkey, + depositReserve?.pubkey, ); - const { accounts: fromAccounts } = useUserBalance( - collateralReserve?.info.collateralMint, + const { accounts: sourceAccounts } = useUserBalance( + depositReserve?.info.collateral.mint, ); const onBorrow = useCallback(() => { - if (!collateralReserve) { + if (!depositReserve) { return; } @@ -144,25 +141,15 @@ export const BorrowInput = (props: { (async () => { try { - await borrow( + await borrowObligationLiquidity( connection, wallet, - - fromAccounts[0], parseFloat(value), - // TODO: switch to collateral when user is using slider - BorrowAmountType.LiquidityBorrowAmount, borrowReserve, - collateralReserve, - - // TODO: select exsisting obligations by collateral reserve + // TODO: select existing obligations by collateral reserve userObligationsByReserve.length > 0 ? userObligationsByReserve[0].obligation.account : undefined, - - userObligationsByReserve.length > 0 - ? userObligationsByReserve[0].userAccounts[0].pubkey - : undefined, ); setValue(''); @@ -179,9 +166,9 @@ export const BorrowInput = (props: { wallet, value, setValue, - collateralReserve, + depositReserve, borrowReserve, - fromAccounts, + sourceAccounts, userObligationsByReserve, setPendingTx, setShowConfirmation, @@ -257,9 +244,9 @@ export const BorrowInput = (props: { type="primary" onClick={onBorrow} loading={pendingTx} - disabled={fromAccounts.length === 0} + disabled={sourceAccounts.length === 0} > - {fromAccounts.length === 0 + {sourceAccounts.length === 0 ? LABELS.NO_COLLATERAL : LABELS.BORROW_ACTION} diff --git a/packages/lending/src/components/CollateralInput/index.tsx b/packages/lending/src/components/CollateralInput/index.tsx index aaefe70a..e838a892 100644 --- a/packages/lending/src/components/CollateralInput/index.tsx +++ b/packages/lending/src/components/CollateralInput/index.tsx @@ -1,18 +1,21 @@ +import { + contexts, + getTokenName, + NumericInput, + ParsedAccount, + TokenDisplay, + TokenIcon, +} from '@oyster/common'; +import { Card, Select } from 'antd'; import React, { useEffect, useState } from 'react'; -import { contexts, utils, ParsedAccount, NumericInput, TokenIcon, TokenDisplay } from '@oyster/common'; import { useLendingReserves, useUserBalance, useUserDeposits, } from '../../hooks'; -import { - LendingReserve, - LendingMarket, - LendingReserveParser, -} from '../../models'; -import { Card, Select } from 'antd'; +import { LendingMarket, Reserve, ReserveParser } from '../../models'; import './style.less'; -const { getTokenName } = utils; + const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; @@ -23,7 +26,7 @@ const { Option } = Select; export default function CollateralInput(props: { title: string; amount?: number | null; - reserve: LendingReserve; + reserve: Reserve; disabled?: boolean; onCollateralReserve?: (id: string) => void; onLeverage?: (leverage: number) => void; @@ -34,10 +37,10 @@ export default function CollateralInput(props: { showLeverageSelector?: boolean; leverage?: number; }) { - const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); + const { balance: tokenBalance } = useUserBalance(props.reserve.liquidity.mint); const { reserveAccounts } = useLendingReserves(); const { tokenMap } = useConnectionConfig(); - const [collateralReserve, setCollateralReserve] = useState(); + const [depositReserve, setCollateralReserve] = useState(); const [balance, setBalance] = useState(0); const [lastAmount, setLastAmount] = useState(''); const userDeposits = useUserDeposits(); @@ -48,29 +51,29 @@ export default function CollateralInput(props: { } else { const id: string = cache - .byParser(LendingReserveParser) - .find(acc => acc === collateralReserve) || ''; - const parser = cache.get(id) as ParsedAccount; + .byParser(ReserveParser) + .find(acc => acc === depositReserve) || ''; + const parser = cache.get(id) as ParsedAccount; if (parser) { const collateralDeposit = userDeposits.userDeposits.find( u => - u.reserve.info.liquidityMint.toBase58() === - parser.info.liquidityMint.toBase58(), + u.reserve.info.liquidity.mint.toBase58() === + parser.info.liquidity.mint.toBase58(), ); if (collateralDeposit) setBalance(collateralDeposit.info.amount); else setBalance(0); } } - }, [collateralReserve, userDeposits, tokenBalance, props.useWalletBalance]); + }, [depositReserve, userDeposits, tokenBalance, props.useWalletBalance]); const market = cache.get( props.reserve.lendingMarket, ) as ParsedAccount; if (!market) return null; - const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals( - market?.info?.quoteMint, + const onlyQuoteAllowed = !props.reserve?.liquidity.mint?.equals( + market?.info?.quoteTokenMint, ); const filteredReserveAccounts = reserveAccounts @@ -78,11 +81,11 @@ export default function CollateralInput(props: { .filter( reserve => !onlyQuoteAllowed || - reserve.info.liquidityMint.equals(market.info.quoteMint), + reserve.info.liquidity.mint.equals(market.info.quoteTokenMint), ); if ( - !collateralReserve && + !depositReserve && props.useFirstReserve && filteredReserveAccounts.length ) { @@ -90,7 +93,7 @@ export default function CollateralInput(props: { setCollateralReserve(address); } const renderReserveAccounts = filteredReserveAccounts.map(reserve => { - const mint = reserve.info.liquidityMint.toBase58(); + const mint = reserve.info.liquidity.mint.toBase58(); const address = reserve.pubkey.toBase58(); const name = getTokenName(tokenMap, mint); return ( @@ -187,7 +190,7 @@ export default function CollateralInput(props: { showSearch style={{ minWidth: 150 }} placeholder="CCY" - value={collateralReserve} + value={depositReserve} onChange={item => { if (props.onCollateralReserve) props.onCollateralReserve(item); setCollateralReserve(item); @@ -200,12 +203,12 @@ export default function CollateralInput(props: { ) : ( )} diff --git a/packages/lending/src/components/CollateralSelector/index.tsx b/packages/lending/src/components/CollateralSelector/index.tsx index 12842ce8..5fafe1bf 100644 --- a/packages/lending/src/components/CollateralSelector/index.tsx +++ b/packages/lending/src/components/CollateralSelector/index.tsx @@ -1,10 +1,15 @@ +import { + contexts, + formatAmount, + getTokenName, + ParsedAccount, + TokenIcon, +} from '@oyster/common'; +import { Select } from 'antd'; import React from 'react'; import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks'; -import { LendingMarket, LendingReserve } from '../../models'; -import { Select } from 'antd'; -import { contexts, utils, ParsedAccount, TokenIcon } from '@oyster/common'; +import { LendingMarket, Reserve } from '../../models'; -const { getTokenName, formatAmount } = utils; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; @@ -12,7 +17,7 @@ const { Option } = Select; export const CollateralItem = (props: { mint: string; - reserve: ParsedAccount; + reserve: ParsedAccount; userDeposit?: UserDeposit; name: string; }) => { @@ -32,8 +37,8 @@ export const CollateralItem = (props: { }; export const CollateralSelector = (props: { - reserve: LendingReserve; - collateralReserve?: string; + reserve: Reserve; + depositReserve?: string; disabled?: boolean; onCollateralReserve?: (id: string) => void; }) => { @@ -46,10 +51,10 @@ export const CollateralSelector = (props: { ) as ParsedAccount; if (!market) return null; - const quoteMintAddress = market?.info?.quoteMint?.toBase58(); + const quoteTokenMintAddress = market?.info?.quoteTokenMint?.toBase58(); const onlyQuoteAllowed = - props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress; + props.reserve?.liquidity.mint?.toBase58() !== quoteTokenMintAddress; return ( ) : ( )} diff --git a/packages/lending/src/components/CollateralSelector/index.tsx b/packages/lending/src/components/CollateralSelector/index.tsx index 5fafe1bf..8e06eb2d 100644 --- a/packages/lending/src/components/CollateralSelector/index.tsx +++ b/packages/lending/src/components/CollateralSelector/index.tsx @@ -54,7 +54,7 @@ export const CollateralSelector = (props: { const quoteTokenMintAddress = market?.info?.quoteTokenMint?.toBase58(); const onlyQuoteAllowed = - props.reserve?.liquidity.mint?.toBase58() !== quoteTokenMintAddress; + props.reserve?.liquidity.mintPubkey?.toBase58() !== quoteTokenMintAddress; return ( {reserveAccounts .filter(reserve => reserve.info !== props.reserve) - .filter( - reserve => - !onlyQuoteAllowed || - reserve.info.liquidity.mintPubkey.equals( - market.info.quoteTokenMint, - ), - ) .map(reserve => { const mint = reserve.info.liquidity.mintPubkey.toBase58(); const address = reserve.pubkey.toBase58(); diff --git a/packages/lending/src/hooks/useBorrowingPower.ts b/packages/lending/src/hooks/useBorrowingPower.ts index d6f49fc8..2b3fba2e 100644 --- a/packages/lending/src/hooks/useBorrowingPower.ts +++ b/packages/lending/src/hooks/useBorrowingPower.ts @@ -25,22 +25,9 @@ export function useBorrowingPower( const liquidityMint = reserve?.info.liquidity.mintPubkey; const liquidityMintAddress = liquidityMint?.toBase58(); - const market = useLendingMarket(reserve?.info.lendingMarket); - - const quoteTokenMintAddess = market?.info?.quoteTokenMint?.toBase58(); - - // TODO: remove once cross-collateral is supported - const onlyQuoteAllowed = liquidityMintAddress !== quoteTokenMintAddess; const exclude = useMemo(() => new Set([key]), [key]); - const inlcude = useMemo(() => { - const quoteReserve = getLendingReserves().find( - r => r.info.liquidity.mintPubkey.toBase58() === quoteTokenMintAddess, - ); - return onlyQuoteAllowed && quoteReserve - ? new Set([quoteReserve.pubkey.toBase58()]) - : undefined; - }, [onlyQuoteAllowed, quoteTokenMintAddess]); + const inlcude = undefined; const { totalInQuote } = useUserDeposits(exclude, inlcude); @@ -52,15 +39,6 @@ export function useBorrowingPower( const utilization = totalDeposits === 0 ? 0 : loansValue / totalDeposits; - // amounts already expressed as quite mint - if (liquidityMintAddress === quoteTokenMintAddess) { - return { - borrowingPower: totalInQuote, - totalInQuote, - utilization, - }; - } - return { borrowingPower: totalInQuote / price, totalInQuote, From 3d021c1b910665d911ec14ce46043532dbd61c3b Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 15:30:06 -0500 Subject: [PATCH 16/58] add pyth program id --- packages/lending/src/models/instructions/initLendingMarket.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/lending/src/models/instructions/initLendingMarket.ts b/packages/lending/src/models/instructions/initLendingMarket.ts index b933e9b3..15e19b8d 100644 --- a/packages/lending/src/models/instructions/initLendingMarket.ts +++ b/packages/lending/src/models/instructions/initLendingMarket.ts @@ -10,8 +10,7 @@ import { LendingInstruction } from './instruction'; // @TODO: move to @oyster/common export const ORACLE_PROGRAM_ID = new PublicKey( -// @FIXME: replace with Pyth - 'XXX', + '5mkqGkkWSaSk2NL9p4XptwEQu4d5jFTJiurbbzdqYexF', ); /// 0 From c054734e7df7fed7e5289a0324d3588780b65b7e Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 15:30:17 -0500 Subject: [PATCH 17/58] oracle/dex fixes --- packages/lending/src/contexts/lending.tsx | 5 ++--- packages/lending/src/contexts/market.tsx | 1 + .../src/hooks/useEnrichedLendingObligations.ts | 11 ++--------- packages/lending/src/models/state/reserve.ts | 2 -- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index 787d4f73..dbe3318d 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -106,10 +106,9 @@ export const useLending = () => { MintParser, ), // ignore dex if its not set + // @FIXME: not a dex market cache.registerParser( - acc?.info.liquidity.oracleOption - ? acc?.info.liquidity.oraclePubkey.toBase58() - : '', + acc?.info.liquidity.oraclePubkey.toBase58(), DexMarketParser, ), ].filter(_ => _); diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index bf71d181..d50f817d 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -480,6 +480,7 @@ function calculateAirdropYield( return airdropYield; } +// @FIXME: replace with market price export const useMidPriceInUSD = (mint: string) => { const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext( MarketsContext, diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index b1e7cde8..e5c22aac 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -101,15 +101,8 @@ export function useEnrichedLendingObligations() { obligation.info.borrows[0].borrowedAmountWads, ).toNumber(); - const borrowedAmount = simulateMarketOrderFill( - borrowed, - item.reserve.info, - // @FIXME: oracle - item.reserve.info.liquidity.oracleOption - ? item.reserve.info.liquidity.oraclePubkey - : item.depositReserve.info.liquidity.oraclePubkey, - true, - ); + // @FIXME: remove dex market + const borrowedAmount = borrowed; const liquidityMintAddress = item.reserve.info.liquidity.mintPubkey.toBase58(); const liquidityMint = cache.get( diff --git a/packages/lending/src/models/state/reserve.ts b/packages/lending/src/models/state/reserve.ts index 536bccac..889a18f0 100644 --- a/packages/lending/src/models/state/reserve.ts +++ b/packages/lending/src/models/state/reserve.ts @@ -19,8 +19,6 @@ export interface ReserveLiquidity { mintDecimals: number; supplyPubkey: PublicKey; feeReceiver: PublicKey; - // @FIXME: oracle option - oracleOption: number; oraclePubkey: PublicKey; availableAmount: BN; borrowedAmountWads: BN; // decimals From df62cdc8da897018f22d496bb090dc53c853a225 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 15:31:16 -0500 Subject: [PATCH 18/58] fix all actions --- .../src/actions/borrowObligationLiquidity.tsx | 7 +-- .../actions/depositObligationCollateral.tsx | 12 +----- .../src/actions/depositReserveLiquidity.tsx | 11 ++--- packages/lending/src/actions/index.ts | 11 ++++- .../lending/src/actions/initObligation.tsx | 6 +-- packages/lending/src/actions/initReserve.tsx | 19 +++----- .../src/actions/liquidateObligation.tsx | 43 ++++++++----------- .../src/actions/redeemReserveCollateral.tsx | 8 +--- .../src/actions/repayObligationLiquidity.tsx | 26 +++++------ .../actions/withdrawObligationCollateral.tsx | 29 ++++++------- 10 files changed, 66 insertions(+), 106 deletions(-) diff --git a/packages/lending/src/actions/borrowObligationLiquidity.tsx b/packages/lending/src/actions/borrowObligationLiquidity.tsx index 9ea7e22f..25c02538 100644 --- a/packages/lending/src/actions/borrowObligationLiquidity.tsx +++ b/packages/lending/src/actions/borrowObligationLiquidity.tsx @@ -25,7 +25,6 @@ import { const { cache, MintParser } = contexts.Accounts; const { sendTransaction } = contexts.Connection; -// @FIXME export const borrowObligationLiquidity = async ( connection: Connection, wallet: any, @@ -105,17 +104,15 @@ export const borrowObligationLiquidity = async ( type: 'warn', }); - // @FIXME: signers signers = []; instructions = []; cleanupInstructions = [...finalCleanupInstructions]; instructions.push( + // @TODO: refresh all obligation reserves refreshReserveInstruction( borrowReserve.pubkey, - borrowReserve.info.liquidity.oracleOption - ? borrowReserve.info.liquidity.oraclePubkey - : undefined, + borrowReserve.info.liquidity.oraclePubkey, ), refreshObligationInstruction( obligation.pubkey, diff --git a/packages/lending/src/actions/depositObligationCollateral.tsx b/packages/lending/src/actions/depositObligationCollateral.tsx index d5fa3b16..5120499d 100644 --- a/packages/lending/src/actions/depositObligationCollateral.tsx +++ b/packages/lending/src/actions/depositObligationCollateral.tsx @@ -1,5 +1,5 @@ import { - contexts, + sendTransaction, LENDING_PROGRAM_ID, models, notify, @@ -13,14 +13,11 @@ import { } from '@solana/web3.js'; import { depositObligationCollateralInstruction, - refreshReserveInstruction, Reserve, } from '../models'; const { approve } = models; -const { sendTransaction } = contexts.Connection; -// @FIXME export const depositObligationCollateral = async ( connection: Connection, wallet: any, @@ -60,12 +57,6 @@ export const depositObligationCollateral = async ( signers.push(transferAuthority); instructions.push( - refreshReserveInstruction( - reserveAddress, - reserve.liquidity.oracleOption - ? reserve.liquidity.oraclePubkey - : undefined, - ), depositObligationCollateralInstruction( collateralAmount, sourceCollateral, @@ -74,7 +65,6 @@ export const depositObligationCollateral = async ( obligationAddress, reserve.lendingMarket, lendingMarketAuthority, - // @FIXME: wallet must sign wallet.publicKey, transferAuthority.publicKey, ), diff --git a/packages/lending/src/actions/depositReserveLiquidity.tsx b/packages/lending/src/actions/depositReserveLiquidity.tsx index ca7c95cc..7cdd1d63 100644 --- a/packages/lending/src/actions/depositReserveLiquidity.tsx +++ b/packages/lending/src/actions/depositReserveLiquidity.tsx @@ -1,9 +1,10 @@ import { - actions, - contexts, + ensureSplAccount, + findOrCreateAccountByMint, LENDING_PROGRAM_ID, models, notify, + sendTransaction, TokenAccount, } from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; @@ -19,8 +20,6 @@ import { Reserve, } from '../models'; -const { sendTransaction } = contexts.Connection; -const { ensureSplAccount, findOrCreateAccountByMint } = actions; const { approve } = models; export const depositReserveLiquidity = async ( @@ -84,9 +83,7 @@ export const depositReserveLiquidity = async ( instructions.push( refreshReserveInstruction( reserveAddress, - reserve.liquidity.oracleOption - ? reserve.liquidity.oraclePubkey - : undefined, + reserve.liquidity.oraclePubkey, ), depositReserveLiquidityInstruction( liquidityAmount, diff --git a/packages/lending/src/actions/index.ts b/packages/lending/src/actions/index.ts index 6ad00a6b..cf62d2f3 100644 --- a/packages/lending/src/actions/index.ts +++ b/packages/lending/src/actions/index.ts @@ -1,7 +1,14 @@ export { borrowObligationLiquidity } from './borrowObligationLiquidity'; +export { createAccount } from './createAccount'; +export { createObligation } from './createObligation'; +export { createReserve } from './createReserve'; +export { depositObligationCollateral } from './depositObligationCollateral'; export { depositReserveLiquidity } from './depositReserveLiquidity'; -export { repayObligationLiquidity } from './repayObligationLiquidity'; -export { redeemReserveCollateral } from './redeemReserveCollateral'; +export { initObligation } from './initObligation'; +export { initReserve } from './initReserve'; export { liquidateObligation } from './liquidateObligation'; +export { redeemReserveCollateral } from './redeemReserveCollateral'; +export { repayObligationLiquidity } from './repayObligationLiquidity'; +export { withdrawObligationCollateral } from './withdrawObligationCollateral'; // @TODO: add actions for other instructions diff --git a/packages/lending/src/actions/initObligation.tsx b/packages/lending/src/actions/initObligation.tsx index 7ed2d2cc..d0f2772a 100644 --- a/packages/lending/src/actions/initObligation.tsx +++ b/packages/lending/src/actions/initObligation.tsx @@ -1,4 +1,4 @@ -import { contexts, notify } from '@oyster/common'; +import { sendTransaction, notify } from '@oyster/common'; import { Account, Connection, @@ -7,8 +7,6 @@ import { } from '@solana/web3.js'; import { initObligationInstruction, Obligation } from '../models'; -const { sendTransaction } = contexts.Connection; - export const initObligation = async ( connection: Connection, wallet: any, @@ -26,14 +24,12 @@ export const initObligation = async ( const instructions: TransactionInstruction[] = []; const cleanupInstructions: TransactionInstruction[] = []; - // @FIXME: obligation owner must sign signers.push(wallet.info.account); instructions.push( initObligationInstruction( obligationAddress, obligation.lendingMarket, - // @FIXME: need to sign with wallet wallet.publicKey ), ); diff --git a/packages/lending/src/actions/initReserve.tsx b/packages/lending/src/actions/initReserve.tsx index 1540b616..59166037 100644 --- a/packages/lending/src/actions/initReserve.tsx +++ b/packages/lending/src/actions/initReserve.tsx @@ -1,6 +1,7 @@ import { - actions, - contexts, + createUninitializedAccount, + ensureSplAccount, + sendTransaction, LENDING_PROGRAM_ID, models, notify, @@ -15,21 +16,16 @@ import { } from '@solana/web3.js'; import { initReserveInstruction, Reserve } from '../models'; -const { sendTransaction } = contexts.Connection; -const { - createUninitializedAccount, - ensureSplAccount, -} = actions; const { approve } = models; export const initReserve = async ( connection: Connection, - // @FIXME: wallet must be lending market owner wallet: any, liquidityAmount: number, source: TokenAccount, reserve: Reserve, reserveAddress: PublicKey, + pythProduct: PublicKey, ) => { notify({ message: 'Initializing reserve...', @@ -88,16 +84,15 @@ export const initReserve = async ( reserve.liquidity.mintPubkey, reserve.liquidity.supplyPubkey, reserve.liquidity.feeReceiver, + pythProduct, + reserve.liquidity.oraclePubkey, reserve.collateral.mintPubkey, reserve.collateral.supplyPubkey, reserve.lendingMarket, lendingMarketAuthority, - // @FIXME: need to sign with wallet as lending market owner + // @TODO: must provide owner as arg if wallet isn't lending market owner wallet.publicKey, transferAuthority.publicKey, - reserve.liquidity.oracleOption - ? reserve.liquidity.oraclePubkey - : undefined, ), ); diff --git a/packages/lending/src/actions/liquidateObligation.tsx b/packages/lending/src/actions/liquidateObligation.tsx index 6afc81ef..1f884266 100644 --- a/packages/lending/src/actions/liquidateObligation.tsx +++ b/packages/lending/src/actions/liquidateObligation.tsx @@ -1,12 +1,11 @@ import { - contexts, - createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint, LENDING_PROGRAM_ID, models, notify, ParsedAccount, + sendTransaction, TokenAccount, } from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; @@ -20,15 +19,13 @@ import { LendingMarket, liquidateObligationInstruction, Obligation, + refreshObligationInstruction, refreshReserveInstruction, - Reserve, + Reserve } from '../models'; -const { cache } = contexts.Accounts; const { approve } = models; -const { sendTransaction } = contexts.Connection; -// @FIXME export const liquidateObligation = async ( connection: Connection, wallet: any, @@ -88,27 +85,21 @@ export const liquidateObligation = async ( signers, ); - // @FIXME: oracle - const oracleAddress = repayReserve.info.liquidity.oracleOption - ? repayReserve.info.liquidity.oraclePubkey - : withdrawReserve.info.liquidity.oraclePubkey; - const oracle = cache.get(oracleAddress); - - if (!oracle) { - throw new Error(`Dex market doesn't exist.`); - } - - const market = cache.get( - withdrawReserve.info.lendingMarket, - ) as ParsedAccount; - - instructions.push( - // @FIXME: oracle needed - refreshReserveInstruction(repayReserve.pubkey), - refreshReserveInstruction(withdrawReserve.pubkey), - ); - instructions.push( + // @TODO: refresh all obligation reserves + refreshReserveInstruction( + repayReserve.pubkey, + repayReserve.info.liquidity.oraclePubkey, + ), + refreshReserveInstruction( + withdrawReserve.pubkey, + withdrawReserve.info.liquidity.oraclePubkey, + ), + refreshObligationInstruction( + obligation.pubkey, + obligation.info.deposits.map(collateral => collateral.depositReserve), + obligation.info.borrows.map(liquidity => liquidity.borrowReserve), + ), liquidateObligationInstruction( liquidityAmount, sourceAccount, diff --git a/packages/lending/src/actions/redeemReserveCollateral.tsx b/packages/lending/src/actions/redeemReserveCollateral.tsx index 7ad64683..84eaa9b1 100644 --- a/packages/lending/src/actions/redeemReserveCollateral.tsx +++ b/packages/lending/src/actions/redeemReserveCollateral.tsx @@ -1,9 +1,9 @@ import { - contexts, findOrCreateAccountByMint, LENDING_PROGRAM_ID, models, notify, + sendTransaction, TokenAccount, } from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; @@ -20,9 +20,7 @@ import { } from '../models'; const { approve } = models; -const { sendTransaction } = contexts.Connection; -// @FIXME export const redeemReserveCollateral = async ( connection: Connection, wallet: any, @@ -78,9 +76,7 @@ export const redeemReserveCollateral = async ( instructions.push( refreshReserveInstruction( reserveAddress, - reserve.liquidity.oracleOption - ? reserve.liquidity.oraclePubkey - : undefined, + reserve.liquidity.oraclePubkey, ), redeemReserveCollateralInstruction( collateralAmount, diff --git a/packages/lending/src/actions/repayObligationLiquidity.tsx b/packages/lending/src/actions/repayObligationLiquidity.tsx index 7ba9ec6b..6480689c 100644 --- a/packages/lending/src/actions/repayObligationLiquidity.tsx +++ b/packages/lending/src/actions/repayObligationLiquidity.tsx @@ -1,10 +1,9 @@ import { - contexts, createTokenAccount, - LENDING_PROGRAM_ID, models, notify, ParsedAccount, + sendTransaction, TOKEN_PROGRAM_ID, TokenAccount, } from '@oyster/common'; @@ -12,20 +11,17 @@ import { AccountLayout, NATIVE_MINT, Token } from '@solana/spl-token'; import { Account, Connection, - PublicKey, TransactionInstruction, } from '@solana/web3.js'; import { - Obligation, + Obligation, refreshObligationInstruction, refreshReserveInstruction, repayObligationLiquidityInstruction, - Reserve, + Reserve } from '../models'; const { approve } = models; -const { sendTransaction } = contexts.Connection; -// @FIXME export const repayObligationLiquidity = async ( connection: Connection, wallet: any, @@ -49,11 +45,6 @@ export const repayObligationLiquidity = async ( AccountLayout.span, ); - const [lendingMarketAuthority] = await PublicKey.findProgramAddress( - [repayReserve.info.lendingMarket.toBuffer()], - LENDING_PROGRAM_ID, - ); - let sourceLiquidity = source.pubkey; if ( wallet.publicKey.equals(sourceLiquidity) && @@ -90,11 +81,15 @@ export const repayObligationLiquidity = async ( signers.push(transferAuthority); instructions.push( + // @TODO: remove after refresh of obligation + reserves on repay is no longer required refreshReserveInstruction( repayReserve.pubkey, - repayReserve.info.liquidity.oracleOption - ? repayReserve.info.liquidity.oraclePubkey - : undefined, + repayReserve.info.liquidity.oraclePubkey, + ), + refreshObligationInstruction( + obligation.pubkey, + obligation.info.deposits.map(collateral => collateral.depositReserve), + obligation.info.borrows.map(liquidity => liquidity.borrowReserve), ), repayObligationLiquidityInstruction( liquidityAmount, @@ -103,7 +98,6 @@ export const repayObligationLiquidity = async ( repayReserve.pubkey, obligation.pubkey, repayReserve.info.lendingMarket, - lendingMarketAuthority, transferAuthority.publicKey, ), ); diff --git a/packages/lending/src/actions/withdrawObligationCollateral.tsx b/packages/lending/src/actions/withdrawObligationCollateral.tsx index 555e99f8..55c19728 100644 --- a/packages/lending/src/actions/withdrawObligationCollateral.tsx +++ b/packages/lending/src/actions/withdrawObligationCollateral.tsx @@ -1,10 +1,9 @@ import { - contexts, findOrCreateAccountByMint, LENDING_PROGRAM_ID, - models, - notify, - TokenAccount, + notify, ParsedAccount, + sendTransaction, + TokenAccount } from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; import { @@ -16,13 +15,9 @@ import { import { withdrawObligationCollateralInstruction, refreshReserveInstruction, - Reserve, + Reserve, refreshObligationInstruction, Obligation } from '../models'; -const { approve } = models; -const { sendTransaction } = contexts.Connection; - -// @FIXME export const withdrawObligationCollateral = async ( connection: Connection, wallet: any, @@ -30,7 +25,7 @@ export const withdrawObligationCollateral = async ( source: TokenAccount, reserve: Reserve, reserveAddress: PublicKey, - obligationAddress: PublicKey, + obligation: ParsedAccount, ) => { notify({ message: 'Withdrawing collateral...', @@ -52,7 +47,6 @@ export const withdrawObligationCollateral = async ( LENDING_PROGRAM_ID, ); - // @FIXME: wallet must sign as obligation owner signers.push(wallet.info.account); // get destination account @@ -67,21 +61,24 @@ export const withdrawObligationCollateral = async ( ); instructions.push( + // @TODO: refresh all obligation reserves refreshReserveInstruction( reserveAddress, - reserve.liquidity.oracleOption - ? reserve.liquidity.oraclePubkey - : undefined, + reserve.liquidity.oraclePubkey, + ), + refreshObligationInstruction( + obligation.pubkey, + obligation.info.deposits.map(collateral => collateral.depositReserve), + obligation.info.borrows.map(liquidity => liquidity.borrowReserve), ), withdrawObligationCollateralInstruction( collateralAmount, reserve.collateral.supplyPubkey, destinationCollateral, reserveAddress, - obligationAddress, + obligation.pubkey, reserve.lendingMarket, lendingMarketAuthority, - // @FIXME: wallet must sign wallet.publicKey ), ); From 5e6daf6941c9194dfe45c3cc5757c850c3954eab Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 17:46:10 -0500 Subject: [PATCH 19/58] add and fix buffer layout types --- .../lending/src/models/state/lastUpdate.ts | 10 +- .../lending/src/models/state/lendingMarket.ts | 8 +- .../lending/src/models/state/obligation.ts | 40 ++++---- packages/lending/src/models/state/reserve.ts | 4 +- packages/lending/src/types/buffer-layout.d.ts | 91 +++++++++++++++++-- packages/lending/src/types/jazzicon.d.ts | 4 + packages/lending/src/utils/layout.ts | 56 ++++++++---- packages/lending/types/buffer-layout.d.ts | 91 +++++++++++++++++-- packages/lending/types/jazzicon.d.ts | 4 + .../lending/types/sol-wallet-adapter.d.ts | 4 + 10 files changed, 250 insertions(+), 62 deletions(-) create mode 100644 packages/lending/src/types/jazzicon.d.ts create mode 100644 packages/lending/types/jazzicon.d.ts create mode 100644 packages/lending/types/sol-wallet-adapter.d.ts diff --git a/packages/lending/src/models/state/lastUpdate.ts b/packages/lending/src/models/state/lastUpdate.ts index 27e0eea2..885cbfa1 100644 --- a/packages/lending/src/models/state/lastUpdate.ts +++ b/packages/lending/src/models/state/lastUpdate.ts @@ -2,12 +2,12 @@ import BN from 'bn.js'; import * as BufferLayout from 'buffer-layout'; import * as Layout from '../../utils/layout'; -export const LastUpdateLayout: typeof BufferLayout.Structure = BufferLayout.struct( - [Layout.uint64('slot'), BufferLayout.u8('stale')], - 'lastUpdate' -); - export interface LastUpdate { slot: BN; stale: boolean; } + +export const LastUpdateLayout = BufferLayout.struct( + [Layout.uint64('slot'), BufferLayout.u8('stale')], + 'lastUpdate' +); diff --git a/packages/lending/src/models/state/lendingMarket.ts b/packages/lending/src/models/state/lendingMarket.ts index 0d981a20..9562b5b6 100644 --- a/packages/lending/src/models/state/lendingMarket.ts +++ b/packages/lending/src/models/state/lendingMarket.ts @@ -4,12 +4,14 @@ import * as Layout from '../../utils/layout'; export interface LendingMarket { version: number; - isInitialized: boolean; + bumpSeed: number; + owner: PublicKey; quoteCurrency: Buffer; tokenProgramId: PublicKey; + oracleProgramId: PublicKey; } -export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct( +export const LendingMarketLayout = BufferLayout.struct( [ BufferLayout.u8('version'), BufferLayout.u8('bumpSeed'), @@ -30,7 +32,7 @@ export const LendingMarketParser = ( info: AccountInfo, ) => { const buffer = Buffer.from(info.data); - const lendingMarket = LendingMarketLayout.decode(buffer) as LendingMarket; + const lendingMarket = LendingMarketLayout.decode(buffer); const details = { pubkey, diff --git a/packages/lending/src/models/state/obligation.ts b/packages/lending/src/models/state/obligation.ts index 0c774085..1540f45a 100644 --- a/packages/lending/src/models/state/obligation.ts +++ b/packages/lending/src/models/state/obligation.ts @@ -32,7 +32,21 @@ export interface ObligationLiquidity { marketValue: BN; // decimals } -export const ObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct( +export interface ProtoObligation { + version: number; + lastUpdate: LastUpdate; + lendingMarket: PublicKey; + owner: PublicKey; + depositedValue: BN; // decimals + borrowedValue: BN; // decimals + allowedBorrowValue: BN; // decimals + unhealthyBorrowValue: BN; // decimals + depositsLen: number; + borrowsLen: number; + dataFlat: Buffer; +} + +export const ObligationLayout = BufferLayout.struct( [ BufferLayout.u8('version'), @@ -51,7 +65,7 @@ export const ObligationLayout: typeof BufferLayout.Structure = BufferLayout.stru ], ); -export const ObligationCollateralLayout: typeof BufferLayout.Structure = BufferLayout.struct( +export const ObligationCollateralLayout = BufferLayout.struct( [ Layout.publicKey('depositReserve'), Layout.uint64('depositedAmount'), @@ -59,7 +73,7 @@ export const ObligationCollateralLayout: typeof BufferLayout.Structure = BufferL ], ); -export const ObligationLiquidityLayout: typeof BufferLayout.Structure = BufferLayout.struct( +export const ObligationLiquidityLayout = BufferLayout.struct( [ Layout.publicKey('borrowReserve'), Layout.uint128('cumulativeBorrowRateWads'), @@ -72,20 +86,6 @@ export const isObligation = (info: AccountInfo) => { return info.data.length === ObligationLayout.span; }; -export interface ProtoObligation { - version: number; - lastUpdate: LastUpdate; - lendingMarket: PublicKey; - owner: PublicKey; - depositedValue: BN; // decimals - borrowedValue: BN; // decimals - allowedBorrowValue: BN; // decimals - unhealthyBorrowValue: BN; // decimals - depositsLen: number; - borrowsLen: number; - dataFlat: Buffer; -} - export const ObligationParser = ( pubkey: PublicKey, info: AccountInfo, @@ -103,7 +103,7 @@ export const ObligationParser = ( depositsLen, borrowsLen, dataFlat, - } = ObligationLayout.decode(buffer) as ProtoObligation; + } = ObligationLayout.decode(buffer); if (lastUpdate.slot.isZero()) { return; @@ -116,13 +116,13 @@ export const ObligationParser = ( const deposits = BufferLayout.seq( ObligationCollateralLayout, depositsLen, - ).decode(depositsBuffer) as ObligationCollateral[]; + ).decode(depositsBuffer); const borrowsBuffer = dataFlat.slice(depositsSpan, depositsSpan + borrowsSpan); const borrows = BufferLayout.seq( ObligationLiquidityLayout, borrowsLen, - ).decode(borrowsBuffer) as ObligationLiquidity[]; + ).decode(borrowsBuffer); const obligation = { version, diff --git a/packages/lending/src/models/state/reserve.ts b/packages/lending/src/models/state/reserve.ts index 889a18f0..effc8d47 100644 --- a/packages/lending/src/models/state/reserve.ts +++ b/packages/lending/src/models/state/reserve.ts @@ -46,7 +46,7 @@ export interface ReserveConfig { }; } -export const ReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct( +export const ReserveLayout = BufferLayout.struct( [ BufferLayout.u8('version'), @@ -109,7 +109,7 @@ export const isReserve = (info: AccountInfo) => { export const ReserveParser = (pubkey: PublicKey, info: AccountInfo) => { const buffer = Buffer.from(info.data); - const reserve = ReserveLayout.decode(buffer) as Reserve; + const reserve = ReserveLayout.decode(buffer); if (reserve.lastUpdate.slot.isZero()) { return; diff --git a/packages/lending/src/types/buffer-layout.d.ts b/packages/lending/src/types/buffer-layout.d.ts index 32e44d0e..94dd907a 100644 --- a/packages/lending/src/types/buffer-layout.d.ts +++ b/packages/lending/src/types/buffer-layout.d.ts @@ -1,9 +1,88 @@ +// https://github.com/project-serum/anchor/blob/master/ts/types/buffer-layout/index.d.ts declare module 'buffer-layout' { - const bl: any; - export = bl; -} + // TODO: remove `any`. + export class Layout { + span: number; + property?: string; + + constructor(span: number, property?: string); -declare module 'jazzicon' { - const jazzicon: any; - export = jazzicon; + decode(b: Buffer, offset?: number): T; + encode(src: T, b: Buffer, offset?: number): number; + getSpan(b: Buffer, offset?: number): number; + replicate(name: string): this; + } + // TODO: remove any. + export class Structure extends Layout { + span: any; + } + export function greedy( + elementSpan?: number, + property?: string, + ): Layout; + export function offset( + layout: Layout, + offset?: number, + property?: string, + ): Layout; + export function u8(property?: string): Layout; + export function u16(property?: string): Layout; + export function u24(property?: string): Layout; + export function u32(property?: string): Layout; + export function u40(property?: string): Layout; + export function u48(property?: string): Layout; + export function nu64(property?: string): Layout; + export function u16be(property?: string): Layout; + export function u24be(property?: string): Layout; + export function u32be(property?: string): Layout; + export function u40be(property?: string): Layout; + export function u48be(property?: string): Layout; + export function nu64be(property?: string): Layout; + export function s8(property?: string): Layout; + export function s16(property?: string): Layout; + export function s24(property?: string): Layout; + export function s32(property?: string): Layout; + export function s40(property?: string): Layout; + export function s48(property?: string): Layout; + export function ns64(property?: string): Layout; + export function s16be(property?: string): Layout; + export function s24be(property?: string): Layout; + export function s32be(property?: string): Layout; + export function s40be(property?: string): Layout; + export function s48be(property?: string): Layout; + export function ns64be(property?: string): Layout; + export function f32(property?: string): Layout; + export function f32be(property?: string): Layout; + export function f64(property?: string): Layout; + export function f64be(property?: string): Layout; + export function struct( + fields: Layout[], + property?: string, + decodePrefixes?: boolean, + ): Layout; + export function bits( + word: Layout, + msb?: boolean, + property?: string, + ): any; + export function seq( + elementLayout: Layout, + count: number | Layout, + property?: string, + ): Layout; + export function union( + discr: Layout, + defaultLayout?: any, + property?: string, + ): any; + export function unionLayoutDiscriminator( + layout: Layout, + property?: string, + ): any; + export function blob( + length: number | Layout, + property?: string, + ): Layout; + export function cstr(property?: string): Layout; + export function utf8(maxSpan: number, property?: string): Layout; } diff --git a/packages/lending/src/types/jazzicon.d.ts b/packages/lending/src/types/jazzicon.d.ts new file mode 100644 index 00000000..3c462eed --- /dev/null +++ b/packages/lending/src/types/jazzicon.d.ts @@ -0,0 +1,4 @@ +declare module 'jazzicon' { + const jazzicon: any; + export = jazzicon; +} diff --git a/packages/lending/src/utils/layout.ts b/packages/lending/src/utils/layout.ts index 38931a47..e1e926d7 100644 --- a/packages/lending/src/utils/layout.ts +++ b/packages/lending/src/utils/layout.ts @@ -5,11 +5,13 @@ import * as BufferLayout from 'buffer-layout'; /** * Layout for a public key */ -export const publicKey = (property = 'publicKey'): unknown => { - const publicKeyLayout = BufferLayout.blob(32, property); +export const publicKey = (property = 'publicKey') => { + const layout = BufferLayout.blob(32, property); - const _decode = publicKeyLayout.decode.bind(publicKeyLayout); - const _encode = publicKeyLayout.encode.bind(publicKeyLayout); + const _decode = layout.decode.bind(layout); + const _encode = layout.encode.bind(layout); + + const publicKeyLayout = layout as BufferLayout.Layout as BufferLayout.Layout; publicKeyLayout.decode = (buffer: Buffer, offset: number) => { const data = _decode(buffer, offset); @@ -26,13 +28,15 @@ export const publicKey = (property = 'publicKey'): unknown => { /** * Layout for a 64bit unsigned value */ -export const uint64 = (property = 'uint64'): unknown => { +export const uint64 = (property = 'uint64') => { const layout = BufferLayout.blob(8, property); const _decode = layout.decode.bind(layout); const _encode = layout.encode.bind(layout); - layout.decode = (buffer: Buffer, offset: number) => { + const bnLayout = layout as BufferLayout.Layout as BufferLayout.Layout; + + bnLayout.decode = (buffer: Buffer, offset: number) => { const data = _decode(buffer, offset); return new BN( [...data] @@ -43,7 +47,7 @@ export const uint64 = (property = 'uint64'): unknown => { ); }; - layout.encode = (num: BN, buffer: Buffer, offset: number) => { + bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => { const a = num.toArray().reverse(); let b = Buffer.from(a); if (b.length !== 8) { @@ -54,17 +58,19 @@ export const uint64 = (property = 'uint64'): unknown => { return _encode(b, buffer, offset); }; - return layout; + return bnLayout; }; // TODO: wrap in BN (what about decimals?) -export const uint128 = (property = 'uint128'): unknown => { +export const uint128 = (property = 'uint128') => { const layout = BufferLayout.blob(16, property); const _decode = layout.decode.bind(layout); const _encode = layout.encode.bind(layout); - layout.decode = (buffer: Buffer, offset: number) => { + const bnLayout = layout as BufferLayout.Layout as BufferLayout.Layout; + + bnLayout.decode = (buffer: Buffer, offset: number) => { const data = _decode(buffer, offset); return new BN( [...data] @@ -75,7 +81,7 @@ export const uint128 = (property = 'uint128'): unknown => { ); }; - layout.encode = (num: BN, buffer: Buffer, offset: number) => { + bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => { const a = num.toArray().reverse(); let b = Buffer.from(a); if (b.length !== 16) { @@ -87,14 +93,20 @@ export const uint128 = (property = 'uint128'): unknown => { return _encode(b, buffer, offset); }; - return layout; + return bnLayout; }; +interface RustString { + length: number; + lengthPadding: number; + chars: Buffer; +} + /** * Layout for a Rust String type */ -export const rustString = (property = 'string'): unknown => { - const rsl = BufferLayout.struct( +export const rustString = (property = 'string') => { + const layout = BufferLayout.struct( [ BufferLayout.u32('length'), BufferLayout.u32('lengthPadding'), @@ -102,20 +114,24 @@ export const rustString = (property = 'string'): unknown => { ], property, ); - const _decode = rsl.decode.bind(rsl); - const _encode = rsl.encode.bind(rsl); - rsl.decode = (buffer: Buffer, offset: number) => { + const _decode = layout.decode.bind(layout); + const _encode = layout.encode.bind(layout); + + const stringLayout = layout as BufferLayout.Layout as BufferLayout.Layout; + + stringLayout.decode = (buffer: Buffer, offset: number) => { const data = _decode(buffer, offset); return data.chars.toString('utf8'); }; - rsl.encode = (str: string, buffer: Buffer, offset: number) => { + stringLayout.encode = (str: string, buffer: Buffer, offset: number) => { + // @TODO: does this need length/padding? const data = { chars: Buffer.from(str, 'utf8'), - }; + } as RustString; return _encode(data, buffer, offset); }; - return rsl; + return stringLayout; }; diff --git a/packages/lending/types/buffer-layout.d.ts b/packages/lending/types/buffer-layout.d.ts index 32e44d0e..94dd907a 100644 --- a/packages/lending/types/buffer-layout.d.ts +++ b/packages/lending/types/buffer-layout.d.ts @@ -1,9 +1,88 @@ +// https://github.com/project-serum/anchor/blob/master/ts/types/buffer-layout/index.d.ts declare module 'buffer-layout' { - const bl: any; - export = bl; -} + // TODO: remove `any`. + export class Layout { + span: number; + property?: string; + + constructor(span: number, property?: string); -declare module 'jazzicon' { - const jazzicon: any; - export = jazzicon; + decode(b: Buffer, offset?: number): T; + encode(src: T, b: Buffer, offset?: number): number; + getSpan(b: Buffer, offset?: number): number; + replicate(name: string): this; + } + // TODO: remove any. + export class Structure extends Layout { + span: any; + } + export function greedy( + elementSpan?: number, + property?: string, + ): Layout; + export function offset( + layout: Layout, + offset?: number, + property?: string, + ): Layout; + export function u8(property?: string): Layout; + export function u16(property?: string): Layout; + export function u24(property?: string): Layout; + export function u32(property?: string): Layout; + export function u40(property?: string): Layout; + export function u48(property?: string): Layout; + export function nu64(property?: string): Layout; + export function u16be(property?: string): Layout; + export function u24be(property?: string): Layout; + export function u32be(property?: string): Layout; + export function u40be(property?: string): Layout; + export function u48be(property?: string): Layout; + export function nu64be(property?: string): Layout; + export function s8(property?: string): Layout; + export function s16(property?: string): Layout; + export function s24(property?: string): Layout; + export function s32(property?: string): Layout; + export function s40(property?: string): Layout; + export function s48(property?: string): Layout; + export function ns64(property?: string): Layout; + export function s16be(property?: string): Layout; + export function s24be(property?: string): Layout; + export function s32be(property?: string): Layout; + export function s40be(property?: string): Layout; + export function s48be(property?: string): Layout; + export function ns64be(property?: string): Layout; + export function f32(property?: string): Layout; + export function f32be(property?: string): Layout; + export function f64(property?: string): Layout; + export function f64be(property?: string): Layout; + export function struct( + fields: Layout[], + property?: string, + decodePrefixes?: boolean, + ): Layout; + export function bits( + word: Layout, + msb?: boolean, + property?: string, + ): any; + export function seq( + elementLayout: Layout, + count: number | Layout, + property?: string, + ): Layout; + export function union( + discr: Layout, + defaultLayout?: any, + property?: string, + ): any; + export function unionLayoutDiscriminator( + layout: Layout, + property?: string, + ): any; + export function blob( + length: number | Layout, + property?: string, + ): Layout; + export function cstr(property?: string): Layout; + export function utf8(maxSpan: number, property?: string): Layout; } diff --git a/packages/lending/types/jazzicon.d.ts b/packages/lending/types/jazzicon.d.ts new file mode 100644 index 00000000..3c462eed --- /dev/null +++ b/packages/lending/types/jazzicon.d.ts @@ -0,0 +1,4 @@ +declare module 'jazzicon' { + const jazzicon: any; + export = jazzicon; +} diff --git a/packages/lending/types/sol-wallet-adapter.d.ts b/packages/lending/types/sol-wallet-adapter.d.ts new file mode 100644 index 00000000..41acf5df --- /dev/null +++ b/packages/lending/types/sol-wallet-adapter.d.ts @@ -0,0 +1,4 @@ +declare module '@project-serum/sol-wallet-adapter' { + const adapter: any; + export = adapter; +} From 934cf0679d9a5a1be418d876012d00ecb195f92c Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 17:58:48 -0500 Subject: [PATCH 20/58] todos --- packages/lending/src/contexts/market.tsx | 1 + packages/lending/src/hooks/useUserObligationByReserve.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index d50f817d..11547722 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -242,6 +242,7 @@ export function MarketProvider({ children = null as any }) { ); } +// @TODO: find all uses, use market price from reserve export const useMarkets = () => { const context = useContext(MarketsContext); return context as MarketsContextState; diff --git a/packages/lending/src/hooks/useUserObligationByReserve.ts b/packages/lending/src/hooks/useUserObligationByReserve.ts index b075d37a..58c4b11e 100644 --- a/packages/lending/src/hooks/useUserObligationByReserve.ts +++ b/packages/lending/src/hooks/useUserObligationByReserve.ts @@ -17,6 +17,7 @@ export function useUserObligationByReserve( typeof depositReserve === 'string' ? depositReserve : depositReserve?.toBase58(); + // @FIXME: support multiple deposits/borrows return userObligations.filter(item => { // @FIXME: borrows and deposits may be empty if (borrowReservePubkey && depositReservePubkey) { From 86ed2f1e5e5f53f31b7042a85bf2c5d6b048823f Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 17:58:55 -0500 Subject: [PATCH 21/58] sort exports --- packages/lending/src/hooks/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/lending/src/hooks/index.ts b/packages/lending/src/hooks/index.ts index 58c6c0c8..8f6439f6 100644 --- a/packages/lending/src/hooks/index.ts +++ b/packages/lending/src/hooks/index.ts @@ -1,12 +1,12 @@ -export * from './useLendingReserves'; +export * from './useBorrowedAmount'; +export * from './useBorrowingPower'; export * from './useCollateralBalance'; +export * from './useEnrichedLendingObligations'; +export * from './useLendingMarket'; export * from './useLendingObligations'; -export * from './useUserObligations'; -export * from './useUserObligationByReserve'; -export * from './useBorrowedAmount'; -export * from './useUserDeposits'; +export * from './useLendingReserves'; export * from './useSliderInput'; export * from './useUserBalance'; -export * from './useEnrichedLendingObligations'; -export * from './useBorrowingPower'; -export * from './useLendingMarket'; +export * from './useUserDeposits'; +export * from './useUserObligationByReserve'; +export * from './useUserObligations'; From 4a5362b880490345d380b9399cc8ecbbfa2a37dd Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 15 Jun 2021 18:45:35 -0500 Subject: [PATCH 22/58] fix all typescript and lint errors --- .../src/actions/liquidateObligation.tsx | 1 - .../src/components/BorrowInput/index.tsx | 1 - .../src/components/RepayInput/index.tsx | 2 -- packages/lending/src/contexts/market.tsx | 17 ++++------- .../lending/src/hooks/useBorrowedAmount.ts | 28 +++---------------- .../lending/src/hooks/useBorrowingPower.ts | 3 +- .../hooks/useEnrichedLendingObligations.ts | 6 ++-- .../lending/src/hooks/useUserObligations.ts | 10 ++----- packages/lending/src/utils/pools.ts | 9 +++--- packages/lending/src/views/faucet/index.tsx | 7 +++-- .../src/views/liquidateReserve/index.tsx | 7 +++-- .../src/views/margin/newPosition/leverage.ts | 2 ++ .../lending/src/views/repayReserve/index.tsx | 6 ++-- 13 files changed, 33 insertions(+), 66 deletions(-) diff --git a/packages/lending/src/actions/liquidateObligation.tsx b/packages/lending/src/actions/liquidateObligation.tsx index 1f884266..e47cb5df 100644 --- a/packages/lending/src/actions/liquidateObligation.tsx +++ b/packages/lending/src/actions/liquidateObligation.tsx @@ -16,7 +16,6 @@ import { TransactionInstruction, } from '@solana/web3.js'; import { - LendingMarket, liquidateObligationInstruction, Obligation, refreshObligationInstruction, diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx index 701f50cb..b92f6f45 100644 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ b/packages/lending/src/components/BorrowInput/index.tsx @@ -164,7 +164,6 @@ export const BorrowInput = (props: { setValue, depositReserve, borrowReserve, - sourceAccounts, userObligationsByReserve, setPendingTx, setShowConfirmation, diff --git a/packages/lending/src/components/RepayInput/index.tsx b/packages/lending/src/components/RepayInput/index.tsx index b70e9d2f..667ead93 100644 --- a/packages/lending/src/components/RepayInput/index.tsx +++ b/packages/lending/src/components/RepayInput/index.tsx @@ -3,7 +3,6 @@ import { ConnectButton, contexts, fromLamports, - hooks, notify, ParsedAccount, wadToLamports, @@ -26,7 +25,6 @@ import './style.less'; const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; const { useMint } = contexts.Accounts; -const { useAccountByMint } = hooks; export const RepayInput = (props: { className?: string; diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index 11547722..31c2ad4f 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -5,7 +5,6 @@ import { fromLamports, getTokenName, KnownTokenMap, - ParsedAccount, STABLE_COINS, } from '@oyster/common'; import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum'; @@ -17,7 +16,7 @@ import React, { useMemo, useState, } from 'react'; -import { LendingMarket, PoolInfo, Reserve } from '../models'; +import { PoolInfo, Reserve } from '../models'; import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from '../utils/pools'; import { getPoolName } from '../utils/utils'; import { POOLS_WITH_AIRDROP } from './../models/airdrops'; @@ -57,7 +56,8 @@ export function MarketProvider({ children = null as any }) { const { endpoint } = useConnectionConfig(); const accountsToObserve = useMemo(() => new Map(), []); const [marketMints, setMarketMints] = useState([]); - const [dailyVolume, setDailyVolume] = useState>( + // @TODO: remove this, it never changes + const [dailyVolume] = useState>( new Map(), ); @@ -536,10 +536,6 @@ export const simulateMarketOrderFill = ( const quoteTokenMintDecimals = cache.get(decodedMarket.quoteTokenMint)?.info.decimals || 0; - const lendingMarket = cache.get( - reserve.lendingMarket, - ) as ParsedAccount; - const market = new Market( decodedMarket, baseMintDecimals, @@ -557,11 +553,8 @@ export const simulateMarketOrderFill = ( const bids = new Orderbook(market, bidInfo.accountFlags, bidInfo.slab); const asks = new Orderbook(market, askInfo.accountFlags, askInfo.slab); - const book = lendingMarket.info.quoteTokenMint.equals( - reserve.liquidity.mintPubkey, - ) - ? bids - : asks; + // @FIXME: removed quote token mint + const book = asks; let cost = 0; let remaining = fromLamports(amount, liquidityMint.info); diff --git a/packages/lending/src/hooks/useBorrowedAmount.ts b/packages/lending/src/hooks/useBorrowedAmount.ts index 76e65f65..992780e4 100644 --- a/packages/lending/src/hooks/useBorrowedAmount.ts +++ b/packages/lending/src/hooks/useBorrowedAmount.ts @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { useLendingReserve } from './useLendingReserves'; import { useUserObligationByReserve } from './useUserObligationByReserve'; -const { cache, getMultipleAccounts, MintParser, useMint } = contexts.Accounts; +const { useMint } = contexts.Accounts; const { useConnection } = contexts.Connection; export function useBorrowedAmount(address?: string | PublicKey) { @@ -30,21 +30,6 @@ export function useBorrowedAmount(address?: string | PublicKey) { }); (async () => { - // precache obligation mints - const { keys, array } = await getMultipleAccounts( - connection, - // @FIXME: obligation tokens - userObligationsByReserve.map(item => - item.obligation.info.tokenMint.toBase58(), - ), - 'single', - ); - - array.forEach((item, index) => { - const address = keys[index]; - cache.add(new PublicKey(address), item, MintParser); - }); - const result = { borrowedLamports: 0, borrowedInUSD: 0, @@ -56,18 +41,13 @@ export function useBorrowedAmount(address?: string | PublicKey) { let liquidationThreshold = 0; userObligationsByReserve.forEach(item => { - const borrowed = wadToLamports( + // @FIXME: support multiple borrows, and decimals may be different than lamports + const borrowedLamports = wadToLamports( item.obligation.info.borrows[0].borrowedAmountWads, ).toNumber(); - const owned = item.userAccounts.reduce( - (amount, acc) => (amount += acc.info.amount.toNumber()), - 0, - ); - // @FIXME: obligation tokens - result.borrowedLamports += - borrowed * (owned / obligationMint?.info.supply.toNumber()); + result.borrowedLamports += borrowedLamports; result.borrowedInUSD += item.obligation.info.borrowedInQuote; result.colateralInUSD += item.obligation.info.collateralInQuote; // @FIXME: BigNumber diff --git a/packages/lending/src/hooks/useBorrowingPower.ts b/packages/lending/src/hooks/useBorrowingPower.ts index 2b3fba2e..56904c11 100644 --- a/packages/lending/src/hooks/useBorrowingPower.ts +++ b/packages/lending/src/hooks/useBorrowingPower.ts @@ -1,8 +1,7 @@ import { PublicKey } from '@solana/web3.js'; import { useMemo } from 'react'; import { useMidPriceInUSD } from '../contexts/market'; -import { useLendingMarket } from './useLendingMarket'; -import { getLendingReserves, useLendingReserve } from './useLendingReserves'; +import { useLendingReserve } from './useLendingReserves'; import { useUserDeposits } from './useUserDeposits'; import { useUserObligations } from './useUserObligations'; diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index e5c22aac..547e42d3 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -7,9 +7,8 @@ import { } from '@oyster/common'; import { MintInfo } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; -import BN from 'bn.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { simulateMarketOrderFill, useMarkets } from '../contexts/market'; +import { useMarkets } from '../contexts/market'; import { collateralToLiquidity, Obligation, Reserve } from '../models'; import { useLendingObligations } from './useLendingObligations'; import { useLendingReserves } from './useLendingReserves'; @@ -17,13 +16,12 @@ import { useLendingReserves } from './useLendingReserves'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; -// @FIXME: BigNumber interface EnrichedLendingObligationInfo extends Obligation { ltv: number; health: number; borrowedInQuote: number; collateralInQuote: number; - liquidationThreshold: BN; + liquidationThreshold: number; repayName: string; collateralName: string; } diff --git a/packages/lending/src/hooks/useUserObligations.ts b/packages/lending/src/hooks/useUserObligations.ts index fb2f00ef..dbcfd76a 100644 --- a/packages/lending/src/hooks/useUserObligations.ts +++ b/packages/lending/src/hooks/useUserObligations.ts @@ -8,6 +8,7 @@ export function useUserObligations() { const { userAccounts } = useUserAccounts(); const { obligations } = useEnrichedLendingObligations(); + // @FIXME: obligation tokens were removed, simplify this const accountsByMint = useMemo(() => { return userAccounts.reduce((res, acc) => { const id = acc.info.mint.toBase58(); @@ -21,17 +22,12 @@ export function useUserObligations() { return []; } - // @FIXME: obligation tokens + // @FIXME: obligation tokens were removed, simplify this return obligations - .filter( - acc => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined, - ) .map(ob => { return { obligation: ob, - userAccounts: [ - ...(accountsByMint.get(ob.info.tokenMint.toBase58()) || []), - ], + userAccounts: [], }; }) .sort( diff --git a/packages/lending/src/utils/pools.ts b/packages/lending/src/utils/pools.ts index cad88b53..0294b12b 100644 --- a/packages/lending/src/utils/pools.ts +++ b/packages/lending/src/utils/pools.ts @@ -3,12 +3,11 @@ import { AccountLayout, MintLayout } from '@solana/spl-token'; import { Connection, PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; import { PoolInfo } from '../models'; +import * as BufferLayout from 'buffer-layout'; -const { - TokenSwapLayout, - TokenSwapLayoutLegacyV0: TokenSwapLayoutV0, - TokenSwapLayoutV1, -} = models; +const TokenSwapLayout = models.TokenSwapLayout as any as BufferLayout.Structure; +const TokenSwapLayoutV0 = models.TokenSwapLayoutLegacyV0 as any as BufferLayout.Structure; +const TokenSwapLayoutV1 = models.TokenSwapLayoutV1 as any as BufferLayout.Structure; const { useConnection } = contexts.Connection; const { cache, getMultipleAccounts, TokenAccountParser } = contexts.Accounts; diff --git a/packages/lending/src/views/faucet/index.tsx b/packages/lending/src/views/faucet/index.tsx index e90360f4..2a7bf88e 100644 --- a/packages/lending/src/views/faucet/index.tsx +++ b/packages/lending/src/views/faucet/index.tsx @@ -10,21 +10,22 @@ const { useWallet } = contexts.Wallet; export const FaucetView = () => { const connection = useConnection(); const { wallet } = useWallet(); + const publicKey = wallet?.publicKey; const airdrop = useCallback(() => { - if (!wallet?.publicKey) { + if (!publicKey) { return; } connection - .requestAirdrop(wallet.publicKey, 2 * LAMPORTS_PER_SOL) + .requestAirdrop(publicKey, 2 * LAMPORTS_PER_SOL) .then(() => { notify({ message: LABELS.ACCOUNT_FUNDED, type: 'success', }); }); - }, [wallet, wallet?.publicKey, connection]); + }, [publicKey, connection]); const bodyStyle: React.CSSProperties = { display: 'flex', diff --git a/packages/lending/src/views/liquidateReserve/index.tsx b/packages/lending/src/views/liquidateReserve/index.tsx index 15b6150e..43f81504 100644 --- a/packages/lending/src/views/liquidateReserve/index.tsx +++ b/packages/lending/src/views/liquidateReserve/index.tsx @@ -19,10 +19,11 @@ export const LiquidateReserveView = () => { const obligation = useEnrichedLendingObligation(id); - const repayReserve = useLendingReserve(obligation?.info.borrowReserve); - const withdrawReserve = useLendingReserve(obligation?.info.depositReserve); + // @FIXME: handle multiple deposits / borrows + const repayReserve = useLendingReserve(obligation?.info.borrows[0].borrowReserve); + const withdrawReserve = useLendingReserve(obligation?.info.deposits[0].depositReserve); - if (!obligation || !repayReserve) { + if (!obligation || !repayReserve || !withdrawReserve) { return null; } diff --git a/packages/lending/src/views/margin/newPosition/leverage.ts b/packages/lending/src/views/margin/newPosition/leverage.ts index e4e275f2..8838187d 100644 --- a/packages/lending/src/views/margin/newPosition/leverage.ts +++ b/packages/lending/src/views/margin/newPosition/leverage.ts @@ -90,5 +90,7 @@ export function useLeverage({ enrichedPools, collValue, collateralDeposit, + newPosition, + setNewPosition ]); } diff --git a/packages/lending/src/views/repayReserve/index.tsx b/packages/lending/src/views/repayReserve/index.tsx index 1060e3e7..90bcfcd4 100644 --- a/packages/lending/src/views/repayReserve/index.tsx +++ b/packages/lending/src/views/repayReserve/index.tsx @@ -25,11 +25,13 @@ export const RepayReserveView = () => { }>(); const lendingObligation = useEnrichedLendingObligation(obligationId); - const id = obligationId ? lendingObligation?.info.borrowReserve : reserveId; + // @FIXME: obligation can have no borrows + const id = obligationId ? lendingObligation?.info.borrows[0].borrowReserve : reserveId; const lendingReserve = useLendingReserve(id); + // @FIXME: obligation can have no deposits const repayReserve = useLendingReserve( - obligationId ? lendingObligation?.info.depositReserve : reserveId, + obligationId ? lendingObligation?.info.deposits[0].depositReserve : reserveId, ); const { userObligations, totalInQuote: loansValue } = useUserObligations(); From b0138944511f85b10cdaed1d68b54b62e42ba667 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 17 Jun 2021 16:39:15 -0500 Subject: [PATCH 23/58] add pyth client + hook --- packages/lending/package.json | 9 +- packages/lending/src/hooks/usePyth.ts | 176 ++++++++++++++++++++++++++ yarn.lock | 62 +++++++++ 3 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 packages/lending/src/hooks/usePyth.ts diff --git a/packages/lending/package.json b/packages/lending/package.json index 4b3af8b3..ce1c2493 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -2,8 +2,6 @@ "name": "lending", "version": "0.1.0", "dependencies": { - "@solana/wallet-base": "0.0.1", - "@solana/wallet-ledger": "0.0.1", "@ant-design/icons": "^4.4.0", "@ant-design/pro-layout": "^6.7.0", "@babel/preset-typescript": "^7.12.13", @@ -11,8 +9,11 @@ "@oyster/common": "0.0.1", "@project-serum/serum": "^0.13.11", "@project-serum/sol-wallet-adapter": "^0.1.4", + "@pythnetwork/client": "^2.0.0", "@solana/spl-token": "0.0.13", "@solana/spl-token-swap": "0.1.0", + "@solana/wallet-base": "0.0.1", + "@solana/wallet-ledger": "0.0.1", "@solana/web3.js": "^1.5.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", @@ -87,8 +88,8 @@ "@types/node": "^12.12.62", "arweave-deploy": "^1.9.1", "gh-pages": "^3.1.0", - "prettier": "^2.1.2", - "npm-link-shared": "0.5.6" + "npm-link-shared": "0.5.6", + "prettier": "^2.1.2" }, "peerDependencies": { "react": "*", diff --git a/packages/lending/src/hooks/usePyth.ts b/packages/lending/src/hooks/usePyth.ts new file mode 100644 index 00000000..6f5761e9 --- /dev/null +++ b/packages/lending/src/hooks/usePyth.ts @@ -0,0 +1,176 @@ +import { contexts, useConnectionConfig } from '@oyster/common'; +import { + parseMappingData, + parsePriceData, + parseProductData, +} from '@pythnetwork/client'; +import { PublicKey } from '@solana/web3.js'; +import { useEffect, useState } from 'react'; + +const { useConnection } = contexts.Connection; +const { getMultipleAccounts } = contexts.Accounts; + +const PYTH_PROGRAM_ID = new PublicKey( + 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2', +); + +type Products = Record; +type Prices = Record; + +const usePythProducts = () => { + const connection = useConnection(); + const [products, setProducts] = useState({}); + + useEffect(() => { + (async () => { + try { + const accountInfo = await connection.getAccountInfo(PYTH_PROGRAM_ID); + if (!accountInfo || !accountInfo.data) return; + + const { productAccountKeys } = parseMappingData(accountInfo.data); + + const productInfos = await getMultipleAccounts( + connection, + productAccountKeys.map(p => p.toBase58()), + 'confirmed', + ); + + const products = productInfos.array.reduce((products, p) => { + const product = parseProductData(p.data); + const symbol = product.product['symbol']; + products[symbol] = product; + return products; + }, {} as Products); + setProducts(products); + } catch (e) { + console.error(e); + } + })(); + }, [connection]); + + return products; +}; + +// @TODO: aggregate subscriptions by mint +const usePythPriceByMint = (mint: string) => { + const { tokenMap } = useConnectionConfig(); + const products = usePythProducts(); + const connection = useConnection(); + const [price, setPrice] = useState(); + + const symbol = tokenMap.get(mint)?.symbol; + + useEffect(() => { + const subscriptionIds = [] as number[]; + if (symbol) { + const product = products[`${symbol}/USD`]; + if (product) { + (async () => { + try { + const accountInfo = await connection.getAccountInfo( + product.priceAccountKey, + ); + if (!accountInfo || !accountInfo.data) return; + + const price = parsePriceData(accountInfo.data); + setPrice(price); + + subscriptionIds.push( + connection.onAccountChange(product.priceAccountKey, accountInfo => { + const price = parsePriceData(accountInfo.data); + setPrice(price); + }), + ); + } + catch (e) { + console.error(e); + } + })(); + } + } + return () => { + for (const subscriptionId of subscriptionIds) { + connection.removeAccountChangeListener(subscriptionId); + } + }; + }, [products, symbol]); + + return price; +}; + +const useMidPriceInUSD = (mint: string) => { + const price = usePythPriceByMint(mint); + return price ? price.price * Math.pow(10, price.exponent) : 0; +} + +interface ProductAttributes { + [index: string]: string; +} + +interface Product { + magic: number; + version: number; + type: number; + size: number; + priceAccountKey: PublicKey; + product: ProductAttributes; +} + +interface Price { + priceComponents: { + publisher: PublicKey; + aggregate: { + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + }; + latest: { + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + }; + }[]; + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + magic: number; + version: number; + type: number; + size: number; + priceType: number; + exponent: number; + numComponentPrices: number; + currentSlot: bigint; + validSlot: bigint; + twapComponent: bigint; + twap: number; + avolComponent: bigint; + avol: number; + drv0Component: bigint; + drv0: number; + drv1Component: bigint; + drv1: number; + drv2Component: bigint; + drv2: number; + drv3Component: bigint; + drv3: number; + drv4Component: bigint; + drv4: number; + drv5Component: bigint; + drv5: number; + productAccountKey: PublicKey; + nextPriceAccountKey: PublicKey | null; + aggregatePriceUpdaterAccountKey: PublicKey; +} diff --git a/yarn.lock b/yarn.lock index 88f99daa..7d591590 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2958,6 +2958,15 @@ bs58 "^4.0.1" eventemitter3 "^4.0.4" +"@pythnetwork/client@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@pythnetwork/client/-/client-2.0.0.tgz#c5cba8999711584b19534ac91a11b3857e437278" + integrity sha512-AIyiKOoFpwP15M+kCv9WMAavQagRX7hYDb3DcE5h5Kd3NC9eijbulwtSX6RvpgcWU3U8jBq30h7SG11BX6AkGA== + dependencies: + "@solana/web3.js" "^1.10.1" + assert "^2.0.0" + buffer "^6.0.1" + "@qixian.cs/path-to-regexp@^6.1.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@qixian.cs/path-to-regexp/-/path-to-regexp-6.1.0.tgz#6b84ad01596332aba95fa29d2e70104698cd5c45" @@ -3166,6 +3175,26 @@ tweetnacl "^1.0.0" ws "^7.0.0" +"@solana/web3.js@^1.10.1": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.18.0.tgz#f4c422a843aa57a1366d98b06cb6b45441644d59" + integrity sha512-ijAoRd4Sje1QYoPAwDr7KYlDK40FE7tAUa2V3wT4PGKatWf4ETDXoyYlW89J6vrqOT+mV3GUuaVC76tOFlrXyA== + dependencies: + "@babel/runtime" "^7.12.5" + bn.js "^5.0.0" + borsh "^0.4.0" + bs58 "^4.0.1" + buffer "6.0.1" + buffer-layout "^1.2.0" + crypto-hash "^1.2.2" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "^2.6.1" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.14.2" + tweetnacl "^1.0.0" + "@solana/web3.js@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.5.0.tgz#4819ecad0408ec55f3d47a227627856002a7358b" @@ -5195,6 +5224,16 @@ assert@^1.1.1: object-assign "^4.1.1" util "0.10.3" +assert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" + integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== + dependencies: + es6-object-assign "^1.1.0" + is-nan "^1.2.1" + object-is "^1.0.1" + util "^0.12.0" + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -6219,6 +6258,16 @@ borsh@^0.3.1: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" +borsh@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.4.0.tgz#9dd6defe741627f1315eac2a73df61421f6ddb9f" + integrity sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g== + dependencies: + "@types/bn.js" "^4.11.5" + bn.js "^5.0.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -9049,6 +9098,11 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" +es6-object-assign@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= + es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -12090,6 +12144,14 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +is-nan@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" From 30e26bad5b053be80308756d5c8721c453581a91 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 17 Jun 2021 16:39:29 -0500 Subject: [PATCH 24/58] mintPubkey is defined --- packages/lending/src/hooks/useCollateralBalance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lending/src/hooks/useCollateralBalance.ts b/packages/lending/src/hooks/useCollateralBalance.ts index 989d2846..6cb20e6f 100644 --- a/packages/lending/src/hooks/useCollateralBalance.ts +++ b/packages/lending/src/hooks/useCollateralBalance.ts @@ -34,7 +34,7 @@ export function useUserCollateralBalance( const updateBalance = () => { setBalanceInUSD( balance * - midPriceInUSD(reserve?.liquidity.mintPubkey?.toBase58() || ''), + midPriceInUSD(reserve?.liquidity.mintPubkey.toBase58() || ''), ); }; From dfc05c4acbb835fa8a56aad37e573dfc1b922860 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 17 Jun 2021 16:39:44 -0500 Subject: [PATCH 25/58] revise fixme comments --- packages/lending/src/hooks/useCollateralBalance.ts | 1 - packages/lending/src/hooks/useEnrichedLendingObligations.ts | 2 +- packages/lending/src/models/state/reserve.ts | 5 ----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/lending/src/hooks/useCollateralBalance.ts b/packages/lending/src/hooks/useCollateralBalance.ts index 6cb20e6f..5e5c654d 100644 --- a/packages/lending/src/hooks/useCollateralBalance.ts +++ b/packages/lending/src/hooks/useCollateralBalance.ts @@ -64,7 +64,6 @@ export function calculateCollateralBalance( reserve: Reserve, balanceLamports: number, ) { - // @FIXME: use BigNumber return ( reserveMarketCap(reserve) * (balanceLamports / (reserve?.collateral.mintTotalSupply.toNumber() || 1)) diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index 547e42d3..d77004d8 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -95,6 +95,7 @@ export function useEnrichedLendingObligations() { collateralMint?.info, ); + // @FIXME: support multiple borrows const borrowed = wadToLamports( obligation.info.borrows[0].borrowedAmountWads, ).toNumber(); @@ -128,7 +129,6 @@ export function useEnrichedLendingObligations() { health, borrowedInQuote, collateralInQuote, - // @FIXME: BigNumber liquidationThreshold: item.reserve.info.config.liquidationThreshold, repayName: getTokenName(tokenMap, reserve.liquidity.mintPubkey), diff --git a/packages/lending/src/models/state/reserve.ts b/packages/lending/src/models/state/reserve.ts index effc8d47..e642c00d 100644 --- a/packages/lending/src/models/state/reserve.ts +++ b/packages/lending/src/models/state/reserve.ts @@ -127,7 +127,6 @@ export const ReserveParser = (pubkey: PublicKey, info: AccountInfo) => { }; export const calculateUtilizationRatio = (reserve: Reserve) => { - // @FIXME: use BigNumber const totalBorrows = wadToLamports( reserve.liquidity.borrowedAmountWads, ).toNumber(); @@ -139,7 +138,6 @@ export const calculateUtilizationRatio = (reserve: Reserve) => { }; export const reserveMarketCap = (reserve?: Reserve) => { - // @FIXME: use BigNumber const available = reserve?.liquidity.availableAmount.toNumber() || 0; const borrowed = wadToLamports( reserve?.liquidity.borrowedAmountWads, @@ -149,7 +147,6 @@ export const reserveMarketCap = (reserve?: Reserve) => { }; export const collateralExchangeRate = (reserve?: Reserve) => { - // @FIXME: use BigNumber return ( (reserve?.collateral.mintTotalSupply.toNumber() || 1) / reserveMarketCap(reserve) @@ -160,7 +157,6 @@ export const collateralToLiquidity = ( collateralAmount: BN | number, reserve?: Reserve, ) => { - // @FIXME: use BigNumber const amount = typeof collateralAmount === 'number' ? collateralAmount @@ -172,7 +168,6 @@ export const liquidityToCollateral = ( liquidityAmount: BN | number, reserve?: Reserve, ) => { - // @FIXME: use BigNumber const amount = typeof liquidityAmount === 'number' ? liquidityAmount From 9827db6801828b6574ccf53c46af6592b8ba5063 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 17 Jun 2021 18:32:45 -0500 Subject: [PATCH 26/58] pyth context + refactor --- .../src/components/BorrowInput/index.tsx | 10 +- .../src/components/LiquidateInput/index.tsx | 6 +- .../src/components/RepayInput/index.tsx | 6 +- .../src/components/ReserveStatus/index.tsx | 4 +- packages/lending/src/contexts/pyth.tsx | 239 ++++++++++++++++++ .../lending/src/hooks/useBorrowingPower.ts | 4 +- .../lending/src/hooks/useCollateralBalance.ts | 26 +- .../hooks/useEnrichedLendingObligations.ts | 22 +- packages/lending/src/hooks/usePyth.ts | 15 +- packages/lending/src/hooks/useUserBalance.ts | 21 +- packages/lending/src/hooks/useUserDeposits.ts | 28 +- packages/lending/src/routes.tsx | 111 ++++---- packages/lending/src/views/borrow/item.tsx | 5 +- packages/lending/src/views/home/index.tsx | 104 ++++---- packages/lending/src/views/margin/item.tsx | 5 +- 15 files changed, 389 insertions(+), 217 deletions(-) create mode 100644 packages/lending/src/contexts/pyth.tsx diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx index b92f6f45..90ab01ce 100644 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ b/packages/lending/src/components/BorrowInput/index.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { borrowObligationLiquidity } from '../../actions'; import { LABELS } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { useSliderInput, useUserBalance, @@ -48,12 +48,12 @@ export const BorrowInput = (props: { return cache.get(id) as ParsedAccount; }, [depositReserveKey]); - const borrowPrice = useMidPriceInUSD( + const borrowPrice = usePrice( borrowReserve.info.liquidity.mintPubkey.toBase58(), - ).price; - const collateralPrice = useMidPriceInUSD( + ); + const collateralPrice = usePrice( depositReserve?.info.liquidity.mintPubkey.toBase58(), - )?.price; + ); const include = useMemo(() => new Set([depositReserve?.pubkey.toBase58()]), [ depositReserve, diff --git a/packages/lending/src/components/LiquidateInput/index.tsx b/packages/lending/src/components/LiquidateInput/index.tsx index 3680c0dc..793ec804 100644 --- a/packages/lending/src/components/LiquidateInput/index.tsx +++ b/packages/lending/src/components/LiquidateInput/index.tsx @@ -12,7 +12,7 @@ import Card from 'antd/lib/card'; import React, { useCallback, useEffect, useState } from 'react'; import { liquidateObligation } from '../../actions'; import { LABELS, marks } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { EnrichedLendingObligation, InputType, @@ -123,9 +123,9 @@ export const LiquidateInput = (props: { type, ]); - const collateralPrice = useMidPriceInUSD( + const collateralPrice = usePrice( withdrawReserve?.info.liquidity.mintPubkey.toBase58(), - )?.price; + ); useEffect(() => { if (withdrawReserve && lastTyped === 'liquidate') { diff --git a/packages/lending/src/components/RepayInput/index.tsx b/packages/lending/src/components/RepayInput/index.tsx index 667ead93..ee6a6ddd 100644 --- a/packages/lending/src/components/RepayInput/index.tsx +++ b/packages/lending/src/components/RepayInput/index.tsx @@ -11,7 +11,7 @@ import { Card, Slider } from 'antd'; import React, { useCallback, useEffect, useState } from 'react'; import { repayObligationLiquidity } from '../../actions'; import { LABELS, marks } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { EnrichedLendingObligation, InputType, @@ -124,9 +124,9 @@ export const RepayInput = (props: { wallet, ]); - const collateralPrice = useMidPriceInUSD( + const collateralPrice = usePrice( depositReserve?.info.liquidity.mintPubkey.toBase58(), - )?.price; + ); useEffect(() => { if (depositReserve && lastTyped === 'repay') { diff --git a/packages/lending/src/components/ReserveStatus/index.tsx b/packages/lending/src/components/ReserveStatus/index.tsx index bf279dc0..b488bdc9 100644 --- a/packages/lending/src/components/ReserveStatus/index.tsx +++ b/packages/lending/src/components/ReserveStatus/index.tsx @@ -9,7 +9,7 @@ import { PublicKey } from '@solana/web3.js'; import { Card, Col, Row, Statistic } from 'antd'; import React, { useMemo } from 'react'; import { GUTTER, LABELS } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { calculateDepositAPY, Reserve } from '../../models'; import { ReserveUtilizationChart } from './../../components/ReserveUtilizationChart'; import './style.less'; @@ -28,7 +28,7 @@ export const ReserveStatus = (props: { const mintAddress = props.reserve.liquidity.mintPubkey?.toBase58(); const liquidityMint = useMint(mintAddress); - const { price } = useMidPriceInUSD(mintAddress); + const price = usePrice(mintAddress); const availableAmount = fromLamports( props.reserve.liquidity.availableAmount, liquidityMint, diff --git a/packages/lending/src/contexts/pyth.tsx b/packages/lending/src/contexts/pyth.tsx new file mode 100644 index 00000000..6c8b62c1 --- /dev/null +++ b/packages/lending/src/contexts/pyth.tsx @@ -0,0 +1,239 @@ +import { contexts, useConnectionConfig } from '@oyster/common'; +import { + parseMappingData, + parsePriceData, + parseProductData, +} from '@pythnetwork/client'; +import { PublicKey } from '@solana/web3.js'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; + +const { useConnection } = contexts.Connection; +const { getMultipleAccounts } = contexts.Accounts; + +const PYTH_PROGRAM_ID = new PublicKey( + 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2', +); + +type Products = Record; +type Prices = Record; + +type Subscription = { id: number; count: number } | undefined; +type Subscriptions = Record; + +export interface PythContextState { + products: Products; + prices: Prices; + getPrice: (mint: string) => number; + subscribeToPrice: (mint: string) => () => void; +} + +const PythContext = React.createContext({ + products: {}, + prices: {}, + getPrice: (mint: string) => 0, + subscribeToPrice: (mint: string) => () => undefined, +}); + +export function PythProvider({ children = null as any }) { + const connection = useConnection(); + const { tokenMap } = useConnectionConfig(); + const [products, setProducts] = useState({}); + const [prices, setPrices] = useState({}); + const [subscriptions, setSubscriptions] = useState({}); + + useEffect(() => { + (async () => { + try { + const accountInfo = await connection.getAccountInfo(PYTH_PROGRAM_ID); + if (!accountInfo || !accountInfo.data) return; + + const { productAccountKeys } = parseMappingData(accountInfo.data); + + const productInfos = await getMultipleAccounts( + connection, + productAccountKeys.map(p => p.toBase58()), + 'confirmed', + ); + + const products = productInfos.array.reduce((products, p) => { + const product = parseProductData(p.data); + const symbol = product.product['symbol']; + products[symbol] = product; + return products; + }, {} as Products); + setProducts(products); + } catch (e) { + console.error(e); + } + })(); + }, [connection, setProducts]); + + const getPrice = useCallback( + (mint: string) => { + const symbol = tokenMap.get(mint)?.symbol; + if (!symbol) return 0; + + const price = prices[symbol]; + if (!price) return 0; + + return price.price * Math.pow(10, price.exponent); + }, + [tokenMap, prices], + ); + + const subscribeToPrice = useCallback( + (mint: string) => { + const tokenSymbol = tokenMap.get(mint)?.symbol; + if (tokenSymbol) { + const symbol = `${tokenSymbol}/USD`; + const product = products[symbol]; + if (product) { + let subscription: Subscription; + (async () => { + try { + const accountInfo = await connection.getAccountInfo( + product.priceAccountKey, + ); + if (!accountInfo || !accountInfo.data) return; + + const price = parsePriceData(accountInfo.data); + setPrices({ ...prices, [symbol]: price }); + + subscription = subscriptions[symbol]; + if (subscription) { + subscription.count++; + } else { + subscription = { + id: connection.onAccountChange( + product.priceAccountKey, + accountInfo => { + const price = parsePriceData(accountInfo.data); + setPrices({ ...prices, [symbol]: price }); + }, + ), + count: 1, + }; + } + setSubscriptions({ ...subscriptions, [symbol]: subscription }); + } catch (e) { + console.error(e); + } + })(); + return () => { + if (subscription) { + subscription.count--; + if (!subscription.count) { + connection.removeAccountChangeListener(subscription.id); + subscription = undefined; + } + setSubscriptions({ ...subscriptions, [symbol]: subscription }); + } + }; + } + } + return () => undefined; + }, + [tokenMap, products, connection, prices, setPrices, subscriptions, setSubscriptions], + ); + + return ( + + {children} + + ); +} + +export const usePyth = () => { + return useContext(PythContext); +}; + +export const usePrice = (mint: string) => { + const { getPrice, subscribeToPrice } = useContext(PythContext); + const [price, setPrice] = useState(0); + + useEffect(() => { + setPrice(getPrice(mint)); + return subscribeToPrice(mint); + }, [setPrice, getPrice, mint, subscribeToPrice]); + + return price; +}; + +interface Product { + magic: number; + version: number; + type: number; + size: number; + priceAccountKey: PublicKey; + product: ProductAttributes; +} + +interface ProductAttributes { + [index: string]: string; +} + +interface Price { + priceComponents: { + publisher: PublicKey; + aggregate: { + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + }; + latest: { + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + }; + }[]; + priceComponent: bigint; + price: number; + confidenceComponent: bigint; + confidence: number; + status: number; + corporateAction: number; + publishSlot: bigint; + magic: number; + version: number; + type: number; + size: number; + priceType: number; + exponent: number; + numComponentPrices: number; + currentSlot: bigint; + validSlot: bigint; + twapComponent: bigint; + twap: number; + avolComponent: bigint; + avol: number; + drv0Component: bigint; + drv0: number; + drv1Component: bigint; + drv1: number; + drv2Component: bigint; + drv2: number; + drv3Component: bigint; + drv3: number; + drv4Component: bigint; + drv4: number; + drv5Component: bigint; + drv5: number; + productAccountKey: PublicKey; + nextPriceAccountKey: PublicKey | null; + aggregatePriceUpdaterAccountKey: PublicKey; +} diff --git a/packages/lending/src/hooks/useBorrowingPower.ts b/packages/lending/src/hooks/useBorrowingPower.ts index 56904c11..e77bb337 100644 --- a/packages/lending/src/hooks/useBorrowingPower.ts +++ b/packages/lending/src/hooks/useBorrowingPower.ts @@ -1,6 +1,6 @@ import { PublicKey } from '@solana/web3.js'; import { useMemo } from 'react'; -import { useMidPriceInUSD } from '../contexts/market'; +import { usePrice } from '../contexts/pyth'; import { useLendingReserve } from './useLendingReserves'; import { useUserDeposits } from './useUserDeposits'; import { useUserObligations } from './useUserObligations'; @@ -30,7 +30,7 @@ export function useBorrowingPower( const { totalInQuote } = useUserDeposits(exclude, inlcude); - const price = useMidPriceInUSD(liquidityMintAddress).price; + const price = usePrice(liquidityMintAddress); const { totalInQuote: loansValue } = useUserObligations(); diff --git a/packages/lending/src/hooks/useCollateralBalance.ts b/packages/lending/src/hooks/useCollateralBalance.ts index 5e5c654d..b3487c5b 100644 --- a/packages/lending/src/hooks/useCollateralBalance.ts +++ b/packages/lending/src/hooks/useCollateralBalance.ts @@ -1,8 +1,8 @@ import { contexts, fromLamports } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; -import { useMarkets } from '../contexts/market'; import { Reserve, reserveMarketCap } from '../models'; +import { usePrice } from '../contexts/pyth'; import { useUserBalance } from './useUserBalance'; const { useMint } = contexts.Accounts; @@ -18,7 +18,6 @@ export function useUserCollateralBalance( ); const [balanceInUSD, setBalanceInUSD] = useState(0); - const { marketEmitter, midPriceInUSD } = useMarkets(); const balanceLamports = useMemo( () => reserve && calculateCollateralBalance(reserve, userBalance), @@ -30,26 +29,11 @@ export function useUserCollateralBalance( mint, ]); - useEffect(() => { - const updateBalance = () => { - setBalanceInUSD( - balance * - midPriceInUSD(reserve?.liquidity.mintPubkey.toBase58() || ''), - ); - }; - - const dispose = marketEmitter.onMarket(args => { - if (args.ids.has(reserve?.liquidity.oraclePubkey.toBase58() || '')) { - updateBalance(); - } - }); + const price = usePrice(reserve?.liquidity.mintPubkey.toBase58() || ''); - updateBalance(); - - return () => { - dispose(); - }; - }, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD, reserve]); + useEffect(() => { + setBalanceInUSD(balance * price); + }, [setBalanceInUSD, balance, price]); return { balance, diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index d77004d8..ddb4fbaf 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -8,7 +8,7 @@ import { import { MintInfo } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useMarkets } from '../contexts/market'; +import { usePyth } from '../contexts/pyth'; import { collateralToLiquidity, Obligation, Reserve } from '../models'; import { useLendingObligations } from './useLendingObligations'; import { useLendingReserves } from './useLendingReserves'; @@ -36,7 +36,7 @@ export function useEnrichedLendingObligations() { const { obligations } = useLendingObligations(); const { reserveAccounts } = useLendingReserves(); const { tokenMap } = useConnectionConfig(); - const { marketEmitter, midPriceInUSD } = useMarkets(); + const { getPrice } = usePyth(); const availableReserves = useMemo(() => { return reserveAccounts.reduce((map, reserve) => { @@ -109,10 +109,10 @@ export function useEnrichedLendingObligations() { ) as ParsedAccount; borrowedInQuote = fromLamports(borrowed, liquidityMint.info) * - midPriceInUSD(liquidityMintAddress); + getPrice(liquidityMintAddress); + // @FIXME: collateral can't be priced by pyth collateralInQuote = - collateral * - midPriceInUSD(collateralMint?.pubkey.toBase58() || ''); + collateral * getPrice(collateralMint?.pubkey.toBase58() || ''); ltv = (100 * borrowedAmount) / collateral; @@ -141,21 +141,15 @@ export function useEnrichedLendingObligations() { }) .sort((a, b) => a.info.health - b.info.health) ); - }, [obligations, availableReserves, midPriceInUSD, tokenMap]); + }, [obligations, availableReserves, getPrice, tokenMap]); const [enriched, setEnriched] = useState( enrichedFactory(), ); useEffect(() => { - const dispose = marketEmitter.onMarket(() => { - setEnriched(enrichedFactory()); - }); - - return () => { - dispose(); - }; - }, [enrichedFactory, setEnriched, marketEmitter, midPriceInUSD]); + setEnriched(enrichedFactory()); + }, [enrichedFactory, setEnriched]); return { obligations: enriched, diff --git a/packages/lending/src/hooks/usePyth.ts b/packages/lending/src/hooks/usePyth.ts index 6f5761e9..b56e28a1 100644 --- a/packages/lending/src/hooks/usePyth.ts +++ b/packages/lending/src/hooks/usePyth.ts @@ -17,7 +17,8 @@ const PYTH_PROGRAM_ID = new PublicKey( type Products = Record; type Prices = Record; -const usePythProducts = () => { +// @TODO: subscribe to changes +export const usePythProducts = () => { const connection = useConnection(); const [products, setProducts] = useState({}); @@ -52,7 +53,7 @@ const usePythProducts = () => { }; // @TODO: aggregate subscriptions by mint -const usePythPriceByMint = (mint: string) => { +export const usePythPriceByMint = (mint: string) => { const { tokenMap } = useConnectionConfig(); const products = usePythProducts(); const connection = useConnection(); @@ -98,15 +99,11 @@ const usePythPriceByMint = (mint: string) => { return price; }; -const useMidPriceInUSD = (mint: string) => { +export const useMidPriceInUSD = (mint: string) => { const price = usePythPriceByMint(mint); return price ? price.price * Math.pow(10, price.exponent) : 0; } -interface ProductAttributes { - [index: string]: string; -} - interface Product { magic: number; version: number; @@ -116,6 +113,10 @@ interface Product { product: ProductAttributes; } +interface ProductAttributes { + [index: string]: string; +} + interface Price { priceComponents: { publisher: PublicKey; diff --git a/packages/lending/src/hooks/useUserBalance.ts b/packages/lending/src/hooks/useUserBalance.ts index 71dcac50..1581fea7 100644 --- a/packages/lending/src/hooks/useUserBalance.ts +++ b/packages/lending/src/hooks/useUserBalance.ts @@ -1,7 +1,7 @@ import { contexts, fromLamports, useUserAccounts } from '@oyster/common'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; -import { useMarkets } from '../contexts/market'; +import { usePrice } from '../contexts/pyth'; const { useMint } = contexts.Accounts; @@ -16,7 +16,6 @@ export function useUserBalance( ); const { userAccounts } = useUserAccounts(); const [balanceInUSD, setBalanceInUSD] = useState(0); - const { marketEmitter, midPriceInUSD } = useMarkets(); const mintInfo = useMint(mint); const accounts = useMemo(() => { @@ -41,21 +40,11 @@ export function useUserBalance( balanceLamports, ]); - useEffect(() => { - const updateBalance = () => { - setBalanceInUSD(balance * midPriceInUSD(mint || '')); - }; - - const dispose = marketEmitter.onMarket(args => { - updateBalance(); - }); + const price = usePrice(mint || ''); - updateBalance(); - - return () => { - dispose(); - }; - }, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD]); + useEffect(() => { + setBalanceInUSD(balance * price); + }, [setBalanceInUSD, balance, price]); return { balance, diff --git a/packages/lending/src/hooks/useUserDeposits.ts b/packages/lending/src/hooks/useUserDeposits.ts index 2861c252..10f172a6 100644 --- a/packages/lending/src/hooks/useUserDeposits.ts +++ b/packages/lending/src/hooks/useUserDeposits.ts @@ -8,7 +8,7 @@ import { } from '@oyster/common'; import { MintInfo } from '@solana/spl-token'; import { useEffect, useMemo, useState } from 'react'; -import { useMarkets } from '../contexts/market'; +import { usePyth } from '../contexts/pyth'; import { calculateDepositAPY, Reserve } from '../models'; import { calculateCollateralBalance } from './useCollateralBalance'; import { useLendingReserves } from './useLendingReserves'; @@ -32,7 +32,7 @@ export function useUserDeposits(exclude?: Set, include?: Set) { const { userAccounts } = useUserAccounts(); const { reserveAccounts } = useLendingReserves(); const [userDeposits, setUserDeposits] = useState([]); - const { marketEmitter, midPriceInUSD } = useMarkets(); + const { getPrice } = usePyth(); const { tokenMap } = useConnectionConfig(); const reservesByCollateralMint = useMemo(() => { @@ -51,10 +51,6 @@ export function useUserDeposits(exclude?: Set, include?: Set) { }, [reserveAccounts, exclude, include]); useEffect(() => { - const activeMarkets = new Set( - reserveAccounts.map(r => r.info.liquidity.oraclePubkey.toBase58()), - ); - const userDepositsFactory = () => { return userAccounts .filter(acc => reservesByCollateralMint.has(acc?.info.mint.toBase58())) @@ -72,9 +68,7 @@ export function useUserDeposits(exclude?: Set, include?: Set) { item?.info.amount.toNumber(), ); const amount = fromLamports(amountLamports, collateralMint?.info); - const price = midPriceInUSD( - reserve.info.liquidity.mintPubkey.toBase58(), - ); + const price = getPrice(reserve.info.liquidity.mintPubkey.toBase58()); const amountInQuote = price * amount; return { @@ -91,27 +85,13 @@ export function useUserDeposits(exclude?: Set, include?: Set) { .sort((a, b) => b.info.amountInQuote - a.info.amountInQuote); }; - const dispose = marketEmitter.onMarket(args => { - // ignore if none of the markets is used by the reserve - if ([...args.ids.values()].every(id => !activeMarkets.has(id))) { - return; - } - - setUserDeposits(userDepositsFactory()); - }); - setUserDeposits(userDepositsFactory()); - - return () => { - dispose(); - }; }, [ userAccounts, reserveAccounts, reservesByCollateralMint, tokenMap, - midPriceInUSD, - marketEmitter, + getPrice, ]); return { diff --git a/packages/lending/src/routes.tsx b/packages/lending/src/routes.tsx index 7f6bfc17..39f44ec5 100644 --- a/packages/lending/src/routes.tsx +++ b/packages/lending/src/routes.tsx @@ -4,6 +4,7 @@ import { HashRouter, Route, Switch } from 'react-router-dom'; import { AppLayout } from './components/Layout'; import { LendingProvider } from './contexts/lending'; import { MarketProvider } from './contexts/market'; +import { PythProvider } from './contexts/pyth'; import { BorrowReserveView, @@ -33,61 +34,63 @@ export function Routes() { - - - - - } /> - } - /> - } /> - } - /> - } - /> - } /> - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> + + + + + + } /> + } + /> + } /> + } + /> + } + /> + } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> - } /> - } /> - - - - + } /> + } /> + + + + + diff --git a/packages/lending/src/views/borrow/item.tsx b/packages/lending/src/views/borrow/item.tsx index 95df8a53..46bc541d 100644 --- a/packages/lending/src/views/borrow/item.tsx +++ b/packages/lending/src/views/borrow/item.tsx @@ -9,14 +9,13 @@ import { Button } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { LABELS } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { useBorrowingPower } from '../../hooks'; import { calculateBorrowAPY, Reserve } from '../../models'; export const BorrowItem = (props: { reserve: Reserve; address: PublicKey }) => { const name = useTokenName(props.reserve.liquidity.mintPubkey); - const price = useMidPriceInUSD(props.reserve.liquidity.mintPubkey.toBase58()) - .price; + const price = usePrice(props.reserve.liquidity.mintPubkey.toBase58()); const { borrowingPower, totalInQuote } = useBorrowingPower(props.address); diff --git a/packages/lending/src/views/home/index.tsx b/packages/lending/src/views/home/index.tsx index 03d14fbc..a7c7efde 100644 --- a/packages/lending/src/views/home/index.tsx +++ b/packages/lending/src/views/home/index.tsx @@ -9,7 +9,7 @@ import { MintInfo } from '@solana/spl-token'; import { Card, Col, Row, Statistic } from 'antd'; import React, { useEffect, useState } from 'react'; import { GUTTER, LABELS } from '../../constants'; -import { useMarkets } from '../../contexts/market'; +import { usePyth } from '../../contexts/pyth'; import { useLendingReserves } from '../../hooks'; import { reserveMarketCap, Totals } from '../../models'; import { BarChartStatistic } from './../../components/BarChartStatistic'; @@ -21,7 +21,7 @@ const { useConnectionConfig } = contexts.Connection; export const HomeView = () => { const { reserveAccounts } = useLendingReserves(); - const { marketEmitter, midPriceInUSD } = useMarkets(); + const { getPrice } = usePyth(); const { tokenMap } = useConnectionConfig(); const [totals, setTotals] = useState({ marketSize: 0, @@ -31,70 +31,54 @@ export const HomeView = () => { }); useEffect(() => { - const refreshTotal = () => { - let newTotals: Totals = { - marketSize: 0, - borrowed: 0, - lentOutPct: 0, - items: [], - }; - - reserveAccounts.forEach(item => { - const marketCapLamports = reserveMarketCap(item.info); - - const localCache = cache; - const liquidityMint = localCache.get( - item.info.liquidity.mintPubkey.toBase58(), - ) as ParsedAccount; - - if (!liquidityMint) { - return; - } - - const price = midPriceInUSD(liquidityMint?.pubkey.toBase58()); - - let leaf = { - key: item.pubkey.toBase58(), - marketSize: - fromLamports(marketCapLamports, liquidityMint?.info) * price, - borrowed: - fromLamports( - wadToLamports(item.info?.liquidity.borrowedAmountWads).toNumber(), - liquidityMint.info, - ) * price, - name: getTokenName( - tokenMap, - item.info.liquidity.mintPubkey.toBase58(), - ), - }; - - newTotals.items.push(leaf); - - newTotals.marketSize = newTotals.marketSize + leaf.marketSize; - newTotals.borrowed = newTotals.borrowed + leaf.borrowed; - }); + let newTotals: Totals = { + marketSize: 0, + borrowed: 0, + lentOutPct: 0, + items: [], + }; - newTotals.lentOutPct = newTotals.borrowed / newTotals.marketSize; - newTotals.lentOutPct = Number.isFinite(newTotals.lentOutPct) - ? newTotals.lentOutPct - : 0; - newTotals.items = newTotals.items.sort( - (a, b) => b.marketSize - a.marketSize, - ); + reserveAccounts.forEach(item => { + const marketCapLamports = reserveMarketCap(item.info); + + const mint = item.info.liquidity.mintPubkey.toBase58(); + + const liquidityMint = cache.get(mint) as + | ParsedAccount + | undefined; + if (!liquidityMint) { + return; + } + + const price = getPrice(mint); + + let leaf = { + key: item.pubkey.toBase58(), + marketSize: fromLamports(marketCapLamports, liquidityMint.info) * price, + borrowed: + fromLamports( + wadToLamports(item.info.liquidity.borrowedAmountWads).toNumber(), + liquidityMint.info, + ) * price, + name: getTokenName(tokenMap, mint), + }; - setTotals(newTotals); - }; + newTotals.items.push(leaf); - const dispose = marketEmitter.onMarket(() => { - refreshTotal(); + newTotals.marketSize = newTotals.marketSize + leaf.marketSize; + newTotals.borrowed = newTotals.borrowed + leaf.borrowed; }); - refreshTotal(); + newTotals.lentOutPct = newTotals.borrowed / newTotals.marketSize; + newTotals.lentOutPct = Number.isFinite(newTotals.lentOutPct) + ? newTotals.lentOutPct + : 0; + newTotals.items = newTotals.items.sort( + (a, b) => b.marketSize - a.marketSize, + ); - return () => { - dispose(); - }; - }, [marketEmitter, midPriceInUSD, setTotals, reserveAccounts, tokenMap]); + setTotals(newTotals); + }, [getPrice, setTotals, reserveAccounts, tokenMap]); return (
diff --git a/packages/lending/src/views/margin/item.tsx b/packages/lending/src/views/margin/item.tsx index f5f671e7..1a1511d8 100644 --- a/packages/lending/src/views/margin/item.tsx +++ b/packages/lending/src/views/margin/item.tsx @@ -9,7 +9,7 @@ import { Button } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { LABELS } from '../../constants'; -import { useMidPriceInUSD } from '../../contexts/market'; +import { usePrice } from '../../contexts/pyth'; import { useBorrowingPower } from '../../hooks'; import { calculateBorrowAPY, Reserve } from '../../models'; @@ -18,8 +18,7 @@ export const MarginTradeItem = (props: { address: PublicKey; }) => { const name = useTokenName(props.reserve.liquidity.mintPubkey); - const price = useMidPriceInUSD(props.reserve.liquidity.mintPubkey.toBase58()) - .price; + const price = usePrice(props.reserve.liquidity.mintPubkey.toBase58()); const apr = calculateBorrowAPY(props.reserve); From 33a3fa3e0903258085a957491bffc6724f49012b Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 17 Jun 2021 18:40:23 -0500 Subject: [PATCH 27/58] don't parse liquidity oracle as dex market --- packages/lending/src/contexts/lending.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index dbe3318d..f81d2afb 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -105,12 +105,6 @@ export const useLending = () => { acc?.info.liquidity.mintPubkey.toBase58(), MintParser, ), - // ignore dex if its not set - // @FIXME: not a dex market - cache.registerParser( - acc?.info.liquidity.oraclePubkey.toBase58(), - DexMarketParser, - ), ].filter(_ => _); return result; }), From b95301d94b594ac8e0ceac3e3e401eb413d1b436 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 14:01:04 -0500 Subject: [PATCH 28/58] remove bad connections --- packages/common/src/contexts/connection.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/common/src/contexts/connection.tsx b/packages/common/src/contexts/connection.tsx index 6bbddada..a0ec93fc 100644 --- a/packages/common/src/contexts/connection.tsx +++ b/packages/common/src/contexts/connection.tsx @@ -25,11 +25,9 @@ import { export type ENV = | 'mainnet-beta (Serum)' | 'mainnet-beta' - | 'mainnet-beta (Serum)' | 'testnet' | 'devnet' - | 'localnet' - | 'lending'; + | 'localnet'; export const ENDPOINTS = [ { @@ -57,16 +55,6 @@ export const ENDPOINTS = [ endpoint: 'http://127.0.0.1:8899', ChainId: ChainId.Devnet, }, - { - name: 'Oyster Dev' as ENV, - endpoint: 'http://oyster-dev.solana.com/', - ChainId: ChainId.Devnet, - }, - { - name: 'Lending' as ENV, - endpoint: 'https://tln.solana.com/', - ChainId: ChainId.Devnet, - }, ]; const DEFAULT = ENDPOINTS[0].endpoint; From 47697063a43de10ded50f1aaf1177a491cdd87c7 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 14:01:19 -0500 Subject: [PATCH 29/58] add pyth program id --- packages/common/src/utils/ids.ts | 12 ++++-------- packages/lending/src/contexts/pyth.tsx | 6 +----- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index db142ab0..626f4f77 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -1,17 +1,16 @@ import { PublicKey } from '@solana/web3.js'; import { TokenSwapLayout, TokenSwapLayoutV1 } from '../models/tokenSwap'; +export let SYSTEM = new PublicKey('11111111111111111111111111111111'); export const WRAPPED_SOL_MINT = new PublicKey( 'So11111111111111111111111111111111111111112', ); export let TOKEN_PROGRAM_ID = new PublicKey( 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', ); - export let LENDING_PROGRAM_ID = new PublicKey( 'LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi', ); - export let SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey( 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', ); @@ -21,24 +20,21 @@ export let BPF_UPGRADE_LOADER_ID = new PublicKey( export let METADATA_PROGRAM_ID = new PublicKey( 'metaTA73sFPqA8whreUbBsbn3SLJH2vhrW9fP5dmfdC', ); - export const MEMO_ID = new PublicKey( 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', ); - export const VAULT_ID = new PublicKey( '94wRaYAQdC2gYF76AUTYSugNJ3rAC4EimjAMPwM7uYry', ); - export const AUCTION_ID = new PublicKey( 'C9nHkL6BfGx9M9MyYrJqAD5hPsGJd1fHpp1uAJA6vTCn', ); - export const METAPLEX_ID = new PublicKey( 'EPtpKdKW8qciGVd1UFyGjgbBHTbSAyvbY61h9uQGVgeu', ); - -export let SYSTEM = new PublicKey('11111111111111111111111111111111'); +export const PYTH_PROGRAM_ID = new PublicKey( + 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2', +); let WORMHOLE_BRIDGE: { pubkey: PublicKey; diff --git a/packages/lending/src/contexts/pyth.tsx b/packages/lending/src/contexts/pyth.tsx index 6c8b62c1..56624995 100644 --- a/packages/lending/src/contexts/pyth.tsx +++ b/packages/lending/src/contexts/pyth.tsx @@ -1,4 +1,4 @@ -import { contexts, useConnectionConfig } from '@oyster/common'; +import { contexts, PYTH_PROGRAM_ID, useConnectionConfig } from '@oyster/common'; import { parseMappingData, parsePriceData, @@ -10,10 +10,6 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; const { useConnection } = contexts.Connection; const { getMultipleAccounts } = contexts.Accounts; -const PYTH_PROGRAM_ID = new PublicKey( - 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2', -); - type Products = Record; type Prices = Record; From e751324b2a154cdf79ccc24bfda28ac427bf8120 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 14:01:44 -0500 Subject: [PATCH 30/58] use oyster/common from file --- packages/lending/package.json | 2 +- yarn.lock | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/lending/package.json b/packages/lending/package.json index ce1c2493..ef1c8e71 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -6,7 +6,7 @@ "@ant-design/pro-layout": "^6.7.0", "@babel/preset-typescript": "^7.12.13", "@craco/craco": "^5.7.0", - "@oyster/common": "0.0.1", + "@oyster/common": "file:../common/src", "@project-serum/serum": "^0.13.11", "@project-serum/sol-wallet-adapter": "^0.1.4", "@pythnetwork/client": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 7d591590..37838dbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2885,6 +2885,9 @@ dependencies: "@octokit/openapi-types" "^6.0.0" +"@oyster/common@file:packages/common/src": + version "0.0.0" + "@pedrouid/iso-crypto@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.1.0.tgz#3fb4050ea99f2f8ee41ba8661193c0989c815c95" @@ -10885,7 +10888,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: +glob@7.1.6, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -10897,6 +10900,18 @@ glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.0.0: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" From c2b1dc3ee9b13c6251f31c78e885bbd2f5a192b9 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 14:01:53 -0500 Subject: [PATCH 31/58] remove unused import --- packages/lending/src/contexts/lending.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index f81d2afb..60f42cdc 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -11,7 +11,6 @@ import { Reserve, ReserveParser, } from '../models'; -import { DexMarketParser } from '../models/dex'; import { usePrecacheMarket } from './market'; const { useConnection } = contexts.Connection; From 99783c00db3bc40fd34b867c41998272646b4682 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 15:17:21 -0500 Subject: [PATCH 32/58] add placeholders for lending program ids --- packages/common/src/utils/ids.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index 626f4f77..98212aa9 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -80,6 +80,7 @@ export const PROGRAM_IDS = [ // new PublicKey("9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL"), ], }), + lending: () => new PublicKey('LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi'), }, { name: 'testnet', @@ -98,8 +99,8 @@ export const PROGRAM_IDS = [ }, legacy: [], }), + lending: () => new PublicKey('LendXXX'), }, - { name: 'devnet', governance: () => ({ @@ -117,6 +118,7 @@ export const PROGRAM_IDS = [ }, legacy: [new PublicKey('BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ')], }), + lending: () => new PublicKey('LendXXX'), }, { name: 'localnet', @@ -135,6 +137,7 @@ export const PROGRAM_IDS = [ }, legacy: [], }), + lending: () => new PublicKey('LendXXX'), }, ]; @@ -154,11 +157,7 @@ export const setProgramIds = (envName: string) => { GOVERNANCE = instance.governance(); - if (envName === 'mainnet-beta') { - LENDING_PROGRAM_ID = new PublicKey( - 'LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi', - ); - } + LENDING_PROGRAM_ID = instance.lending(); }; export const programIds = () => { From 29b352aec3b99548c8d349ced006b63f114a68f7 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 18 Jun 2021 15:22:47 -0500 Subject: [PATCH 33/58] update serum, buffer-layout deps --- packages/common/package.json | 4 ++-- packages/lending/package.json | 4 ++-- yarn.lock | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index 2f90825f..57312d44 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -25,7 +25,7 @@ "prepare": "run-s clean build" }, "dependencies": { - "@project-serum/serum": "^0.13.11", + "@project-serum/serum": "^0.13.38", "@project-serum/sol-wallet-adapter": "^0.1.4", "@solana/spl-token": "0.0.13", "@solana/spl-token-swap": "0.1.0", @@ -44,7 +44,7 @@ "bn.js": "^5.1.3", "borsh": "^0.3.1", "bs58": "^4.0.1", - "buffer-layout": "^1.2.0", + "buffer-layout": "^1.2.1", "eventemitter3": "^4.0.7", "identicon.js": "^2.3.3", "jazzicon": "^1.5.0", diff --git a/packages/lending/package.json b/packages/lending/package.json index ef1c8e71..b490409f 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -7,7 +7,7 @@ "@babel/preset-typescript": "^7.12.13", "@craco/craco": "^5.7.0", "@oyster/common": "file:../common/src", - "@project-serum/serum": "^0.13.11", + "@project-serum/serum": "^0.13.38", "@project-serum/sol-wallet-adapter": "^0.1.4", "@pythnetwork/client": "^2.0.0", "@solana/spl-token": "0.0.13", @@ -26,7 +26,7 @@ "antd": "^4.6.6", "bn.js": "^5.1.3", "bs58": "^4.0.1", - "buffer-layout": "^1.2.0", + "buffer-layout": "^1.2.1", "chart.js": "^2.9.4", "craco-alias": "^2.1.1", "craco-babel-loader": "^0.1.4", diff --git a/yarn.lock b/yarn.lock index 37838dbf..e39dc69b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2953,6 +2953,15 @@ bn.js "^5.1.2" buffer-layout "^1.2.0" +"@project-serum/serum@^0.13.38": + version "0.13.38" + resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.38.tgz#ef50a0f50bd69fd7b51309fbb44ad995a1e6e210" + integrity sha512-TOph1Hxoi5kOUg72tWbbNGviqBw29SrP4BH70gXtqUDiFTQJLrE2yfS5HC7p0JaU8p9WdrYGnxcFKCddZJ3ing== + dependencies: + "@solana/web3.js" "^0.90.0" + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@project-serum/sol-wallet-adapter@^0.1.4": version "0.1.8" resolved "https://registry.yarnpkg.com/@project-serum/sol-wallet-adapter/-/sol-wallet-adapter-0.1.8.tgz#90c6c1da793d32ed4ba3c67c5702a5bc804ef197" @@ -6492,6 +6501,11 @@ buffer-layout@1.2.0, buffer-layout@^1.2.0: resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.0.tgz#ee1f5ef05a8afd5db6b3a8fe2056c111bc69c737" integrity sha512-iiyRoho/ERzBUv6kFvfsrLNgTlVwOkqQcSQN7WrO3Y+c5SeuEhCn6+y1KwhM0V3ndptF5mI/RI44zkw0qcR5Jg== +buffer-layout@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.1.tgz#17b6db7abea303cab35eebd77919b89de026297d" + integrity sha512-RUTGEYG1vX0Zp1dStQFl8yeU/LEBPXVtHwzzDbPWkE5zq+Prt9fkFLKNiwmaeHg6BBiRMcQAgj4cynazO6eekw== + buffer-to-arraybuffer@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" From f39b03ef1fccb4f2918f99fe91afa470eb33ee5d Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 22 Jun 2021 19:12:17 -0500 Subject: [PATCH 34/58] fixing pubkeys --- packages/common/src/utils/ids.ts | 6 +++--- packages/lending/src/models/dex/market.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index 03de2e06..4086b345 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -99,7 +99,7 @@ export const PROGRAM_IDS = [ }, legacy: [], }), - lending: () => new PublicKey('LendXXX'), + lending: () => new PublicKey('6TvznH3B2e3p2mbhufNBpgSrLx6UkgvxtVQvopEZ2kuH'), }, { name: 'devnet', @@ -118,7 +118,7 @@ export const PROGRAM_IDS = [ }, legacy: [new PublicKey('BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ')], }), - lending: () => new PublicKey('LendXXX'), + lending: () => new PublicKey('6TvznH3B2e3p2mbhufNBpgSrLx6UkgvxtVQvopEZ2kuH'), }, { name: 'localnet', @@ -137,7 +137,7 @@ export const PROGRAM_IDS = [ }, legacy: [], }), - lending: () => new PublicKey('LendXXX'), + lending: () => new PublicKey('6TvznH3B2e3p2mbhufNBpgSrLx6UkgvxtVQvopEZ2kuH'), }, ]; diff --git a/packages/lending/src/models/dex/market.ts b/packages/lending/src/models/dex/market.ts index 68e582d0..db916e96 100644 --- a/packages/lending/src/models/dex/market.ts +++ b/packages/lending/src/models/dex/market.ts @@ -18,8 +18,10 @@ export const OrderBookParser = (id: PublicKey, acc: AccountInfo) => { return details; }; +// Mainnet Beta: 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin + const DEFAULT_DEX_ID = new PublicKey( - 'EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o', + 'DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY', ); export const DexMarketParser = ( From 4580be551435efc1bfd2c436955065607ca2822f Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 22 Jun 2021 19:12:40 -0500 Subject: [PATCH 35/58] fixing config --- packages/lending/package.json | 6 +++--- packages/lending/tsconfig.json | 2 +- yarn.lock | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/lending/package.json b/packages/lending/package.json index b490409f..bce82a33 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -6,7 +6,7 @@ "@ant-design/pro-layout": "^6.7.0", "@babel/preset-typescript": "^7.12.13", "@craco/craco": "^5.7.0", - "@oyster/common": "file:../common/src", + "@oyster/common": "0.0.1", "@project-serum/serum": "^0.13.38", "@project-serum/sol-wallet-adapter": "^0.1.4", "@pythnetwork/client": "^2.0.0", @@ -47,7 +47,6 @@ "scripts": { "prestart": "npm-link-shared ../common/node_modules/ . react", "start": "craco start --verbose", - "start:lending": "craco start --verbose", "build": "craco build", "test": "craco test", "eject": "react-scripts eject", @@ -97,6 +96,7 @@ }, "resolutions": { "react": "16.13.1", - "react-dom": "16.13.1" + "react-dom": "16.13.1", + "@oyster/common": "file:../common/dist/lib" } } diff --git a/packages/lending/tsconfig.json b/packages/lending/tsconfig.json index 7420e1f7..a4d8f941 100644 --- a/packages/lending/tsconfig.json +++ b/packages/lending/tsconfig.json @@ -18,5 +18,5 @@ "jsx": "react", "typeRoots": ["types", "../../types", "../../node_modules/@types"] }, - "include": ["src"] + "include": ["src", "../../node_modules/@oyster/dist/lib"] } diff --git a/yarn.lock b/yarn.lock index e39dc69b..a66c3878 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2885,9 +2885,6 @@ dependencies: "@octokit/openapi-types" "^6.0.0" -"@oyster/common@file:packages/common/src": - version "0.0.0" - "@pedrouid/iso-crypto@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.1.0.tgz#3fb4050ea99f2f8ee41ba8661193c0989c815c95" From f811d7a4d05ce506857efd6c678aaaabfe524b45 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Tue, 22 Jun 2021 19:12:54 -0500 Subject: [PATCH 36/58] working on fixing dex market init --- packages/lending/src/contexts/market.tsx | 37 ++++++++++++------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index 31c2ad4f..1fdb1120 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -99,7 +99,7 @@ export function MarketProvider({ children = null as any }) { timer = window.setTimeout(() => updateData(), REFRESH_INTERVAL); }; - const initalQuery = async () => { + const initialQuery = async () => { const reverseSerumMarketCache = new Map(); [...marketByMint.keys()].forEach(mint => { const m = marketByMint.get(mint); @@ -112,28 +112,27 @@ export function MarketProvider({ children = null as any }) { return m.marketInfo.address.toBase58(); }); - await getMultipleAccounts( + const accounts = await getMultipleAccounts( connection, - // only query for markets that are not in cahce + // only query for markets that are not in cache allMarkets.filter(a => cache.get(a) === undefined), 'single', - ).then(({ keys, array }) => { - allMarkets.forEach(() => {}); - - return array.map((item, index) => { - const marketAddress = keys[index]; - const mintAddress = reverseSerumMarketCache.get(marketAddress); - if (mintAddress) { - const market = marketByMint.get(mintAddress); - - if (market) { - const id = market.marketInfo.address; - cache.add(id, item, DexMarketParser); - } + ); + + const { keys, array } = accounts; + array.forEach((item, index) => { + const marketAddress = keys[index]; + const mintAddress = reverseSerumMarketCache.get(marketAddress); + if (mintAddress) { + const market = marketByMint.get(mintAddress); + + if (market) { + const id = market.marketInfo.address; + cache.add(id, item, DexMarketParser); } + } - return item; - }); + return item; }); const toQuery = new Set(); @@ -165,7 +164,7 @@ export function MarketProvider({ children = null as any }) { updateData(); }; - initalQuery(); + initialQuery(); return () => { window.clearTimeout(timer); From aeadf50cd5a50527dd7657bd0e28aa507bab98ae Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:03:23 -0500 Subject: [PATCH 37/58] refresh reserves and obligation as needed --- .../src/actions/borrowObligationLiquidity.tsx | 14 +----- .../helpers/refreshObligationAndReserves.tsx | 50 +++++++++++++++++++ .../src/actions/liquidateObligation.tsx | 24 ++------- .../src/actions/repayObligationLiquidity.tsx | 23 ++------- .../actions/withdrawObligationCollateral.tsx | 23 +++------ 5 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx diff --git a/packages/lending/src/actions/borrowObligationLiquidity.tsx b/packages/lending/src/actions/borrowObligationLiquidity.tsx index 25c02538..6b1790fd 100644 --- a/packages/lending/src/actions/borrowObligationLiquidity.tsx +++ b/packages/lending/src/actions/borrowObligationLiquidity.tsx @@ -17,10 +17,9 @@ import { import { borrowObligationLiquidityInstruction, Obligation, - refreshObligationInstruction, - refreshReserveInstruction, Reserve, } from '../models'; +import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; const { cache, MintParser } = contexts.Accounts; const { sendTransaction } = contexts.Connection; @@ -109,16 +108,7 @@ export const borrowObligationLiquidity = async ( cleanupInstructions = [...finalCleanupInstructions]; instructions.push( - // @TODO: refresh all obligation reserves - refreshReserveInstruction( - borrowReserve.pubkey, - borrowReserve.info.liquidity.oraclePubkey, - ), - refreshObligationInstruction( - obligation.pubkey, - obligation.info.deposits.map(collateral => collateral.depositReserve), - obligation.info.borrows.map(liquidity => liquidity.borrowReserve), - ), + ...await refreshObligationAndReserves(connection, obligation), borrowObligationLiquidityInstruction( amountLamports, borrowReserve.info.liquidity.supplyPubkey, diff --git a/packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx b/packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx new file mode 100644 index 00000000..f59ac5ed --- /dev/null +++ b/packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx @@ -0,0 +1,50 @@ +import { contexts, ParsedAccount } from '@oyster/common'; +import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { + Obligation, + refreshObligationInstruction, + refreshReserveInstruction, + Reserve, + ReserveParser, +} from '../../models'; + +const { cache } = contexts.Accounts; + +export const refreshObligationAndReserves = async ( + connection: Connection, + obligation: ParsedAccount, +) => { + const instructions = [] as TransactionInstruction[]; + const reserves = {} as Record; + + for (const collateral of obligation.info.deposits) { + reserves[collateral.depositReserve.toBase58()] = collateral.depositReserve; + } + for (const liquidity of obligation.info.borrows) { + reserves[liquidity.borrowReserve.toBase58()] = liquidity.borrowReserve; + } + + await Promise.all( + Object.values(reserves).map(async pubkey => { + const reserve = (await cache.query( + connection, + pubkey, + ReserveParser, + )) as ParsedAccount; + + instructions.push( + refreshReserveInstruction(pubkey, reserve.info.liquidity.oraclePubkey), + ); + }), + ); + + instructions.push( + refreshObligationInstruction( + obligation.pubkey, + obligation.info.deposits.map(collateral => collateral.depositReserve), + obligation.info.borrows.map(liquidity => liquidity.borrowReserve), + ), + ); + + return instructions; +}; diff --git a/packages/lending/src/actions/liquidateObligation.tsx b/packages/lending/src/actions/liquidateObligation.tsx index e47cb5df..96a495cd 100644 --- a/packages/lending/src/actions/liquidateObligation.tsx +++ b/packages/lending/src/actions/liquidateObligation.tsx @@ -15,13 +15,8 @@ import { PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { - liquidateObligationInstruction, - Obligation, - refreshObligationInstruction, - refreshReserveInstruction, - Reserve -} from '../models'; +import { liquidateObligationInstruction, Obligation, Reserve } from '../models'; +import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; const { approve } = models; @@ -85,20 +80,7 @@ export const liquidateObligation = async ( ); instructions.push( - // @TODO: refresh all obligation reserves - refreshReserveInstruction( - repayReserve.pubkey, - repayReserve.info.liquidity.oraclePubkey, - ), - refreshReserveInstruction( - withdrawReserve.pubkey, - withdrawReserve.info.liquidity.oraclePubkey, - ), - refreshObligationInstruction( - obligation.pubkey, - obligation.info.deposits.map(collateral => collateral.depositReserve), - obligation.info.borrows.map(liquidity => liquidity.borrowReserve), - ), + ...await refreshObligationAndReserves(connection, obligation), liquidateObligationInstruction( liquidityAmount, sourceAccount, diff --git a/packages/lending/src/actions/repayObligationLiquidity.tsx b/packages/lending/src/actions/repayObligationLiquidity.tsx index 6480689c..bee8001d 100644 --- a/packages/lending/src/actions/repayObligationLiquidity.tsx +++ b/packages/lending/src/actions/repayObligationLiquidity.tsx @@ -8,17 +8,13 @@ import { TokenAccount, } from '@oyster/common'; import { AccountLayout, NATIVE_MINT, Token } from '@solana/spl-token'; +import { Account, Connection, TransactionInstruction } from '@solana/web3.js'; import { - Account, - Connection, - TransactionInstruction, -} from '@solana/web3.js'; -import { - Obligation, refreshObligationInstruction, - refreshReserveInstruction, + Obligation, repayObligationLiquidityInstruction, - Reserve + Reserve, } from '../models'; +import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; const { approve } = models; @@ -81,16 +77,7 @@ export const repayObligationLiquidity = async ( signers.push(transferAuthority); instructions.push( - // @TODO: remove after refresh of obligation + reserves on repay is no longer required - refreshReserveInstruction( - repayReserve.pubkey, - repayReserve.info.liquidity.oraclePubkey, - ), - refreshObligationInstruction( - obligation.pubkey, - obligation.info.deposits.map(collateral => collateral.depositReserve), - obligation.info.borrows.map(liquidity => liquidity.borrowReserve), - ), + ...await refreshObligationAndReserves(connection, obligation), repayObligationLiquidityInstruction( liquidityAmount, sourceLiquidity, diff --git a/packages/lending/src/actions/withdrawObligationCollateral.tsx b/packages/lending/src/actions/withdrawObligationCollateral.tsx index 55c19728..9b6ce645 100644 --- a/packages/lending/src/actions/withdrawObligationCollateral.tsx +++ b/packages/lending/src/actions/withdrawObligationCollateral.tsx @@ -1,9 +1,10 @@ import { findOrCreateAccountByMint, LENDING_PROGRAM_ID, - notify, ParsedAccount, + notify, + ParsedAccount, sendTransaction, - TokenAccount + TokenAccount, } from '@oyster/common'; import { AccountLayout } from '@solana/spl-token'; import { @@ -13,10 +14,11 @@ import { TransactionInstruction, } from '@solana/web3.js'; import { + Obligation, + Reserve, withdrawObligationCollateralInstruction, - refreshReserveInstruction, - Reserve, refreshObligationInstruction, Obligation } from '../models'; +import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; export const withdrawObligationCollateral = async ( connection: Connection, @@ -61,16 +63,7 @@ export const withdrawObligationCollateral = async ( ); instructions.push( - // @TODO: refresh all obligation reserves - refreshReserveInstruction( - reserveAddress, - reserve.liquidity.oraclePubkey, - ), - refreshObligationInstruction( - obligation.pubkey, - obligation.info.deposits.map(collateral => collateral.depositReserve), - obligation.info.borrows.map(liquidity => liquidity.borrowReserve), - ), + ...(await refreshObligationAndReserves(connection, obligation)), withdrawObligationCollateralInstruction( collateralAmount, reserve.collateral.supplyPubkey, @@ -79,7 +72,7 @@ export const withdrawObligationCollateral = async ( obligation.pubkey, reserve.lendingMarket, lendingMarketAuthority, - wallet.publicKey + wallet.publicKey, ), ); From 0027e7d5af9922818d84bd7074ce4672716f1598 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:03:42 -0500 Subject: [PATCH 38/58] add devnet tokens from mango --- packages/common/src/contexts/connection.tsx | 68 +++++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/packages/common/src/contexts/connection.tsx b/packages/common/src/contexts/connection.tsx index 03abfdfc..384d2042 100644 --- a/packages/common/src/contexts/connection.tsx +++ b/packages/common/src/contexts/connection.tsx @@ -1,4 +1,8 @@ -import { sleep, useLocalStorageState } from '../utils/utils'; +import { + ENV as ChainId, + TokenInfo, + TokenListProvider, +} from '@solana/spl-token-registry'; import { Account, BlockhashAndFeeCalculator, @@ -13,14 +17,10 @@ import { TransactionSignature, } from '@solana/web3.js'; import React, { useContext, useEffect, useMemo, useState } from 'react'; -import { notify } from '../utils/notifications'; import { ExplorerLink } from '../components/ExplorerLink'; import { setProgramIds } from '../utils/ids'; -import { - TokenInfo, - TokenListProvider, - ENV as ChainId, -} from '@solana/spl-token-registry'; +import { notify } from '../utils/notifications'; +import { sleep, useLocalStorageState } from '../utils/utils'; export type ENV = | 'mainnet-beta (Serum)' @@ -95,14 +95,12 @@ export function ConnectionProvider({ children = undefined as any }) { DEFAULT_SLIPPAGE.toString(), ); - const connection = useMemo( - () => new Connection(endpoint, 'recent'), - [endpoint], - ); - const sendConnection = useMemo( - () => new Connection(endpoint, 'recent'), - [endpoint], - ); + const connection = useMemo(() => new Connection(endpoint, 'recent'), [ + endpoint, + ]); + const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [ + endpoint, + ]); const env = ENDPOINTS.find(end => end.endpoint === endpoint)?.name || ENDPOINTS[0].name; @@ -112,7 +110,7 @@ export function ConnectionProvider({ children = undefined as any }) { useEffect(() => { // fetch token files new TokenListProvider().resolve().then(container => { - const list = container + const tokens = container .excludeByTag('nft') .filterByChainId( ENDPOINTS.find(end => end.endpoint === endpoint)?.ChainId || @@ -120,13 +118,45 @@ export function ConnectionProvider({ children = undefined as any }) { ) .getList(); - const knownMints = [...list].reduce((map, item) => { + // @FIXME: remove hardcoded values + if (endpoint.ChainId === ChainId.Devnet) { + tokens.push( + { + chainId: 103, + address: '9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S', + symbol: 'SRM', + name: 'Serum', + decimals: 6, + logoURI: + 'https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png', + tags: [], + extensions: { + website: 'https://projectserum.com/', + }, + }, + { + chainId: 103, + address: '7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm', + symbol: 'USDT', + name: 'USDT', + decimals: 6, + logoURI: + 'https://cdn.jsdelivr.net/gh/solana-labs/explorer/public/tokens/usdt.svg', + tags: ['stablecoin'], + extensions: { + website: 'https://tether.to/', + }, + }, + ); + } + + const tokenMap = tokens.reduce((map, item) => { map.set(item.address, item); return map; }, new Map()); - setTokenMap(knownMints); - setTokens(list); + setTokenMap(tokenMap); + setTokens(tokens); }); }, [env]); From b865087de81741c03c1c2cbe1f42ba58a7f105e2 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:04:21 -0500 Subject: [PATCH 39/58] remove old pyth hook --- packages/lending/src/hooks/usePyth.ts | 177 -------------------------- 1 file changed, 177 deletions(-) delete mode 100644 packages/lending/src/hooks/usePyth.ts diff --git a/packages/lending/src/hooks/usePyth.ts b/packages/lending/src/hooks/usePyth.ts deleted file mode 100644 index b56e28a1..00000000 --- a/packages/lending/src/hooks/usePyth.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { contexts, useConnectionConfig } from '@oyster/common'; -import { - parseMappingData, - parsePriceData, - parseProductData, -} from '@pythnetwork/client'; -import { PublicKey } from '@solana/web3.js'; -import { useEffect, useState } from 'react'; - -const { useConnection } = contexts.Connection; -const { getMultipleAccounts } = contexts.Accounts; - -const PYTH_PROGRAM_ID = new PublicKey( - 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2', -); - -type Products = Record; -type Prices = Record; - -// @TODO: subscribe to changes -export const usePythProducts = () => { - const connection = useConnection(); - const [products, setProducts] = useState({}); - - useEffect(() => { - (async () => { - try { - const accountInfo = await connection.getAccountInfo(PYTH_PROGRAM_ID); - if (!accountInfo || !accountInfo.data) return; - - const { productAccountKeys } = parseMappingData(accountInfo.data); - - const productInfos = await getMultipleAccounts( - connection, - productAccountKeys.map(p => p.toBase58()), - 'confirmed', - ); - - const products = productInfos.array.reduce((products, p) => { - const product = parseProductData(p.data); - const symbol = product.product['symbol']; - products[symbol] = product; - return products; - }, {} as Products); - setProducts(products); - } catch (e) { - console.error(e); - } - })(); - }, [connection]); - - return products; -}; - -// @TODO: aggregate subscriptions by mint -export const usePythPriceByMint = (mint: string) => { - const { tokenMap } = useConnectionConfig(); - const products = usePythProducts(); - const connection = useConnection(); - const [price, setPrice] = useState(); - - const symbol = tokenMap.get(mint)?.symbol; - - useEffect(() => { - const subscriptionIds = [] as number[]; - if (symbol) { - const product = products[`${symbol}/USD`]; - if (product) { - (async () => { - try { - const accountInfo = await connection.getAccountInfo( - product.priceAccountKey, - ); - if (!accountInfo || !accountInfo.data) return; - - const price = parsePriceData(accountInfo.data); - setPrice(price); - - subscriptionIds.push( - connection.onAccountChange(product.priceAccountKey, accountInfo => { - const price = parsePriceData(accountInfo.data); - setPrice(price); - }), - ); - } - catch (e) { - console.error(e); - } - })(); - } - } - return () => { - for (const subscriptionId of subscriptionIds) { - connection.removeAccountChangeListener(subscriptionId); - } - }; - }, [products, symbol]); - - return price; -}; - -export const useMidPriceInUSD = (mint: string) => { - const price = usePythPriceByMint(mint); - return price ? price.price * Math.pow(10, price.exponent) : 0; -} - -interface Product { - magic: number; - version: number; - type: number; - size: number; - priceAccountKey: PublicKey; - product: ProductAttributes; -} - -interface ProductAttributes { - [index: string]: string; -} - -interface Price { - priceComponents: { - publisher: PublicKey; - aggregate: { - priceComponent: bigint; - price: number; - confidenceComponent: bigint; - confidence: number; - status: number; - corporateAction: number; - publishSlot: bigint; - }; - latest: { - priceComponent: bigint; - price: number; - confidenceComponent: bigint; - confidence: number; - status: number; - corporateAction: number; - publishSlot: bigint; - }; - }[]; - priceComponent: bigint; - price: number; - confidenceComponent: bigint; - confidence: number; - status: number; - corporateAction: number; - publishSlot: bigint; - magic: number; - version: number; - type: number; - size: number; - priceType: number; - exponent: number; - numComponentPrices: number; - currentSlot: bigint; - validSlot: bigint; - twapComponent: bigint; - twap: number; - avolComponent: bigint; - avol: number; - drv0Component: bigint; - drv0: number; - drv1Component: bigint; - drv1: number; - drv2Component: bigint; - drv2: number; - drv3Component: bigint; - drv3: number; - drv4Component: bigint; - drv4: number; - drv5Component: bigint; - drv5: number; - productAccountKey: PublicKey; - nextPriceAccountKey: PublicKey | null; - aggregatePriceUpdaterAccountKey: PublicKey; -} From 11d8ba6047d8b8218e905138b21f49de09be9657 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:04:59 -0500 Subject: [PATCH 40/58] fix pyth subscription --- packages/lending/src/contexts/pyth.tsx | 94 ++++++++------------------ 1 file changed, 29 insertions(+), 65 deletions(-) diff --git a/packages/lending/src/contexts/pyth.tsx b/packages/lending/src/contexts/pyth.tsx index 56624995..ef163b19 100644 --- a/packages/lending/src/contexts/pyth.tsx +++ b/packages/lending/src/contexts/pyth.tsx @@ -11,7 +11,7 @@ const { useConnection } = contexts.Connection; const { getMultipleAccounts } = contexts.Accounts; type Products = Record; -type Prices = Record; +type Prices = Record; type Subscription = { id: number; count: number } | undefined; type Subscriptions = Record; @@ -20,14 +20,12 @@ export interface PythContextState { products: Products; prices: Prices; getPrice: (mint: string) => number; - subscribeToPrice: (mint: string) => () => void; } const PythContext = React.createContext({ products: {}, prices: {}, getPrice: (mint: string) => 0, - subscribeToPrice: (mint: string) => () => undefined, }); export function PythProvider({ children = null as any }) { @@ -64,72 +62,40 @@ export function PythProvider({ children = null as any }) { })(); }, [connection, setProducts]); - const getPrice = useCallback( + const subscribeToPrice = useCallback( (mint: string) => { - const symbol = tokenMap.get(mint)?.symbol; - if (!symbol) return 0; + let subscription = subscriptions[mint]; + if (subscription) return; + + let symbol = tokenMap.get(mint)?.symbol; + if (!symbol) return; - const price = prices[symbol]; - if (!price) return 0; + const product = products[`${ symbol }/USD`]; + if (!product) return; + + const id = connection.onAccountChange(product.priceAccountKey, function (accountInfo) { + try { + const price = parsePriceData(accountInfo.data); + setPrices({ ...prices, [mint]: price.price }); + } + catch (e) { + console.error(e); + } + }); - return price.price * Math.pow(10, price.exponent); + // @TODO: add subscription counting / removal + subscription = { id, count: 1 }; + setSubscriptions({ ...subscriptions, [mint]: subscription }); }, - [tokenMap, prices], + [subscriptions, tokenMap, products, connection, prices, setPrices, setSubscriptions], ); - const subscribeToPrice = useCallback( + const getPrice = useCallback( (mint: string) => { - const tokenSymbol = tokenMap.get(mint)?.symbol; - if (tokenSymbol) { - const symbol = `${tokenSymbol}/USD`; - const product = products[symbol]; - if (product) { - let subscription: Subscription; - (async () => { - try { - const accountInfo = await connection.getAccountInfo( - product.priceAccountKey, - ); - if (!accountInfo || !accountInfo.data) return; - - const price = parsePriceData(accountInfo.data); - setPrices({ ...prices, [symbol]: price }); - - subscription = subscriptions[symbol]; - if (subscription) { - subscription.count++; - } else { - subscription = { - id: connection.onAccountChange( - product.priceAccountKey, - accountInfo => { - const price = parsePriceData(accountInfo.data); - setPrices({ ...prices, [symbol]: price }); - }, - ), - count: 1, - }; - } - setSubscriptions({ ...subscriptions, [symbol]: subscription }); - } catch (e) { - console.error(e); - } - })(); - return () => { - if (subscription) { - subscription.count--; - if (!subscription.count) { - connection.removeAccountChangeListener(subscription.id); - subscription = undefined; - } - setSubscriptions({ ...subscriptions, [symbol]: subscription }); - } - }; - } - } - return () => undefined; + subscribeToPrice(mint); + return prices[mint] || 0; }, - [tokenMap, products, connection, prices, setPrices, subscriptions, setSubscriptions], + [subscribeToPrice, prices], ); return ( @@ -138,7 +104,6 @@ export function PythProvider({ children = null as any }) { products, prices, getPrice, - subscribeToPrice, }} > {children} @@ -151,13 +116,12 @@ export const usePyth = () => { }; export const usePrice = (mint: string) => { - const { getPrice, subscribeToPrice } = useContext(PythContext); + const { getPrice } = useContext(PythContext); const [price, setPrice] = useState(0); useEffect(() => { setPrice(getPrice(mint)); - return subscribeToPrice(mint); - }, [setPrice, getPrice, mint, subscribeToPrice]); + }, [setPrice, getPrice, mint]); return price; }; From 1256f4e07689e1d6734ed37fdd9a7d75b990649c Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:05:29 -0500 Subject: [PATCH 41/58] fix dex market --- packages/lending/src/contexts/market.tsx | 6 +-- packages/lending/src/models/dex/market.ts | 2 +- .../lending/src/models/marketOverrides.ts | 46 ++++++++++++++++++- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index 1fdb1120..2fe84e04 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -26,7 +26,7 @@ import { MINT_TO_MARKET } from './../models/marketOverrides'; const { useConnectionConfig } = contexts.Connection; const { cache, getMultipleAccounts } = contexts.Accounts; -const INITAL_LIQUIDITY_DATE = new Date('2020-10-27'); +const INITIAL_LIQUIDITY_DATE = new Date('2021-06-21'); export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min interface RecentPoolData { @@ -74,7 +74,7 @@ export function MarketProvider({ children = null as any }) { ); const marketAddress = MINT_TO_MARKET[mintAddress]; - const marketName = `${SERUM_TOKEN?.name}/USDC`; + const marketName = `${SERUM_TOKEN?.name}/USDT`; const marketInfo = MARKETS.find( m => m.name === marketName || m.address.toBase58() === marketAddress, ); @@ -383,7 +383,7 @@ function createEnrichedPools( // Aproximation not true for all pools we need to fine a better way const daysSinceInception = Math.floor( - (TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) / + (TODAY.getTime() - INITIAL_LIQUIDITY_DATE.getTime()) / (24 * 3600 * 1000), ); const apy0 = diff --git a/packages/lending/src/models/dex/market.ts b/packages/lending/src/models/dex/market.ts index db916e96..70b548f9 100644 --- a/packages/lending/src/models/dex/market.ts +++ b/packages/lending/src/models/dex/market.ts @@ -21,7 +21,7 @@ export const OrderBookParser = (id: PublicKey, acc: AccountInfo) => { // Mainnet Beta: 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin const DEFAULT_DEX_ID = new PublicKey( - 'DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY', + '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin', ); export const DexMarketParser = ( diff --git a/packages/lending/src/models/marketOverrides.ts b/packages/lending/src/models/marketOverrides.ts index 1a626c83..34c14ec6 100644 --- a/packages/lending/src/models/marketOverrides.ts +++ b/packages/lending/src/models/marketOverrides.ts @@ -1,2 +1,44 @@ -// use to override serum market to use specifc mint -export const MINT_TO_MARKET: { [key: string]: string } = {}; +import { MARKETS, TOKEN_MINTS } from "@project-serum/serum"; +import { PublicKey } from "@solana/web3.js"; + +// @FIXME: overrides should be limited to devnet + +// use to override serum market to use specific mint +export const MINT_TO_MARKET: { [key: string]: string } = { + // SOL/USDT + "So11111111111111111111111111111111111111112": "8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN", + // SRM/USDT + "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S": "CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc", +}; + +TOKEN_MINTS.length = 0; +TOKEN_MINTS.push( + { + address: new PublicKey("7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"), + name: "USDT", + }, + { + address: new PublicKey("9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S"), + name: "SRM", + }, + { + address: new PublicKey("So11111111111111111111111111111111111111112"), + name: "SOL", + } +); + +MARKETS.length = 0; +MARKETS.push( + { + address: new PublicKey("8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN"), + name: "SOL/USDT", + programId: new PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY"), + deprecated: false, + }, + { + address: new PublicKey("CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"), + name: "SRM/USDT", + programId: new PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY"), + deprecated: false, + } +); From ce889d9094de41ced08ebe3a62980b1d31e99725 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:06:44 -0500 Subject: [PATCH 42/58] shorthand object literals --- packages/lending/src/hooks/useUserDeposits.ts | 2 +- packages/lending/src/utils/pools.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/lending/src/hooks/useUserDeposits.ts b/packages/lending/src/hooks/useUserDeposits.ts index 10f172a6..dd64302c 100644 --- a/packages/lending/src/hooks/useUserDeposits.ts +++ b/packages/lending/src/hooks/useUserDeposits.ts @@ -75,7 +75,7 @@ export function useUserDeposits(exclude?: Set, include?: Set) { account: item, info: { amount, - amountInQuote: amountInQuote, + amountInQuote, apy: calculateDepositAPY(reserve.info), name: getTokenName(tokenMap, reserve.info.liquidity.mintPubkey), }, diff --git a/packages/lending/src/utils/pools.ts b/packages/lending/src/utils/pools.ts index 0294b12b..1519b62b 100644 --- a/packages/lending/src/utils/pools.ts +++ b/packages/lending/src/utils/pools.ts @@ -22,7 +22,7 @@ const toPoolInfo = (item: any, program: PublicKey) => { return { pubkeys: { account: item.pubkey, - program: program, + program, mint: item.data.tokenPool, holdingMints: [] as PublicKey[], holdingAccounts: [item.data.tokenAccountA, item.data.tokenAccountB], @@ -162,7 +162,7 @@ export const usePools = () => { const account = info.accountInfo; const updated = { data: programIds().swapLayout.decode(account.data), - account: account, + account, pubkey, }; From 9d8ad541fd5b77d374a6a2a0ca23908d13a1e0a7 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 25 Jun 2021 17:07:03 -0500 Subject: [PATCH 43/58] [WIP] fix borrowing --- .../src/components/BorrowInput/index.tsx | 86 ++++--------------- .../lending/src/hooks/useBorrowingPower.ts | 5 +- 2 files changed, 17 insertions(+), 74 deletions(-) diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx index 90ab01ce..3fb7e26b 100644 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ b/packages/lending/src/components/BorrowInput/index.tsx @@ -15,7 +15,7 @@ import { useSliderInput, useUserBalance, useUserDeposits, - useUserObligationByReserve, + useUserObligationByReserve, useUserObligations } from '../../hooks'; import { Reserve, ReserveParser } from '../../models'; import CollateralInput from '../CollateralInput'; @@ -32,22 +32,15 @@ export const BorrowInput = (props: { }) => { const connection = useConnection(); const { wallet } = useWallet(); - const [collateralValue, setCollateralValue] = useState(''); - const [lastTyped, setLastTyped] = useState('collateral'); + const { userDeposits } = useUserDeposits(); + const { userObligations } = useUserObligations(); + const [pendingTx, setPendingTx] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const borrowReserve = props.reserve; + const depositReserves = []; - const [depositReserveKey, setCollateralReserveKey] = useState(); - - const depositReserve = useMemo(() => { - const id: string = - cache.byParser(ReserveParser).find(acc => acc === depositReserveKey) || - ''; - - return cache.get(id) as ParsedAccount; - }, [depositReserveKey]); const borrowPrice = usePrice( borrowReserve.info.liquidity.mintPubkey.toBase58(), ); @@ -55,14 +48,7 @@ export const BorrowInput = (props: { depositReserve?.info.liquidity.mintPubkey.toBase58(), ); - const include = useMemo(() => new Set([depositReserve?.pubkey.toBase58()]), [ - depositReserve, - ]); - - const exclude = useMemo(() => new Set([]), []); - - const { userDeposits: accountBalance } = useUserDeposits(exclude, include); - const tokenBalance = accountBalance[0]?.info.amount || 0; + const tokenBalance = userDeposits[0]?.info.amount || 0; const convert = useCallback( (val: string | number) => { @@ -79,44 +65,18 @@ export const BorrowInput = (props: { const { value, setValue, pct } = useSliderInput(convert); useEffect(() => { - if (depositReserve && lastTyped === 'collateral') { - const ltv = borrowReserve.info.config.loanToValueRatio / 100; - - if (collateralValue) { - const nCollateralValue = parseFloat(collateralValue); - const borrowInUSD = nCollateralValue * collateralPrice * ltv; - const borrowAmount = borrowInUSD / borrowPrice; - setValue(borrowAmount.toString()); - } else { - setValue(''); - } - } - }, [ - lastTyped, - depositReserve, - collateralPrice, - borrowPrice, - borrowReserve, - collateralValue, - setValue, - ]); - - useEffect(() => { - if (depositReserve && lastTyped === 'borrow') { - const ltv = borrowReserve.info.config.loanToValueRatio / 100; - - if (value) { - const nValue = parseFloat(value); - const borrowInUSD = nValue * borrowPrice; - const collateralAmount = borrowInUSD / ltv / collateralPrice; - setCollateralValue(collateralAmount.toString()); - } else { - setCollateralValue(''); - } + const ltv = borrowReserve.info.config.loanToValueRatio / 100; + + if (value) { + const nValue = parseFloat(value); + const borrowInUSD = nValue * borrowPrice; + const collateralAmount = borrowInUSD / ltv / collateralPrice; + // @FIXME + setCollateralValue(collateralAmount.toString()); + } else { + setCollateralValue(''); } }, [ - lastTyped, - depositReserve, collateralPrice, borrowPrice, borrowReserve, @@ -198,19 +158,6 @@ export const BorrowInput = (props: { alignItems: 'center', }} > - { - setCollateralValue(val?.toString() || ''); - setLastTyped('collateral'); - }} - onCollateralReserve={key => { - setCollateralReserveKey(key); - }} - useFirstReserve={true} - />
{ setValue(val?.toString() || ''); - setLastTyped('borrow'); }} disabled={true} hideBalance={true} diff --git a/packages/lending/src/hooks/useBorrowingPower.ts b/packages/lending/src/hooks/useBorrowingPower.ts index e77bb337..0df07b82 100644 --- a/packages/lending/src/hooks/useBorrowingPower.ts +++ b/packages/lending/src/hooks/useBorrowingPower.ts @@ -25,10 +25,7 @@ export function useBorrowingPower( const liquidityMint = reserve?.info.liquidity.mintPubkey; const liquidityMintAddress = liquidityMint?.toBase58(); - const exclude = useMemo(() => new Set([key]), [key]); - const inlcude = undefined; - - const { totalInQuote } = useUserDeposits(exclude, inlcude); + const { totalInQuote } = useUserDeposits(); const price = usePrice(liquidityMintAddress); From 05571e50da1079bb149f5e4c7a1bdf1e30d57cfb Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 17:01:30 -0500 Subject: [PATCH 44/58] fix compilation --- .../src/components/BorrowInput/index.tsx | 44 ++----------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx index 3fb7e26b..d2faf47c 100644 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ b/packages/lending/src/components/BorrowInput/index.tsx @@ -39,14 +39,10 @@ export const BorrowInput = (props: { const [showConfirmation, setShowConfirmation] = useState(false); const borrowReserve = props.reserve; - const depositReserves = []; const borrowPrice = usePrice( borrowReserve.info.liquidity.mintPubkey.toBase58(), ); - const collateralPrice = usePrice( - depositReserve?.info.liquidity.mintPubkey.toBase58(), - ); const tokenBalance = userDeposits[0]?.info.amount || 0; @@ -64,37 +60,14 @@ export const BorrowInput = (props: { const { value, setValue, pct } = useSliderInput(convert); - useEffect(() => { - const ltv = borrowReserve.info.config.loanToValueRatio / 100; - - if (value) { - const nValue = parseFloat(value); - const borrowInUSD = nValue * borrowPrice; - const collateralAmount = borrowInUSD / ltv / collateralPrice; - // @FIXME - setCollateralValue(collateralAmount.toString()); - } else { - setCollateralValue(''); - } - }, [ - collateralPrice, - borrowPrice, - borrowReserve, - value, - ]); - const { userObligationsByReserve } = useUserObligationByReserve( - borrowReserve?.pubkey, - depositReserve?.pubkey, + borrowReserve?.pubkey ); const { accounts: sourceAccounts } = useUserBalance( - depositReserve?.info.collateral.mintPubkey, + // @FIXME + // depositReserve?.info.collateral.mintPubkey, ); const onBorrow = useCallback(() => { - if (!depositReserve) { - return; - } - setPendingTx(true); (async () => { @@ -109,7 +82,6 @@ export const BorrowInput = (props: { ); setValue(''); - setCollateralValue(''); setShowConfirmation(true); } catch { // TODO: @@ -122,7 +94,6 @@ export const BorrowInput = (props: { wallet, value, setValue, - depositReserve, borrowReserve, userObligationsByReserve, setPendingTx, @@ -150,15 +121,6 @@ export const BorrowInput = (props: { }} >
{LABELS.BORROW_QUESTION}
-
-
Date: Mon, 28 Jun 2021 17:01:39 -0500 Subject: [PATCH 45/58] add flashloan placeholder --- packages/lending/src/models/instructions/instruction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lending/src/models/instructions/instruction.ts b/packages/lending/src/models/instructions/instruction.ts index d342c072..9fdbb595 100644 --- a/packages/lending/src/models/instructions/instruction.ts +++ b/packages/lending/src/models/instructions/instruction.ts @@ -12,4 +12,5 @@ export enum LendingInstruction { BorrowObligationLiquidity = 10, RepayObligationLiquidity = 11, LiquidateObligation = 12, + FlashLoan = 13, } From 5719ae9301313a6e15b73abf2154ed572133a6b4 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 17:01:53 -0500 Subject: [PATCH 46/58] make route components consistent --- packages/lending/src/routes.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/lending/src/routes.tsx b/packages/lending/src/routes.tsx index 39f44ec5..1cec214c 100644 --- a/packages/lending/src/routes.tsx +++ b/packages/lending/src/routes.tsx @@ -43,9 +43,9 @@ export function Routes() { } + component={() => } /> - } /> + } /> } + component={() => } /> - } /> - } /> + } /> + } /> } + component={() => } /> } + component={() => } /> } + component={() => } /> } + component={() => } /> } + component={() => } /> } + component={() => } /> - } /> - } /> + } /> + } /> From 16ad2b13e637bafe17241e7b59be43e7dafb5ec1 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:57:54 -0500 Subject: [PATCH 47/58] obligation init working --- .../lending/src/actions/initObligation.tsx | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/lending/src/actions/initObligation.tsx b/packages/lending/src/actions/initObligation.tsx index d0f2772a..041fe587 100644 --- a/packages/lending/src/actions/initObligation.tsx +++ b/packages/lending/src/actions/initObligation.tsx @@ -1,17 +1,17 @@ -import { sendTransaction, notify } from '@oyster/common'; +import { notify, sendTransaction } from '@oyster/common'; import { Account, Connection, PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { initObligationInstruction, Obligation } from '../models'; +import { initObligationInstruction, ObligationLayout } from '../models'; +import { createObligation } from './createObligation'; export const initObligation = async ( connection: Connection, wallet: any, - obligation: Obligation, - obligationAddress: PublicKey + lendingMarket: PublicKey, ) => { notify({ message: 'Initializing obligation...', @@ -19,18 +19,26 @@ export const initObligation = async ( type: 'warn', }); - // user from account const signers: Account[] = []; const instructions: TransactionInstruction[] = []; const cleanupInstructions: TransactionInstruction[] = []; - signers.push(wallet.info.account); + const obligationRentExempt = await connection.getMinimumBalanceForRentExemption( + ObligationLayout.span, + ); + + const obligationAddress = createObligation( + instructions, + wallet.publicKey, + obligationRentExempt, + signers, + ); instructions.push( initObligationInstruction( obligationAddress, - obligation.lendingMarket, - wallet.publicKey + lendingMarket, + wallet.publicKey, ), ); From 9cbb9ddb1c551dd1bfd6fb293e1675108b0d8b6e Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:58:06 -0500 Subject: [PATCH 48/58] wallet.info.account doesn't exist --- packages/lending/src/actions/withdrawObligationCollateral.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/lending/src/actions/withdrawObligationCollateral.tsx b/packages/lending/src/actions/withdrawObligationCollateral.tsx index 9b6ce645..a163b70b 100644 --- a/packages/lending/src/actions/withdrawObligationCollateral.tsx +++ b/packages/lending/src/actions/withdrawObligationCollateral.tsx @@ -49,8 +49,6 @@ export const withdrawObligationCollateral = async ( LENDING_PROGRAM_ID, ); - signers.push(wallet.info.account); - // get destination account const destinationCollateral = await findOrCreateAccountByMint( wallet.publicKey, From effeeeb867d8d16cdcca2071410a4097c087e04b Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:58:49 -0500 Subject: [PATCH 49/58] check for reserves, obligations also have a lendingMarket pubkey --- packages/lending/src/contexts/lending.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index 60f42cdc..552d265b 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -90,7 +90,7 @@ export const useLending = () => { .filter(item => item !== undefined); const lendingReserves = accounts - .filter(acc => (acc?.info as Reserve).lendingMarket !== undefined) + .filter(acc => acc?.account && isReserve(acc.account) && (acc.info as Reserve).lendingMarket !== undefined) .map(acc => acc as ParsedAccount); const toQuery = [ From 6fcbdf1a51128bd663f94094db3460855777b767 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:59:17 -0500 Subject: [PATCH 50/58] handle new obligations --- packages/lending/src/hooks/useEnrichedLendingObligations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts index ddb4fbaf..247b0e75 100644 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ b/packages/lending/src/hooks/useEnrichedLendingObligations.ts @@ -55,10 +55,10 @@ export function useEnrichedLendingObligations() { .map(obligation => ({ obligation, reserve: availableReserves.get( - obligation.info.borrows[0].borrowReserve.toBase58(), + obligation.info.borrows?.[0]?.borrowReserve.toBase58() || '', ) as ParsedAccount, depositReserve: availableReserves.get( - obligation.info.deposits[0].depositReserve.toBase58(), + obligation.info.deposits?.[0]?.depositReserve.toBase58() || '', ) as ParsedAccount, })) // use obligations with reserves available From fd483919a3a28ef5d9fffb867eed6b9bce010425 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:59:49 -0500 Subject: [PATCH 51/58] obligations view --- .../lending/src/components/Layout/index.tsx | 27 ++++-- packages/lending/src/constants/labels.ts | 2 + packages/lending/src/routes.tsx | 2 + packages/lending/src/views/index.tsx | 1 + .../lending/src/views/obligations/index.tsx | 54 ++++++++++++ .../lending/src/views/obligations/style.less | 84 +++++++++++++++++++ 6 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 packages/lending/src/views/obligations/index.tsx create mode 100644 packages/lending/src/views/obligations/style.less diff --git a/packages/lending/src/components/Layout/index.tsx b/packages/lending/src/components/Layout/index.tsx index 46ea144e..ca2c759b 100644 --- a/packages/lending/src/components/Layout/index.tsx +++ b/packages/lending/src/components/Layout/index.tsx @@ -7,6 +7,7 @@ import { PieChartOutlined, RocketOutlined, ShoppingOutlined, + AuditOutlined, } from '@ant-design/icons'; import BasicLayout from '@ant-design/pro-layout'; @@ -28,10 +29,11 @@ export const AppLayout = React.memo((props: any) => { const paths: { [key: string]: string } = { '/dashboard': '2', '/deposit': '3', - '/borrow': '4', - '/liquidate': '5', - '/margin': '6', - '/faucet': '7', + '/obligations': '4', + '/borrow': '5', + '/liquidate': '6', + '/margin': '7', + '/faucet': '8', }; const current = @@ -96,7 +98,16 @@ export const AppLayout = React.memo((props: any) => { {LABELS.MENU_DEPOSIT} - }> + }> + + {LABELS.MENU_OBLIGATIONS} + + + }> { {LABELS.MENU_BORROW} - }> + }> { {/* Hide margin option for now */} - {/* {}} icon={< LineChartOutlined/>}> + {/* {}} icon={< LineChartOutlined/>}> { */} {env !== 'mainnet-beta' && ( - }> + }> } /> } /> + } /> } /> { + const connection = useConnection(); + const { wallet } = useWallet(); + const { userObligations } = useUserObligations(); + const { userDeposits } = useUserDeposits(); + const [pendingTx, setPendingTx] = useState(false); + const { lendingMarkets } = useLendingMarkets(); + + // @FIXME + const lendingMarket = lendingMarkets[0]?.pubkey; + + const onInit = useCallback(() => { + setPendingTx(true); + + (async () => { + try { + await initObligation(connection, wallet, lendingMarket); + } catch { + // TODO: + } finally { + setPendingTx(false); + } + })(); + }, [ + connection, + wallet, + lendingMarket, + setPendingTx, + ]); + + return
+ + {LABELS.INIT_OBLIGATION_ACTION} + +
; +}; diff --git a/packages/lending/src/views/obligations/style.less b/packages/lending/src/views/obligations/style.less new file mode 100644 index 00000000..a560df14 --- /dev/null +++ b/packages/lending/src/views/obligations/style.less @@ -0,0 +1,84 @@ +@import '~antd/es/style/themes/dark.less'; + +.obligations-item { + display: flex; + justify-content: space-between; + align-items: center; + color: @text-color; + + & > :nth-child(n) { + flex: 15%; + text-align: right; + white-space: nowrap; + margin: 10px 0px; + } + + & > :first-child { + flex: 50px; + } + + & > :last-child { + flex: 200px + } + + border-bottom: 1px solid @border-color-split; +} + +.obligations-title { + display: flex; + + & > :first-child { + margin-left: 0px; + margin-right: auto; + } + + & > :last-child { + span { + font-size: 10px; + color: gray + } + } +} + +.obligations-header { + + & > :nth-child(n) { + flex: 15%; + text-align: right; + } + + & > :first-child { + text-align: left; + flex: 50px; + } + + & > :last-child { + flex: 200px; + } +} + +.obligations-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + flex: 1; + + & > :first-child { + flex: 1; + } +} + +.obligations-info { + display: flex; + flex-direction: column; + align-self: center; + justify-content: center; + flex: 1; + + .obligations-splash { + max-width: 400px; + width: 100%; + align-self: center; + } +} + From 8898bb5bc0046e971af7dd7ffffc6ddeadad1870 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Mon, 28 Jun 2021 20:59:56 -0500 Subject: [PATCH 52/58] fix setter name --- packages/lending/src/hooks/useLendingMarket.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/lending/src/hooks/useLendingMarket.ts b/packages/lending/src/hooks/useLendingMarket.ts index f5b33c13..0042be2f 100644 --- a/packages/lending/src/hooks/useLendingMarket.ts +++ b/packages/lending/src/hooks/useLendingMarket.ts @@ -13,21 +13,21 @@ const getLendingMarkets = () => { }; export function useLendingMarkets() { - const [lendingMarkets, setLendingMarket] = useState< + const [lendingMarkets, setLendingMarkets] = useState< ParsedAccount[] >(getLendingMarkets()); useEffect(() => { const dispose = cache.emitter.onCache(args => { if (args.parser === LendingMarketParser) { - setLendingMarket(getLendingMarkets()); + setLendingMarkets(getLendingMarkets()); } }); return () => { dispose(); }; - }, [setLendingMarket]); + }, [setLendingMarkets]); return { lendingMarkets, From b4255eab7e9bec3ab379faaefb1caf0116dc0a16 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 1 Jul 2021 20:53:53 -0500 Subject: [PATCH 53/58] update deps, add @solana/spl-token-lending --- packages/lending/package.json | 7 ++-- yarn.lock | 75 ++++++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/packages/lending/package.json b/packages/lending/package.json index bce82a33..f5528900 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -10,11 +10,12 @@ "@project-serum/serum": "^0.13.38", "@project-serum/sol-wallet-adapter": "^0.1.4", "@pythnetwork/client": "^2.0.0", - "@solana/spl-token": "0.0.13", - "@solana/spl-token-swap": "0.1.0", + "@solana/spl-token": "^0.1.6", + "@solana/spl-token-lending": "^0.3.3", + "@solana/spl-token-swap": "^0.1.0", "@solana/wallet-base": "0.0.1", "@solana/wallet-ledger": "0.0.1", - "@solana/web3.js": "^1.5.0", + "@solana/web3.js": "^1.20.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/yarn.lock b/yarn.lock index a66c3878..a0d25286 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3082,6 +3082,15 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@solana/spl-token-lending@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@solana/spl-token-lending/-/spl-token-lending-0.3.3.tgz#efc5813d6c45fc51340d530e8c9f498dfa4e75e7" + integrity sha512-CDNiNIu+31jLUO2ZduM4oEDXyOUn1Wwgim3/pyA0aiL7kOcOeatstnJWMUT3bsyy42FiY9GR+PT0eloShV6abg== + dependencies: + bigint-buffer "^1.1.5" + bignumber.js "^9.0.1" + buffer-layout "^1.2.1" + "@solana/spl-token-registry@^0.2.0": version "0.2.51" resolved "https://registry.yarnpkg.com/@solana/spl-token-registry/-/spl-token-registry-0.2.51.tgz#a263c146575f1dbab2d3e2e1f379068a8a63706f" @@ -3089,7 +3098,7 @@ dependencies: cross-fetch "3.0.6" -"@solana/spl-token-swap@0.1.0": +"@solana/spl-token-swap@0.1.0", "@solana/spl-token-swap@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@solana/spl-token-swap/-/spl-token-swap-0.1.0.tgz#a1bc2b0c96edae8b31bb2cc000ebacdc36e131c8" integrity sha512-h0ntp6xwRZBEKDd6oNhJTbPISjIfeGm0eqQqAccTkeluo1zHve4dnUChTKF4MQ+JqXjfqd6z6DJjfa0+dGA37w== @@ -3114,6 +3123,18 @@ dotenv "8.2.0" mkdirp "1.0.4" +"@solana/spl-token@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.1.6.tgz#fa136b0a3db84f07a99bc0e54cf4e91f2d6da2e8" + integrity sha512-fYj+a3w1bqWN6Ibf85XF3h2JkuxevI3Spvqi+mjsNqVUEo2AgxxTZmujNLn/jIzQDNdWkBfF/wYzH5ikcGHmfw== + dependencies: + "@babel/runtime" "^7.10.5" + "@solana/web3.js" "^1.12.0" + bn.js "^5.1.0" + buffer "6.0.3" + buffer-layout "^1.2.0" + dotenv "10.0.0" + "@solana/wallet-base@0.0.1", "@solana/wallet-base@^0.0.1": version "0.0.1" resolved "https://registry.yarnpkg.com/@solana/wallet-base/-/wallet-base-0.0.1.tgz#9b1a898b0d4ca029fe2cf1dad1b00f63448e394c" @@ -3204,6 +3225,26 @@ superstruct "^0.14.2" tweetnacl "^1.0.0" +"@solana/web3.js@^1.12.0", "@solana/web3.js@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.20.0.tgz#9a1855a239c96c5b946bdbe4cc5e3768ee3b2a77" + integrity sha512-s/hmbeC7h0QeMKDHl5HTJbY4NSrT3IK1oGerNwdmFfnbs9ygtBbXo1MXaeYVDcNazsthKgiYE+unNd+cpVH8HA== + dependencies: + "@babel/runtime" "^7.12.5" + bn.js "^5.0.0" + borsh "^0.4.0" + bs58 "^4.0.1" + buffer "6.0.1" + buffer-layout "^1.2.0" + crypto-hash "^1.2.2" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "^2.6.1" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.14.2" + tweetnacl "^1.0.0" + "@solana/web3.js@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.5.0.tgz#4819ecad0408ec55f3d47a227627856002a7358b" @@ -6150,6 +6191,13 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bigint-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== + dependencies: + bindings "^1.3.0" + bignumber.js@^9.0.0, bignumber.js@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" @@ -6170,7 +6218,7 @@ bind-decorator@^1.0.11: resolved "https://registry.yarnpkg.com/bind-decorator/-/bind-decorator-1.0.11.tgz#e41bc06a1f65dd9cec476c91c5daf3978488252f" integrity sha1-5BvAah9l3ZzsR2yRxdrzl4SIJS8= -bindings@^1.2.1, bindings@^1.5.0: +bindings@^1.2.1, bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -6219,7 +6267,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.1.3: +bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.1.3: version "5.2.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== @@ -6521,6 +6569,14 @@ buffer@6.0.1: base64-js "^1.3.1" ieee754 "^1.2.1" +buffer@6.0.3, buffer@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -6538,14 +6594,6 @@ buffer@^5.0.5, buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.1: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.3.tgz#66724b756bed23cd7c28c4d306d7994f9943cc6b" @@ -8829,6 +8877,11 @@ dotenv-expand@5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" From dce860798648932474d80ccaaa9d7dfb3ab428af Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 1 Jul 2021 20:56:04 -0500 Subject: [PATCH 54/58] remove broken views --- .../src/components/BorrowInput/index.tsx | 161 ----------- .../src/components/BorrowInput/style.less | 8 - .../src/components/LiquidateInput/index.tsx | 251 ------------------ .../src/components/LiquidateInput/style.less | 8 - .../src/components/LoanInfoLine/index.tsx | 104 -------- .../src/components/RepayInput/index.tsx | 251 ------------------ .../src/components/RepayInput/style.less | 8 - .../hooks/useEnrichedLendingObligations.ts | 168 ------------ .../lending/src/views/borrowReserve/index.tsx | 89 ------- .../src/views/borrowReserve/style.less | 16 -- .../src/views/dashboard/obligation/index.tsx | 47 ---- .../src/views/dashboard/obligation/item.tsx | 112 -------- .../lending/src/views/liquidate/index.tsx | 119 --------- packages/lending/src/views/liquidate/item.tsx | 102 ------- .../lending/src/views/liquidate/style.less | 48 ---- .../src/views/liquidateReserve/index.tsx | 52 ---- .../src/views/liquidateReserve/style.less | 30 --- .../lending/src/views/repayReserve/index.tsx | 110 -------- .../lending/src/views/repayReserve/style.less | 30 --- 19 files changed, 1714 deletions(-) delete mode 100644 packages/lending/src/components/BorrowInput/index.tsx delete mode 100644 packages/lending/src/components/BorrowInput/style.less delete mode 100644 packages/lending/src/components/LiquidateInput/index.tsx delete mode 100644 packages/lending/src/components/LiquidateInput/style.less delete mode 100644 packages/lending/src/components/LoanInfoLine/index.tsx delete mode 100644 packages/lending/src/components/RepayInput/index.tsx delete mode 100644 packages/lending/src/components/RepayInput/style.less delete mode 100644 packages/lending/src/hooks/useEnrichedLendingObligations.ts delete mode 100644 packages/lending/src/views/borrowReserve/index.tsx delete mode 100644 packages/lending/src/views/borrowReserve/style.less delete mode 100644 packages/lending/src/views/dashboard/obligation/index.tsx delete mode 100644 packages/lending/src/views/dashboard/obligation/item.tsx delete mode 100644 packages/lending/src/views/liquidate/index.tsx delete mode 100644 packages/lending/src/views/liquidate/item.tsx delete mode 100644 packages/lending/src/views/liquidate/style.less delete mode 100644 packages/lending/src/views/liquidateReserve/index.tsx delete mode 100644 packages/lending/src/views/liquidateReserve/style.less delete mode 100644 packages/lending/src/views/repayReserve/index.tsx delete mode 100644 packages/lending/src/views/repayReserve/style.less diff --git a/packages/lending/src/components/BorrowInput/index.tsx b/packages/lending/src/components/BorrowInput/index.tsx deleted file mode 100644 index d2faf47c..00000000 --- a/packages/lending/src/components/BorrowInput/index.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { - ActionConfirmation, - BackButton, - ConnectButton, - contexts, - ParsedAccount, -} from '@oyster/common'; -import { Card } from 'antd'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; - -import { borrowObligationLiquidity } from '../../actions'; -import { LABELS } from '../../constants'; -import { usePrice } from '../../contexts/pyth'; -import { - useSliderInput, - useUserBalance, - useUserDeposits, - useUserObligationByReserve, useUserObligations -} from '../../hooks'; -import { Reserve, ReserveParser } from '../../models'; -import CollateralInput from '../CollateralInput'; -import { RiskSlider } from '../RiskSlider'; -import './style.less'; - -const { useWallet } = contexts.Wallet; -const { useConnection } = contexts.Connection; -const { cache } = contexts.Accounts; - -export const BorrowInput = (props: { - className?: string; - reserve: ParsedAccount; -}) => { - const connection = useConnection(); - const { wallet } = useWallet(); - const { userDeposits } = useUserDeposits(); - const { userObligations } = useUserObligations(); - - const [pendingTx, setPendingTx] = useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); - - const borrowReserve = props.reserve; - - const borrowPrice = usePrice( - borrowReserve.info.liquidity.mintPubkey.toBase58(), - ); - - const tokenBalance = userDeposits[0]?.info.amount || 0; - - const convert = useCallback( - (val: string | number) => { - const minAmount = Math.min(tokenBalance, Infinity); - if (typeof val === 'string') { - return (parseFloat(val) / minAmount) * 100; - } else { - return (val * minAmount) / 100; - } - }, - [tokenBalance], - ); - - const { value, setValue, pct } = useSliderInput(convert); - - const { userObligationsByReserve } = useUserObligationByReserve( - borrowReserve?.pubkey - ); - const { accounts: sourceAccounts } = useUserBalance( - // @FIXME - // depositReserve?.info.collateral.mintPubkey, - ); - const onBorrow = useCallback(() => { - setPendingTx(true); - - (async () => { - try { - await borrowObligationLiquidity( - connection, - wallet, - parseFloat(value), - borrowReserve, - // TODO: select existing obligations by collateral reserve - userObligationsByReserve[0].obligation.account - ); - - setValue(''); - setShowConfirmation(true); - } catch { - // TODO: - } finally { - setPendingTx(false); - } - })(); - }, [ - connection, - wallet, - value, - setValue, - borrowReserve, - userObligationsByReserve, - setPendingTx, - setShowConfirmation, - ]); - - const bodyStyle: React.CSSProperties = { - display: 'flex', - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: '100%', - }; - - return ( - - {showConfirmation ? ( - setShowConfirmation(false)} /> - ) : ( -
-
{LABELS.BORROW_QUESTION}
- -
- { - setValue(val?.toString() || ''); - }} - disabled={true} - hideBalance={true} - /> -
- - {sourceAccounts.length === 0 - ? LABELS.NO_COLLATERAL - : LABELS.BORROW_ACTION} - - -
- )} -
- ); -}; diff --git a/packages/lending/src/components/BorrowInput/style.less b/packages/lending/src/components/BorrowInput/style.less deleted file mode 100644 index 43b1f23e..00000000 --- a/packages/lending/src/components/BorrowInput/style.less +++ /dev/null @@ -1,8 +0,0 @@ -@import "~antd/dist/antd.dark.less"; - -.borrow-input-title { - font-size: 1.05rem; - margin-top: 15px; - margin-bottom: 15px; - color: @text-color-secondary; -} \ No newline at end of file diff --git a/packages/lending/src/components/LiquidateInput/index.tsx b/packages/lending/src/components/LiquidateInput/index.tsx deleted file mode 100644 index 793ec804..00000000 --- a/packages/lending/src/components/LiquidateInput/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { - ActionConfirmation, - ConnectButton, - contexts, - fromLamports, - notify, - ParsedAccount, - wadToLamports, -} from '@oyster/common'; -import { Slider } from 'antd'; -import Card from 'antd/lib/card'; -import React, { useCallback, useEffect, useState } from 'react'; -import { liquidateObligation } from '../../actions'; -import { LABELS, marks } from '../../constants'; -import { usePrice } from '../../contexts/pyth'; -import { - EnrichedLendingObligation, - InputType, - useSliderInput, - useUserBalance, -} from '../../hooks'; -import { Reserve } from '../../models'; -import CollateralInput from '../CollateralInput'; -import './style.less'; - -const { useConnection } = contexts.Connection; -const { useWallet } = contexts.Wallet; -const { useMint } = contexts.Accounts; - -export const LiquidateInput = (props: { - className?: string; - repayReserve: ParsedAccount; - withdrawReserve: ParsedAccount; - obligation: EnrichedLendingObligation; -}) => { - const connection = useConnection(); - const { wallet } = useWallet(); - const { repayReserve, withdrawReserve, obligation } = props; - const [lastTyped, setLastTyped] = useState('liquidate'); - const [pendingTx, setPendingTx] = useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); - const [collateralValue, setCollateralValue] = useState(''); - - const liquidityMint = useMint(repayReserve.info.liquidity.mintPubkey); - const { accounts: sourceAccounts, balance: tokenBalance } = useUserBalance( - repayReserve?.info.liquidity.mintPubkey, - ); - const borrowAmountLamports = wadToLamports( - obligation.info.borrows[0].borrowedAmountWads, - ).toNumber(); - - const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint); - - const convert = useCallback( - (val: string | number) => { - const minAmount = Math.min(tokenBalance || Infinity, borrowAmount); - setLastTyped('liquidate'); - if (typeof val === 'string') { - return (parseFloat(val) / minAmount) * 100; - } else { - return (val * minAmount) / 100; - } - }, - [borrowAmount, tokenBalance], - ); - - const { value, setValue, pct, setPct, type } = useSliderInput(convert); - - const onLiquidate = useCallback(() => { - if (!withdrawReserve) { - return; - } - - setPendingTx(true); - - (async () => { - try { - // @TODO: handle 100% -> u64::MAX - const toLiquidateLamports = - type === InputType.Percent && tokenBalance >= borrowAmount - ? (pct * borrowAmountLamports) / 100 - : Math.ceil( - borrowAmountLamports * (parseFloat(value) / borrowAmount), - ); - await liquidateObligation( - connection, - wallet, - // TODO: ensure user has available amount - toLiquidateLamports, - sourceAccounts[0], - repayReserve, - withdrawReserve, - obligation.account, - ); - - setValue(''); - setCollateralValue(''); - setShowConfirmation(true); - } catch (error) { - // TODO: - notify({ - message: 'Unable to liquidate loan.', - type: 'error', - description: error.message, - }); - } finally { - setPendingTx(false); - } - })(); - }, [ - withdrawReserve, - sourceAccounts, - obligation, - repayReserve, - wallet, - connection, - value, - setValue, - borrowAmount, - borrowAmountLamports, - pct, - tokenBalance, - type, - ]); - - const collateralPrice = usePrice( - withdrawReserve?.info.liquidity.mintPubkey.toBase58(), - ); - - useEffect(() => { - if (withdrawReserve && lastTyped === 'liquidate') { - const collateralInQuote = obligation.info.collateralInQuote; - const collateral = collateralInQuote / collateralPrice; - if (value) { - const borrowRatio = (parseFloat(value) / borrowAmount) * 100; - const collateralAmount = (borrowRatio * collateral) / 100; - setCollateralValue(collateralAmount.toString()); - } else { - setCollateralValue(''); - } - } - }, [ - borrowAmount, - collateralPrice, - withdrawReserve, - lastTyped, - obligation.info.collateralInQuote, - value, - ]); - - useEffect(() => { - if (withdrawReserve && lastTyped === 'collateral') { - const collateralInQuote = obligation.info.collateralInQuote; - const collateral = collateralInQuote / collateralPrice; - if (collateralValue) { - const collateralRatio = - (parseFloat(collateralValue) / collateral) * 100; - const borrowValue = (collateralRatio * borrowAmount) / 100; - setValue(borrowValue.toString()); - } else { - setValue(''); - } - } - }, [ - borrowAmount, - collateralPrice, - withdrawReserve, - collateralValue, - lastTyped, - obligation.info.collateralInQuote, - setValue, - ]); - - if (!withdrawReserve) return null; - const bodyStyle: React.CSSProperties = { - display: 'flex', - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: '100%', - }; - - return ( - - {showConfirmation ? ( - setShowConfirmation(false)} /> - ) : ( -
-
{LABELS.LIQUIDATE_QUESTION}
-
- { - setValue(val?.toString() || ''); - setLastTyped('liquidate'); - }} - disabled={true} - useWalletBalance={true} - /> -
- -
- { - setCollateralValue(val?.toString() || ''); - setLastTyped('collateral'); - }} - disabled={true} - hideBalance={true} - /> -
- - {LABELS.LIQUIDATE_ACTION} - -
- )} -
- ); -}; diff --git a/packages/lending/src/components/LiquidateInput/style.less b/packages/lending/src/components/LiquidateInput/style.less deleted file mode 100644 index a1ae361f..00000000 --- a/packages/lending/src/components/LiquidateInput/style.less +++ /dev/null @@ -1,8 +0,0 @@ -@import "~antd/dist/antd.dark.less"; - -.liquidate-input-title { - font-size: 1.05rem; - margin-top: 15px; - margin-bottom: 15px; - color: @text-color-secondary; -} \ No newline at end of file diff --git a/packages/lending/src/components/LoanInfoLine/index.tsx b/packages/lending/src/components/LoanInfoLine/index.tsx deleted file mode 100644 index ba476c81..00000000 --- a/packages/lending/src/components/LoanInfoLine/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { - contexts, - formatNumber, - formatPct, - fromLamports, - useTokenName, - wadToLamports, -} from '@oyster/common'; -import { Card, Col, Row, Statistic } from 'antd'; -import React, { useMemo } from 'react'; -import { GUTTER } from '../../constants'; -import { EnrichedLendingObligation, useLendingReserve } from '../../hooks'; -import { calculateBorrowAPY, collateralToLiquidity } from '../../models'; - -const { useMint } = contexts.Accounts; -export const LoanInfoLine = (props: { - className?: string; - obligation: EnrichedLendingObligation; -}) => { - const obligation = props.obligation; - - const repayReserve = useLendingReserve( - obligation?.info.borrows[0].borrowReserve, - ); - const withdrawReserve = useLendingReserve( - obligation?.info.deposits[0].depositReserve, - ); - - const liquidityMint = useMint(repayReserve?.info.liquidity.mintPubkey); - const collateralMint = useMint(withdrawReserve?.info.liquidity.mintPubkey); - const repayName = useTokenName(repayReserve?.info.liquidity.mintPubkey); - const withdrawName = useTokenName(withdrawReserve?.info.liquidity.mintPubkey); - - const borrowAPY = useMemo( - () => (repayReserve ? calculateBorrowAPY(repayReserve?.info) : 0), - [repayReserve], - ); - if (!obligation || !repayReserve) { - return null; - } - const borrowAmount = fromLamports( - wadToLamports(obligation?.info.borrows[0].borrowedAmountWads), - liquidityMint, - ); - const collateralLamports = collateralToLiquidity( - obligation?.info.deposits[0].depositedAmount, - repayReserve.info, - ); - const collateral = fromLamports(collateralLamports, collateralMint); - - return ( - - - - ( -
-
- {formatNumber.format(borrowAmount)} {repayName} -
-
- ${formatNumber.format(parseFloat(val.toString()))} -
-
- )} - /> -
- - - - ( -
-
- {formatNumber.format(collateral)} {withdrawName} -
-
- ${formatNumber.format(parseFloat(val.toString()))} -
-
- )} - /> -
- - - - - - - - - - - -
- ); -}; diff --git a/packages/lending/src/components/RepayInput/index.tsx b/packages/lending/src/components/RepayInput/index.tsx deleted file mode 100644 index ee6a6ddd..00000000 --- a/packages/lending/src/components/RepayInput/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { - ActionConfirmation, - ConnectButton, - contexts, - fromLamports, - notify, - ParsedAccount, - wadToLamports, -} from '@oyster/common'; -import { Card, Slider } from 'antd'; -import React, { useCallback, useEffect, useState } from 'react'; -import { repayObligationLiquidity } from '../../actions'; -import { LABELS, marks } from '../../constants'; -import { usePrice } from '../../contexts/pyth'; -import { - EnrichedLendingObligation, - InputType, - useSliderInput, - useUserBalance, -} from '../../hooks'; -import { Reserve } from '../../models'; -import CollateralInput from '../CollateralInput'; -import './style.less'; - -const { useWallet } = contexts.Wallet; -const { useConnection } = contexts.Connection; -const { useMint } = contexts.Accounts; - -export const RepayInput = (props: { - className?: string; - borrowReserve: ParsedAccount; - depositReserve: ParsedAccount; - obligation: EnrichedLendingObligation; -}) => { - const connection = useConnection(); - const { wallet } = useWallet(); - const [lastTyped, setLastTyped] = useState('repay'); - const [pendingTx, setPendingTx] = useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); - const [collateralValue, setCollateralValue] = useState(''); - - const repayReserve = props.borrowReserve; - const obligation = props.obligation; - - const liquidityMint = useMint(repayReserve.info.liquidity.mintPubkey); - const { balance: tokenBalance } = useUserBalance( - repayReserve.info.liquidity.mintPubkey, - ); - - const borrowAmountLamports = wadToLamports( - obligation.info.borrows[0].borrowedAmountWads, - ).toNumber(); - const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint); - const depositReserve = props.depositReserve; - - const { accounts: sourceAccounts } = useUserBalance( - repayReserve.info.liquidity.mintPubkey, - ); - - const convert = useCallback( - (val: string | number) => { - const minAmount = Math.min(tokenBalance || Infinity, borrowAmount); - setLastTyped('repay'); - if (typeof val === 'string') { - return (parseFloat(val) / minAmount) * 100; - } else { - return (val * minAmount) / 100; - } - }, - [borrowAmount, tokenBalance], - ); - - const { value, setValue, pct, setPct, type } = useSliderInput(convert); - - const onRepay = useCallback(() => { - if (!depositReserve || !obligation || !repayReserve) { - return; - } - - setPendingTx(true); - - (async () => { - try { - const toRepayLamports = - type === InputType.Percent - ? (pct * borrowAmountLamports) / 100 - : Math.ceil( - borrowAmountLamports * (parseFloat(value) / borrowAmount), - ); - await repayObligationLiquidity( - connection, - wallet, - toRepayLamports, - sourceAccounts[0], - repayReserve, - obligation.account, - ); - - setValue(''); - setCollateralValue(''); - setShowConfirmation(true); - } catch (error) { - notify({ - message: 'Unable to repay loan.', - type: 'error', - description: error.message, - }); - } finally { - setPendingTx(false); - } - })(); - }, [ - borrowAmount, - borrowAmountLamports, - depositReserve, - connection, - sourceAccounts, - obligation, - pct, - repayReserve, - setValue, - type, - value, - wallet, - ]); - - const collateralPrice = usePrice( - depositReserve?.info.liquidity.mintPubkey.toBase58(), - ); - - useEffect(() => { - if (depositReserve && lastTyped === 'repay') { - const collateralInQuote = obligation.info.collateralInQuote; - const collateral = collateralInQuote / collateralPrice; - if (value) { - const borrowRatio = (parseFloat(value) / borrowAmount) * 100; - const collateralAmount = (borrowRatio * collateral) / 100; - setCollateralValue(collateralAmount.toString()); - } else { - setCollateralValue(''); - } - } - }, [ - borrowAmount, - collateralPrice, - depositReserve, - lastTyped, - obligation.info.collateralInQuote, - value, - ]); - - useEffect(() => { - if (depositReserve && lastTyped === 'collateral') { - const collateralInQuote = obligation.info.collateralInQuote; - const collateral = collateralInQuote / collateralPrice; - if (collateralValue) { - const collateralRatio = - (parseFloat(collateralValue) / collateral) * 100; - const borrowValue = (collateralRatio * borrowAmount) / 100; - setValue(borrowValue.toString()); - } else { - setValue(''); - } - } - }, [ - borrowAmount, - collateralPrice, - depositReserve, - collateralValue, - lastTyped, - obligation.info.collateralInQuote, - setValue, - ]); - - const bodyStyle: React.CSSProperties = { - display: 'flex', - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: '100%', - }; - - return ( - - {showConfirmation ? ( - setShowConfirmation(false)} /> - ) : ( -
-
{LABELS.REPAY_QUESTION}
-
- { - setValue(val?.toString() || ''); - setLastTyped('repay'); - }} - disabled={true} - useWalletBalance={true} - /> -
- -
- { - setCollateralValue(val?.toString() || ''); - setLastTyped('collateral'); - }} - disabled={true} - hideBalance={true} - /> -
- - {LABELS.REPAY_ACTION} - -
- )} -
- ); -}; diff --git a/packages/lending/src/components/RepayInput/style.less b/packages/lending/src/components/RepayInput/style.less deleted file mode 100644 index cdb0b399..00000000 --- a/packages/lending/src/components/RepayInput/style.less +++ /dev/null @@ -1,8 +0,0 @@ -@import "~antd/dist/antd.dark.less"; - -.repay-input-title { - font-size: 1.05rem; - margin-top: 15px; - margin-bottom: 15px; - color: @text-color-secondary; -} \ No newline at end of file diff --git a/packages/lending/src/hooks/useEnrichedLendingObligations.ts b/packages/lending/src/hooks/useEnrichedLendingObligations.ts deleted file mode 100644 index 247b0e75..00000000 --- a/packages/lending/src/hooks/useEnrichedLendingObligations.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - contexts, - fromLamports, - getTokenName, - ParsedAccount, - wadToLamports, -} from '@oyster/common'; -import { MintInfo } from '@solana/spl-token'; -import { PublicKey } from '@solana/web3.js'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { usePyth } from '../contexts/pyth'; -import { collateralToLiquidity, Obligation, Reserve } from '../models'; -import { useLendingObligations } from './useLendingObligations'; -import { useLendingReserves } from './useLendingReserves'; - -const { cache } = contexts.Accounts; -const { useConnectionConfig } = contexts.Connection; - -interface EnrichedLendingObligationInfo extends Obligation { - ltv: number; - health: number; - borrowedInQuote: number; - collateralInQuote: number; - liquidationThreshold: number; - repayName: string; - collateralName: string; -} - -// @TODO: rework -export interface EnrichedLendingObligation { - account: ParsedAccount; - info: EnrichedLendingObligationInfo; -} - -export function useEnrichedLendingObligations() { - const { obligations } = useLendingObligations(); - const { reserveAccounts } = useLendingReserves(); - const { tokenMap } = useConnectionConfig(); - const { getPrice } = usePyth(); - - const availableReserves = useMemo(() => { - return reserveAccounts.reduce((map, reserve) => { - map.set(reserve.pubkey.toBase58(), reserve); - return map; - }, new Map>()); - }, [reserveAccounts]); - - const enrichedFactory = useCallback(() => { - if (availableReserves.size === 0) { - return []; - } - - return ( - obligations - .map(obligation => ({ - obligation, - reserve: availableReserves.get( - obligation.info.borrows?.[0]?.borrowReserve.toBase58() || '', - ) as ParsedAccount, - depositReserve: availableReserves.get( - obligation.info.deposits?.[0]?.depositReserve.toBase58() || '', - ) as ParsedAccount, - })) - // use obligations with reserves available - .filter(item => item.reserve) - // use reserves with borrow amount greater than zero - .filter( - item => - wadToLamports( - item.obligation.info.borrows[0].borrowedAmountWads, - ).toNumber() > 0, - ) - .map(item => { - const obligation = item.obligation; - const reserve = item.reserve.info; - const depositReserve = item.reserve.info; - const liquidityMint = cache.get( - reserve.liquidity.mintPubkey, - ) as ParsedAccount; - let ltv = 0; - let health = 0; - let borrowedInQuote = 0; - let collateralInQuote = 0; - - if (liquidityMint) { - const collateralMint = cache.get( - item.depositReserve.info.liquidity.mintPubkey, - ); - - const collateral = fromLamports( - collateralToLiquidity( - obligation.info.deposits[0].depositedAmount, - item.reserve.info, - ), - collateralMint?.info, - ); - - // @FIXME: support multiple borrows - const borrowed = wadToLamports( - obligation.info.borrows[0].borrowedAmountWads, - ).toNumber(); - - // @FIXME: remove dex market - const borrowedAmount = borrowed; - - const liquidityMintAddress = item.reserve.info.liquidity.mintPubkey.toBase58(); - const liquidityMint = cache.get( - liquidityMintAddress, - ) as ParsedAccount; - borrowedInQuote = - fromLamports(borrowed, liquidityMint.info) * - getPrice(liquidityMintAddress); - // @FIXME: collateral can't be priced by pyth - collateralInQuote = - collateral * getPrice(collateralMint?.pubkey.toBase58() || ''); - - ltv = (100 * borrowedAmount) / collateral; - - const liquidationThreshold = - item.reserve.info.config.liquidationThreshold; - health = (collateral * liquidationThreshold) / 100 / borrowedAmount; - } - - return { - account: obligation, - info: { - ...obligation.info, - ltv, - health, - borrowedInQuote, - collateralInQuote, - liquidationThreshold: - item.reserve.info.config.liquidationThreshold, - repayName: getTokenName(tokenMap, reserve.liquidity.mintPubkey), - collateralName: getTokenName( - tokenMap, - depositReserve.liquidity.mintPubkey, - ), - }, - } as EnrichedLendingObligation; - }) - .sort((a, b) => a.info.health - b.info.health) - ); - }, [obligations, availableReserves, getPrice, tokenMap]); - - const [enriched, setEnriched] = useState( - enrichedFactory(), - ); - - useEffect(() => { - setEnriched(enrichedFactory()); - }, [enrichedFactory, setEnriched]); - - return { - obligations: enriched, - }; -} - -export function useEnrichedLendingObligation(address?: string | PublicKey) { - const id = typeof address === 'string' ? address : address?.toBase58(); - const { obligations } = useEnrichedLendingObligations(); - - const obligation = useMemo(() => { - return obligations.find(ob => ob.account.pubkey.toBase58() === id); - }, [obligations, id]); - - return obligation; -} diff --git a/packages/lending/src/views/borrowReserve/index.tsx b/packages/lending/src/views/borrowReserve/index.tsx deleted file mode 100644 index bac5be95..00000000 --- a/packages/lending/src/views/borrowReserve/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Card, Col, Row, Statistic } from 'antd'; -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { BarChartStatistic } from '../../components/BarChartStatistic'; - -import { BorrowInput } from '../../components/BorrowInput'; -import { - SideReserveOverview, - SideReserveOverviewMode, -} from '../../components/SideReserveOverview'; -import { GUTTER, LABELS } from '../../constants'; -import { - useBorrowingPower, - useLendingReserve, - useUserObligations, -} from '../../hooks'; -import './style.less'; - -export const BorrowReserveView = () => { - const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); - const { userObligations, totalInQuote: loansValue } = useUserObligations(); - - const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id); - - if (!lendingReserve) { - return null; - } - - return ( -
- - - - - - - - - - - - - - - - - - - item.obligation.info.borrowedInQuote / loansValue} - name={item => item.obligation.info.repayName} - /> - - - - - - - - - - - -
- ); -}; diff --git a/packages/lending/src/views/borrowReserve/style.less b/packages/lending/src/views/borrowReserve/style.less deleted file mode 100644 index 9ac1cb22..00000000 --- a/packages/lending/src/views/borrowReserve/style.less +++ /dev/null @@ -1,16 +0,0 @@ -.borrow-reserve { - display: flex; - flex-direction: column; - flex: 1; - overflow-x: hidden; -} - -.borrow-reserve-item { - height: 100%; -} - -.borrow-reserve-container { - display: flex; - flex-wrap: wrap; - flex: 1; -} diff --git a/packages/lending/src/views/dashboard/obligation/index.tsx b/packages/lending/src/views/dashboard/obligation/index.tsx deleted file mode 100644 index 519fefb2..00000000 --- a/packages/lending/src/views/dashboard/obligation/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { utils } from '@oyster/common'; -import { Card } from 'antd'; -import React from 'react'; -import { BarChartStatistic } from '../../../components/BarChartStatistic'; -import { LABELS } from '../../../constants'; -import { useUserObligations } from './../../../hooks'; -import { ObligationItem } from './item'; - -export const DashboardObligations = () => { - const { userObligations, totalInQuote } = useUserObligations(); - - return ( - -
{LABELS.DASHBOARD_TITLE_LOANS}
-
- {LABELS.TOTAL_TITLE}: $ - {utils.formatNumber.format(totalInQuote)} -
-
- } - > - item.obligation.info.borrowedInQuote / totalInQuote} - name={item => item.obligation.info.repayName} - /> -
-
{LABELS.TABLE_TITLE_ASSET}
-
{LABELS.TABLE_TITLE_YOUR_LOAN_BALANCE}
-
{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}
-
{LABELS.TABLE_TITLE_APY}
-
{LABELS.TABLE_TITLE_LTV}
-
-
- {userObligations.map(item => { - return ( - - ); - })} - - ); -}; diff --git a/packages/lending/src/views/dashboard/obligation/item.tsx b/packages/lending/src/views/dashboard/obligation/item.tsx deleted file mode 100644 index cd69c2d4..00000000 --- a/packages/lending/src/views/dashboard/obligation/item.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { - contexts, - formatNumber, - formatPct, - fromLamports, - ParsedAccount, - TokenIcon, - useTokenName, - wadToLamports, -} from '@oyster/common'; -import { Button } from 'antd'; -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import { EnrichedLendingObligation } from '../../../hooks'; -import { - calculateBorrowAPY, - collateralToLiquidity, - healthFactorToRiskColor, - Reserve, -} from '../../../models'; - -const { cache, useMint } = contexts.Accounts; - -export const ObligationItem = (props: { - obligation: EnrichedLendingObligation; -}) => { - const { obligation } = props; - - const borrowReserve = cache.get( - obligation.info.borrows[0].borrowReserve, - ) as ParsedAccount; - - const depositReserve = cache.get( - obligation.info.deposits[0].depositReserve, - ) as ParsedAccount; - - const liquidityMint = useMint(borrowReserve.info.liquidity.mintPubkey); - const collateralMint = useMint(depositReserve.info.liquidity.mintPubkey); - - const borrowAmount = fromLamports( - wadToLamports(obligation.info.borrows[0].borrowedAmountWads), - liquidityMint, - ); - - const borrowAPY = useMemo(() => calculateBorrowAPY(borrowReserve.info), [ - borrowReserve, - ]); - - const collateralLamports = collateralToLiquidity( - obligation.info.deposits[0].depositedAmount, - borrowReserve.info, - ); - const collateral = fromLamports(collateralLamports, collateralMint); - - const borrowName = useTokenName(borrowReserve?.info.liquidity.mintPubkey); - const collateralName = useTokenName( - depositReserve?.info.liquidity.mintPubkey, - ); - - return ( -
- -
- - -
-
-
-
-
- {formatNumber.format(borrowAmount)} {borrowName} -
-
- ${formatNumber.format(obligation.info.borrowedInQuote)} -
-
-
-
-
-
- {formatNumber.format(collateral)} {collateralName} -
-
- ${formatNumber.format(obligation.info.collateralInQuote)} -
-
-
-
{formatPct.format(borrowAPY)}
-
- {formatPct.format(obligation.info.ltv / 100)} -
-
- - - - - - -
-
- ); -}; diff --git a/packages/lending/src/views/liquidate/index.tsx b/packages/lending/src/views/liquidate/index.tsx deleted file mode 100644 index a8b80d83..00000000 --- a/packages/lending/src/views/liquidate/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { Card, Col, Row, Statistic, Typography } from 'antd'; -import React, { useMemo } from 'react'; -import { BarChartStatistic } from '../../components/BarChartStatistic'; -import { GUTTER, LABELS } from '../../constants'; -import { useEnrichedLendingObligations } from '../../hooks'; -import { LiquidateItem } from './item'; -import './style.less'; - -export const LiquidateView = () => { - const { obligations } = useEnrichedLendingObligations(); - - const atRisk = useMemo( - () => obligations.filter(item => item.info.health < 1.0), - [obligations], - ); - - const valueAtRisk = useMemo( - () => atRisk.reduce((acc, item) => acc + item.info.collateralInQuote, 0), - [atRisk], - ); - const loansAtRiskCount = useMemo(() => atRisk.length, [atRisk]); - const pctAtRisk = useMemo(() => atRisk.length / obligations.length, [ - atRisk, - obligations, - ]); - - const groupedLoans = useMemo(() => { - return atRisk.reduce((acc, item) => { - acc.set( - item.info.repayName, - (acc.get(item.info.collateralName) || 0) + item.info.collateralInQuote, - ); - return acc; - }, new Map()); - }, [atRisk]); - - const keys = useMemo(() => [...groupedLoans.keys()], [groupedLoans]); - - return ( -
- {atRisk.length === 0 ? ( -
{LABELS.LIQUIDATE_NO_LOANS}
- ) : ( -
- - - - {LABELS.LIQUIDATION_INFO} - - - - - - - - - - - - - - - - - - - - - - item} - getPct={item => (groupedLoans.get(item) || 0) / valueAtRisk} - items={keys} - /> - - - - - - -
-
{LABELS.TABLE_TITLE_ASSET}
-
{LABELS.TABLE_TITLE_LOAN_BALANCE}
-
{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}
-
{LABELS.TABLE_TITLE_APY}
-
{LABELS.TABLE_TITLE_LTV}
-
{LABELS.TABLE_TITLE_HEALTH}
-
{LABELS.TABLE_TITLE_ACTION}
-
- {atRisk.map(item => ( - - ))} -
- -
-
- )} -
- ); -}; diff --git a/packages/lending/src/views/liquidate/item.tsx b/packages/lending/src/views/liquidate/item.tsx deleted file mode 100644 index 6c895958..00000000 --- a/packages/lending/src/views/liquidate/item.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { - contexts, - formatNumber, - formatPct, - fromLamports, - ParsedAccount, - TokenIcon, - useTokenName, - wadToLamports, -} from '@oyster/common'; -import { Button } from 'antd'; -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import { LABELS } from '../../constants'; -import { EnrichedLendingObligation } from '../../hooks'; -import { - calculateBorrowAPY, - collateralToLiquidity, - Reserve, -} from '../../models'; - -const { useMint, cache } = contexts.Accounts; - -export const LiquidateItem = (props: { item: EnrichedLendingObligation }) => { - let obligation = props.item.info; - - const borrowReserve = cache.get( - obligation.borrows[0].borrowReserve, - ) as ParsedAccount; - - const depositReserve = cache.get( - obligation.deposits[0].depositReserve, - ) as ParsedAccount; - - const liquidityMint = useMint(borrowReserve.info.liquidity.mintPubkey); - const collateralMint = useMint(depositReserve.info.liquidity.mintPubkey); - - const borrowAmount = fromLamports( - wadToLamports(obligation.borrows[0].borrowedAmountWads), - liquidityMint, - ); - - const borrowAPY = useMemo(() => calculateBorrowAPY(borrowReserve.info), [ - borrowReserve, - ]); - - const collateralLamports = collateralToLiquidity( - obligation.deposits[0].depositedAmount, - borrowReserve.info, - ); - const collateral = fromLamports(collateralLamports, collateralMint); - - const borrowName = useTokenName(borrowReserve?.info.liquidity.mintPubkey); - const collateralName = useTokenName( - depositReserve?.info.liquidity.mintPubkey, - ); - - return ( - -
- -
- - -
- {collateralName}→{borrowName} -
-
-
-
- {formatNumber.format(borrowAmount)} {borrowName} -
-
- ${formatNumber.format(obligation.borrowedInQuote)} -
-
-
-
-
-
- {formatNumber.format(collateral)} {collateralName} -
-
- ${formatNumber.format(obligation.collateralInQuote)} -
-
-
-
{formatPct.format(borrowAPY)}
-
{formatPct.format(obligation.ltv / 100)}
-
{obligation.health.toFixed(2)}
-
- -
-
- - ); -}; diff --git a/packages/lending/src/views/liquidate/style.less b/packages/lending/src/views/liquidate/style.less deleted file mode 100644 index 75c3a167..00000000 --- a/packages/lending/src/views/liquidate/style.less +++ /dev/null @@ -1,48 +0,0 @@ -@import '~antd/es/style/themes/dark.less'; - -.liquidate-item { - display: flex; - justify-content: space-between; - align-items: center; - color: @text-color; - - & > :nth-child(n) { - flex: 20%; - text-align: right; - margin: 10px 0px; - } - - & > :first-child { - flex: 80px - } - - border-bottom: 1px solid @border-color-split; -} - -.liquidate-header { - - & > div { - flex: 20%; - text-align: right; - } - - & > :first-child { - text-align: left; - flex: 160px - } -} - -.liquidate-info { - display: flex; - align-self: center; - justify-content: center; - flex: 1; -} - -.liquidate-container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - flex: 1; - overflow-x: hidden; -} \ No newline at end of file diff --git a/packages/lending/src/views/liquidateReserve/index.tsx b/packages/lending/src/views/liquidateReserve/index.tsx deleted file mode 100644 index 43f81504..00000000 --- a/packages/lending/src/views/liquidateReserve/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Col, Row } from 'antd'; -import React from 'react'; -import { useParams } from 'react-router-dom'; - -import { LiquidateInput } from '../../components/LiquidateInput'; - -import { LoanInfoLine } from '../../components/LoanInfoLine'; -import { - SideReserveOverview, - SideReserveOverviewMode, -} from '../../components/SideReserveOverview'; -import { GUTTER } from '../../constants'; -import { useEnrichedLendingObligation, useLendingReserve } from '../../hooks'; - -import './style.less'; - -export const LiquidateReserveView = () => { - const { id } = useParams<{ id: string }>(); - - const obligation = useEnrichedLendingObligation(id); - - // @FIXME: handle multiple deposits / borrows - const repayReserve = useLendingReserve(obligation?.info.borrows[0].borrowReserve); - const withdrawReserve = useLendingReserve(obligation?.info.deposits[0].depositReserve); - - if (!obligation || !repayReserve || !withdrawReserve) { - return null; - } - - return ( -
- - - - - - - - - -
- ); -}; diff --git a/packages/lending/src/views/liquidateReserve/style.less b/packages/lending/src/views/liquidateReserve/style.less deleted file mode 100644 index d59fae42..00000000 --- a/packages/lending/src/views/liquidateReserve/style.less +++ /dev/null @@ -1,30 +0,0 @@ -.liquidate-reserve { - display: flex; - flex-direction: column; - flex: 1; -} - -.liquidate-reserve-item { - margin: 4px; -} - -.liquidate-reserve-container { - display: flex; - flex-wrap: wrap; - flex: 1; -} - -.liquidate-reserve-item-left { - flex: 60%; -} - -.liquidate-reserve-item-right { - flex: 30%; -} - -/* Responsive layout - makes a one column layout instead of a two-column layout */ -@media (max-width: 600px) { - .liquidate-reserve-item-right, .liquidate-reserve-item-left { - flex: 100%; - } -} \ No newline at end of file diff --git a/packages/lending/src/views/repayReserve/index.tsx b/packages/lending/src/views/repayReserve/index.tsx deleted file mode 100644 index 90bcfcd4..00000000 --- a/packages/lending/src/views/repayReserve/index.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Card, Col, Row, Statistic } from 'antd'; -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { BarChartStatistic } from '../../components/BarChartStatistic'; - -import { RepayInput } from '../../components/RepayInput'; -import { - SideReserveOverview, - SideReserveOverviewMode, -} from '../../components/SideReserveOverview'; -import { GUTTER, LABELS } from '../../constants'; -import { - useBorrowingPower, - useEnrichedLendingObligation, - useLendingReserve, - useUserObligations, -} from '../../hooks'; - -import './style.less'; - -export const RepayReserveView = () => { - const { reserve: reserveId, obligation: obligationId } = useParams<{ - reserve?: string; - obligation?: string; - }>(); - - const lendingObligation = useEnrichedLendingObligation(obligationId); - // @FIXME: obligation can have no borrows - const id = obligationId ? lendingObligation?.info.borrows[0].borrowReserve : reserveId; - const lendingReserve = useLendingReserve(id); - - // @FIXME: obligation can have no deposits - const repayReserve = useLendingReserve( - obligationId ? lendingObligation?.info.deposits[0].depositReserve : reserveId, - ); - - const { userObligations, totalInQuote: loansValue } = useUserObligations(); - const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id); - - const reserve = lendingReserve?.info; - - if (!reserve || !lendingReserve || !lendingObligation || !repayReserve) { - return null; - } - - return ( -
- - - - - - - - - - - - - - - - - - - item.obligation.info.borrowedInQuote / loansValue} - name={item => item.obligation.info.repayName} - /> - - - - - - - - - - - -
- ); -}; diff --git a/packages/lending/src/views/repayReserve/style.less b/packages/lending/src/views/repayReserve/style.less deleted file mode 100644 index 79cb0196..00000000 --- a/packages/lending/src/views/repayReserve/style.less +++ /dev/null @@ -1,30 +0,0 @@ -.repay-reserve { - display: flex; - flex-direction: column; - flex: 1; -} - -.repay-reserve-item { - margin: 4px; -} - -.repay-reserve-container { - display: flex; - flex-wrap: wrap; - flex: 1; -} - -.repay-reserve-item-left { - flex: 60%; -} - -.repay-reserve-item-right { - flex: 30%; -} - -/* Responsive layout - makes a one column layout instead of a two-column layout */ -@media (max-width: 600px) { - .repay-reserve-item-right, .repay-reserve-item-left { - flex: 100%; - } -} \ No newline at end of file From 682c1ed9678ed0b00b92cf5e58dfb66204f30f1a Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 1 Jul 2021 20:57:55 -0500 Subject: [PATCH 55/58] models and instructions extracted to lib --- packages/lending/src/models/index.ts | 1 - .../instructions/borrowObligationLiquidity.ts | 115 ------------ .../depositObligationCollateral.ts | 77 -------- .../instructions/depositReserveLiquidity.ts | 86 --------- .../lending/src/models/instructions/index.ts | 13 -- .../models/instructions/initLendingMarket.ts | 66 ------- .../src/models/instructions/initObligation.ts | 46 ----- .../src/models/instructions/initReserve.ts | 119 ------------- .../src/models/instructions/instruction.ts | 16 -- .../instructions/liquidateObligation.ts | 89 ---------- .../instructions/redeemReserveCollateral.ts | 76 -------- .../models/instructions/refreshObligation.ts | 52 ------ .../src/models/instructions/refreshReserve.ts | 39 ---- .../instructions/repayObligationLiquidity.ts | 71 -------- .../instructions/setLendingMarketOwner.ts | 48 ----- .../withdrawObligationCollateral.ts | 73 -------- .../lending/src/models/state/lastUpdate.ts | 13 -- .../lending/src/models/state/lendingMarket.ts | 48 +---- .../lending/src/models/state/obligation.ts | 149 +--------------- packages/lending/src/models/state/reserve.ts | 166 +++++------------- 20 files changed, 59 insertions(+), 1304 deletions(-) delete mode 100644 packages/lending/src/models/instructions/borrowObligationLiquidity.ts delete mode 100644 packages/lending/src/models/instructions/depositObligationCollateral.ts delete mode 100644 packages/lending/src/models/instructions/depositReserveLiquidity.ts delete mode 100644 packages/lending/src/models/instructions/index.ts delete mode 100644 packages/lending/src/models/instructions/initLendingMarket.ts delete mode 100644 packages/lending/src/models/instructions/initObligation.ts delete mode 100644 packages/lending/src/models/instructions/initReserve.ts delete mode 100644 packages/lending/src/models/instructions/instruction.ts delete mode 100644 packages/lending/src/models/instructions/liquidateObligation.ts delete mode 100644 packages/lending/src/models/instructions/redeemReserveCollateral.ts delete mode 100644 packages/lending/src/models/instructions/refreshObligation.ts delete mode 100644 packages/lending/src/models/instructions/refreshReserve.ts delete mode 100644 packages/lending/src/models/instructions/repayObligationLiquidity.ts delete mode 100644 packages/lending/src/models/instructions/setLendingMarketOwner.ts delete mode 100644 packages/lending/src/models/instructions/withdrawObligationCollateral.ts delete mode 100644 packages/lending/src/models/state/lastUpdate.ts diff --git a/packages/lending/src/models/index.ts b/packages/lending/src/models/index.ts index e2e28868..1d345c4f 100644 --- a/packages/lending/src/models/index.ts +++ b/packages/lending/src/models/index.ts @@ -1,4 +1,3 @@ -export * from './instructions'; export * from './state'; export * from './pool'; export * from './totals'; diff --git a/packages/lending/src/models/instructions/borrowObligationLiquidity.ts b/packages/lending/src/models/instructions/borrowObligationLiquidity.ts deleted file mode 100644 index 24567d21..00000000 --- a/packages/lending/src/models/instructions/borrowObligationLiquidity.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { calculateUtilizationRatio, Reserve } from '../state/reserve'; -import { LendingInstruction } from './instruction'; - -/// 10 -/// Borrow liquidity from a reserve by depositing collateral tokens. Requires a refreshed -/// obligation and reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source borrow reserve liquidity supply SPL Token account. -/// 1. `[writable]` Destination liquidity token account. -/// Minted by borrow reserve liquidity mint. -/// 2. `[writable]` Borrow reserve account - refreshed. -/// 3. `[writable]` Borrow reserve liquidity fee receiver account. -/// Must be the fee account specified at InitReserve. -/// 4. `[writable]` Obligation account - refreshed. -/// 5. `[]` Lending market account. -/// 6. `[]` Derived lending market authority. -/// 7. `[signer]` Obligation owner. -/// 8. `[]` Clock sysvar. -/// 9. `[]` Token program id. -/// 10 `[optional, writable]` Host fee receiver account. -/// -/// BorrowObligationLiquidity { -/// /// Amount of liquidity to borrow - u64::MAX for 100% of borrowing power -/// liquidity_amount: u64, -/// }, -export const borrowObligationLiquidityInstruction = ( - liquidityAmount: number | BN, - sourceLiquidity: PublicKey, - destinationLiquidity: PublicKey, - borrowReserve: PublicKey, - borrowReserveLiquidityFeeReceiver: PublicKey, - obligation: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - obligationOwner: PublicKey, - hostFeeReceiver?: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.BorrowObligationLiquidity, - liquidityAmount: new BN(liquidityAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, - { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, - { pubkey: borrowReserve, isSigner: false, isWritable: true }, - { - pubkey: borrowReserveLiquidityFeeReceiver, - isSigner: false, - isWritable: true, - }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: obligationOwner, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - if (hostFeeReceiver) { - keys.push({ pubkey: hostFeeReceiver, isSigner: false, isWritable: true }); - } - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; - -// deposit APY utilization currentUtilizationRate * borrowAPY - -export const calculateBorrowAPY = (reserve: Reserve) => { - const currentUtilization = calculateUtilizationRatio(reserve); - const optimalUtilization = reserve.config.optimalUtilizationRate / 100; - - let borrowAPY; - if (optimalUtilization === 1.0 || currentUtilization < optimalUtilization) { - const normalizedFactor = currentUtilization / optimalUtilization; - const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; - const minBorrowRate = reserve.config.minBorrowRate / 100; - borrowAPY = - normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate; - } else { - const normalizedFactor = - (currentUtilization - optimalUtilization) / (1 - optimalUtilization); - const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; - const maxBorrowRate = reserve.config.maxBorrowRate / 100; - borrowAPY = - normalizedFactor * (maxBorrowRate - optimalBorrowRate) + - optimalBorrowRate; - } - - return borrowAPY; -}; diff --git a/packages/lending/src/models/instructions/depositObligationCollateral.ts b/packages/lending/src/models/instructions/depositObligationCollateral.ts deleted file mode 100644 index d024537e..00000000 --- a/packages/lending/src/models/instructions/depositObligationCollateral.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from './../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 8 -/// Deposit collateral to an obligation. Requires a refreshed reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source collateral token account. -/// Minted by deposit reserve collateral mint. -/// $authority can transfer $collateral_amount. -/// 1. `[writable]` Destination deposit reserve collateral supply SPL Token account. -/// 2. `[]` Deposit reserve account - refreshed. -/// 3. `[writable]` Obligation account. -/// 4. `[]` Lending market account. -/// 5. `[]` Derived lending market authority. -/// 6. `[signer]` Obligation owner. -/// 7. `[signer]` User transfer authority ($authority). -/// 8. `[]` Clock sysvar. -/// 9. `[]` Token program id. -/// -/// DepositObligationCollateral { -/// /// Amount of collateral tokens to deposit -/// collateral_amount: u64, -/// }, -export const depositObligationCollateralInstruction = ( - collateralAmount: number | BN, - sourceCollateral: PublicKey, - destinationCollateral: PublicKey, - depositReserve: PublicKey, - obligation: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - obligationOwner: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('collateralAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.DepositObligationCollateral, - collateralAmount: new BN(collateralAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceCollateral, isSigner: false, isWritable: true }, - { pubkey: destinationCollateral, isSigner: false, isWritable: true }, - { pubkey: depositReserve, isSigner: false, isWritable: false }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: obligationOwner, isSigner: true, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/depositReserveLiquidity.ts b/packages/lending/src/models/instructions/depositReserveLiquidity.ts deleted file mode 100644 index ed953ab2..00000000 --- a/packages/lending/src/models/instructions/depositReserveLiquidity.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { calculateUtilizationRatio, Reserve } from '../state/reserve'; -import { calculateBorrowAPY } from './borrowObligationLiquidity'; -import { LendingInstruction } from './instruction'; - -/// 4 -/// Deposit liquidity into a reserve in exchange for collateral. Collateral represents a share -/// of the reserve liquidity pool. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source liquidity token account. -/// $authority can transfer $liquidity_amount. -/// 1. `[writable]` Destination collateral token account. -/// 2. `[writable]` Reserve account. -/// 3. `[writable]` Reserve liquidity supply SPL Token account. -/// 4. `[writable]` Reserve collateral SPL Token mint. -/// 5. `[]` Lending market account. -/// 6. `[]` Derived lending market authority. -/// 7. `[signer]` User transfer authority ($authority). -/// 8. `[]` Clock sysvar. -/// 9. `[]` Token program id. -/// -/// DepositReserveLiquidity { -/// /// Amount of liquidity to deposit in exchange for collateral tokens -/// liquidity_amount: u64, -/// }, -export const depositReserveLiquidityInstruction = ( - liquidityAmount: number | BN, - sourceLiquidity: PublicKey, - destinationCollateral: PublicKey, - reserve: PublicKey, - reserveLiquiditySupply: PublicKey, - reserveCollateralMint: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.DepositReserveLiquidity, - liquidityAmount: new BN(liquidityAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, - { pubkey: destinationCollateral, isSigner: false, isWritable: true }, - { pubkey: reserve, isSigner: false, isWritable: true }, - { pubkey: reserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: reserveCollateralMint, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; - -export const calculateDepositAPY = (reserve: Reserve) => { - const currentUtilization = calculateUtilizationRatio(reserve); - - const borrowAPY = calculateBorrowAPY(reserve); - return currentUtilization * borrowAPY; -}; diff --git a/packages/lending/src/models/instructions/index.ts b/packages/lending/src/models/instructions/index.ts deleted file mode 100644 index 38d8ce46..00000000 --- a/packages/lending/src/models/instructions/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from './borrowObligationLiquidity'; -export * from './depositObligationCollateral'; -export * from './depositReserveLiquidity'; -export * from './initLendingMarket'; -export * from './initObligation'; -export * from './initReserve'; -export * from './instruction'; -export * from './liquidateObligation'; -export * from './redeemReserveCollateral'; -export * from './refreshObligation'; -export * from './refreshReserve'; -export * from './repayObligationLiquidity'; -export * from './withdrawObligationCollateral'; diff --git a/packages/lending/src/models/instructions/initLendingMarket.ts b/packages/lending/src/models/instructions/initLendingMarket.ts deleted file mode 100644 index 15e19b8d..00000000 --- a/packages/lending/src/models/instructions/initLendingMarket.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LendingInstruction } from './instruction'; - -// @TODO: move to @oyster/common -export const ORACLE_PROGRAM_ID = new PublicKey( - '5mkqGkkWSaSk2NL9p4XptwEQu4d5jFTJiurbbzdqYexF', -); - -/// 0 -/// Initializes a new lending market. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Lending market account - uninitialized. -/// 1. `[]` Rent sysvar. -/// 2. `[]` Token program id. -/// 3. `[]` Oracle program id. -/// -/// InitLendingMarket { -/// /// Owner authority which can add new reserves -/// owner: Pubkey, -/// /// Currency market prices are quoted in -/// /// e.g. "USD" null padded (`*b"USD\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"`) or SPL token mint pubkey -/// quote_currency: [u8; 32], -/// }, -export const initLendingMarketInstruction = ( - owner: PublicKey, - quoteCurrency: Buffer, - lendingMarket: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.publicKey('owner'), - BufferLayout.blob(32, 'quoteCurrency'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.InitLendingMarket, - owner, - quoteCurrency, - }, - data, - ); - - const keys = [ - { pubkey: lendingMarket, isSigner: false, isWritable: true }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: ORACLE_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/initObligation.ts b/packages/lending/src/models/instructions/initObligation.ts deleted file mode 100644 index 9ce5e1d8..00000000 --- a/packages/lending/src/models/instructions/initObligation.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './instruction'; - -/// 6 -/// Initializes a new lending market obligation. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Obligation account - uninitialized. -/// 1. `[]` Lending market account. -/// 2. `[signer]` Obligation owner. -/// 3. `[]` Clock sysvar. -/// 4. `[]` Rent sysvar. -/// 5. `[]` Token program id. -export const initObligationInstruction = ( - obligation: PublicKey, - lendingMarket: PublicKey, - obligationOwner: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode({ instruction: LendingInstruction.InitObligation }, data); - - const keys = [ - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: obligationOwner, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/initReserve.ts b/packages/lending/src/models/instructions/initReserve.ts deleted file mode 100644 index 7400f45d..00000000 --- a/packages/lending/src/models/instructions/initReserve.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { ReserveConfig } from '../state'; -import { LendingInstruction } from './instruction'; - -/// 2 -/// Initializes a new lending market reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source liquidity token account. -/// $authority can transfer $liquidity_amount. -/// 1. `[writable]` Destination collateral token account - uninitialized. -/// 2. `[writable]` Reserve account - uninitialized. -/// 3. `[]` Reserve liquidity SPL Token mint. -/// 4. `[writable]` Reserve liquidity supply SPL Token account - uninitialized. -/// 5. `[writable]` Reserve liquidity fee receiver - uninitialized. -/// 6. `[]` Pyth product account. -/// 7. `[]` Pyth price account. -/// This will be used as the reserve liquidity oracle account. -/// 8. `[writable]` Reserve collateral SPL Token mint - uninitialized. -/// 9. `[writable]` Reserve collateral token supply - uninitialized. -/// 10 `[]` Lending market account. -/// 11 `[]` Derived lending market authority. -/// 12 `[signer]` Lending market owner. -/// 13 `[signer]` User transfer authority ($authority). -/// 14 `[]` Clock sysvar. -/// 15 `[]` Rent sysvar. -/// 16 `[]` Token program id. -/// -/// InitReserve { -/// /// Initial amount of liquidity to deposit into the new reserve -/// liquidity_amount: u64, -/// /// Reserve configuration values -/// config: ReserveConfig, -/// }, -export const initReserveInstruction = ( - liquidityAmount: number | BN, - config: ReserveConfig, - sourceLiquidity: PublicKey, - destinationCollateral: PublicKey, - reserve: PublicKey, - liquidityMint: PublicKey, - liquiditySupply: PublicKey, - liquidityFeeReceiver: PublicKey, - pythProduct: PublicKey, - pythPrice: PublicKey, - collateralMint: PublicKey, - collateralSupply: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - lendingMarketOwner: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - BufferLayout.u8('optimalUtilizationRate'), - BufferLayout.u8('loanToValueRatio'), - BufferLayout.u8('liquidationBonus'), - BufferLayout.u8('liquidationThreshold'), - BufferLayout.u8('minBorrowRate'), - BufferLayout.u8('optimalBorrowRate'), - BufferLayout.u8('maxBorrowRate'), - Layout.uint64('borrowFeeWad'), - BufferLayout.u8('hostFeePercentage'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.InitReserve, - liquidityAmount: new BN(liquidityAmount), - optimalUtilizationRate: config.optimalUtilizationRate, - loanToValueRatio: config.loanToValueRatio, - liquidationBonus: config.liquidationBonus, - liquidationThreshold: config.liquidationThreshold, - minBorrowRate: config.minBorrowRate, - optimalBorrowRate: config.optimalBorrowRate, - maxBorrowRate: config.maxBorrowRate, - borrowFeeWad: config.fees.borrowFeeWad, - hostFeePercentage: config.fees.hostFeePercentage, - }, - data, - ); - - const keys = [ - { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, - { pubkey: destinationCollateral, isSigner: false, isWritable: true }, - { pubkey: reserve, isSigner: false, isWritable: true }, - { pubkey: liquidityMint, isSigner: false, isWritable: false }, - { pubkey: liquiditySupply, isSigner: false, isWritable: true }, - { pubkey: liquidityFeeReceiver, isSigner: false, isWritable: true }, - { pubkey: pythProduct, isSigner: false, isWritable: false }, - { pubkey: pythPrice, isSigner: false, isWritable: false }, - { pubkey: collateralMint, isSigner: false, isWritable: true }, - { pubkey: collateralSupply, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: true }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/instruction.ts b/packages/lending/src/models/instructions/instruction.ts deleted file mode 100644 index 9fdbb595..00000000 --- a/packages/lending/src/models/instructions/instruction.ts +++ /dev/null @@ -1,16 +0,0 @@ -export enum LendingInstruction { - InitLendingMarket = 0, - SetLendingMarketOwner = 1, - InitReserve = 2, - RefreshReserve = 3, - DepositReserveLiquidity = 4, - RedeemReserveCollateral = 5, - InitObligation = 6, - RefreshObligation = 7, - DepositObligationCollateral = 8, - WithdrawObligationCollateral = 9, - BorrowObligationLiquidity = 10, - RepayObligationLiquidity = 11, - LiquidateObligation = 12, - FlashLoan = 13, -} diff --git a/packages/lending/src/models/instructions/liquidateObligation.ts b/packages/lending/src/models/instructions/liquidateObligation.ts deleted file mode 100644 index 3452c21a..00000000 --- a/packages/lending/src/models/instructions/liquidateObligation.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 12 -/// Repay borrowed liquidity to a reserve to receive collateral at a discount from an unhealthy -/// obligation. Requires a refreshed obligation and reserves. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source liquidity token account. -/// Minted by repay reserve liquidity mint. -/// $authority can transfer $liquidity_amount. -/// 1. `[writable]` Destination collateral token account. -/// Minted by withdraw reserve collateral mint. -/// 2. `[writable]` Repay reserve account - refreshed. -/// 3. `[writable]` Repay reserve liquidity supply SPL Token account. -/// 4. `[]` Withdraw reserve account - refreshed. -/// 5. `[writable]` Withdraw reserve collateral supply SPL Token account. -/// 6. `[writable]` Obligation account - refreshed. -/// 7. `[]` Lending market account. -/// 8. `[]` Derived lending market authority. -/// 9. `[signer]` User transfer authority ($authority). -/// 10 `[]` Clock sysvar. -/// 11 `[]` Token program id. -/// -/// LiquidateObligation { -/// /// Amount of liquidity to repay - u64::MAX for up to 100% of borrowed amount -/// liquidity_amount: u64, -/// }, -export const liquidateObligationInstruction = ( - liquidityAmount: number | BN, - sourceLiquidity: PublicKey, - destinationCollateral: PublicKey, - repayReserve: PublicKey, - repayReserveLiquiditySupply: PublicKey, - withdrawReserve: PublicKey, - withdrawReserveCollateralSupply: PublicKey, - obligation: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.LiquidateObligation, - liquidityAmount: new BN(liquidityAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, - { pubkey: destinationCollateral, isSigner: false, isWritable: true }, - { pubkey: repayReserve, isSigner: false, isWritable: true }, - { pubkey: repayReserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: withdrawReserve, isSigner: false, isWritable: false }, - { - pubkey: withdrawReserveCollateralSupply, - isSigner: false, - isWritable: true, - }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/redeemReserveCollateral.ts b/packages/lending/src/models/instructions/redeemReserveCollateral.ts deleted file mode 100644 index 39f02a70..00000000 --- a/packages/lending/src/models/instructions/redeemReserveCollateral.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 5 -/// Redeem collateral from a reserve in exchange for liquidity. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source collateral token account. -/// $authority can transfer $collateral_amount. -/// 1. `[writable]` Destination liquidity token account. -/// 2. `[writable]` Reserve account. -/// 3. `[writable]` Reserve collateral SPL Token mint. -/// 4. `[writable]` Reserve liquidity supply SPL Token account. -/// 5. `[]` Lending market account. -/// 6. `[]` Derived lending market authority. -/// 7. `[signer]` User transfer authority ($authority). -/// 8. `[]` Clock sysvar. -/// 9. `[]` Token program id. -/// -/// RedeemReserveCollateral { -/// /// Amount of collateral tokens to redeem in exchange for liquidity -/// collateral_amount: u64, -/// }, -export const redeemReserveCollateralInstruction = ( - collateralAmount: number | BN, - sourceCollateral: PublicKey, - destinationLiquidity: PublicKey, - reserve: PublicKey, - reserveCollateralMint: PublicKey, - reserveLiquiditySupply: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('collateralAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.RedeemReserveCollateral, - collateralAmount: new BN(collateralAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceCollateral, isSigner: false, isWritable: true }, - { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, - { pubkey: reserve, isSigner: false, isWritable: true }, - { pubkey: reserveCollateralMint, isSigner: false, isWritable: true }, - { pubkey: reserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/refreshObligation.ts b/packages/lending/src/models/instructions/refreshObligation.ts deleted file mode 100644 index 69b032cb..00000000 --- a/packages/lending/src/models/instructions/refreshObligation.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { LENDING_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './instruction'; - -/// 7 -/// Refresh an obligation's accrued interest and collateral and liquidity prices. Requires -/// refreshed reserves, as all obligation collateral deposit reserves in order, followed by all -/// liquidity borrow reserves in order. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Obligation account. -/// 1. `[]` Clock sysvar. -/// .. `[]` Collateral deposit reserve accounts - refreshed, all, in order. -/// .. `[]` Liquidity borrow reserve accounts - refreshed, all, in order. -export const refreshObligationInstruction = ( - obligation: PublicKey, - depositReserves: PublicKey[], - borrowReserves: PublicKey[], -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { instruction: LendingInstruction.RefreshObligation }, - data, - ); - - const keys = [ - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - ]; - - for (const depositReserve of depositReserves) { - keys.push({ pubkey: depositReserve, isSigner: false, isWritable: false }); - } - - for (const borrowReserve of borrowReserves) { - keys.push({ pubkey: borrowReserve, isSigner: false, isWritable: false }); - } - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/refreshReserve.ts b/packages/lending/src/models/instructions/refreshReserve.ts deleted file mode 100644 index 44b9b0c6..00000000 --- a/packages/lending/src/models/instructions/refreshReserve.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { LENDING_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import * as BufferLayout from 'buffer-layout'; -import { LendingInstruction } from './instruction'; - -/// 3 -/// Accrue interest and update market price of liquidity on a reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Reserve account. -/// 1. `[]` Reserve liquidity oracle account. -/// Must be the Pyth price account specified at InitReserve. -/// 2. `[]` Clock sysvar. -export const refreshReserveInstruction = ( - reserve: PublicKey, - oracle: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode({ instruction: LendingInstruction.RefreshReserve }, data); - - const keys = [ - { pubkey: reserve, isSigner: false, isWritable: true }, - { pubkey: oracle, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/repayObligationLiquidity.ts b/packages/lending/src/models/instructions/repayObligationLiquidity.ts deleted file mode 100644 index 132a637d..00000000 --- a/packages/lending/src/models/instructions/repayObligationLiquidity.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 11 -/// Repay borrowed liquidity to a reserve. Requires a refreshed obligation and reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source liquidity token account. -/// Minted by repay reserve liquidity mint. -/// $authority can transfer $liquidity_amount. -/// 1. `[writable]` Destination repay reserve liquidity supply SPL Token account. -/// 2. `[writable]` Repay reserve account - refreshed. -/// 3. `[writable]` Obligation account - refreshed. -/// 4. `[]` Lending market account. -/// 5. `[signer]` User transfer authority ($authority). -/// 6. `[]` Clock sysvar. -/// 7. `[]` Token program id. -/// -/// RepayObligationLiquidity { -/// /// Amount of liquidity to repay - u64::MAX for 100% of borrowed amount -/// liquidity_amount: u64, -/// }, -export const repayObligationLiquidityInstruction = ( - liquidityAmount: number | BN, - sourceLiquidity: PublicKey, - destinationLiquidity: PublicKey, - repayReserve: PublicKey, - obligation: PublicKey, - lendingMarket: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('liquidityAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.RepayObligationLiquidity, - liquidityAmount: new BN(liquidityAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, - { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, - { pubkey: repayReserve, isSigner: false, isWritable: true }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/setLendingMarketOwner.ts b/packages/lending/src/models/instructions/setLendingMarketOwner.ts deleted file mode 100644 index d9baee58..00000000 --- a/packages/lending/src/models/instructions/setLendingMarketOwner.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { LENDING_PROGRAM_ID } from '@oyster/common'; -import { PublicKey, TransactionInstruction } from '@solana/web3.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 1 -/// Sets the new owner of a lending market. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Lending market account. -/// 1. `[signer]` Current owner. -/// -/// SetLendingMarketOwner { -/// /// The new owner -/// new_owner: Pubkey, -/// }, -export const setLendingMarketOwnerInstruction = ( - newOwner: PublicKey, - lendingMarket: PublicKey, - currentOwner: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.publicKey('newOwner'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.SetLendingMarketOwner, - newOwner, - }, - data, - ); - - const keys = [ - { pubkey: lendingMarket, isSigner: false, isWritable: true }, - { pubkey: currentOwner, isSigner: true, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/instructions/withdrawObligationCollateral.ts b/packages/lending/src/models/instructions/withdrawObligationCollateral.ts deleted file mode 100644 index 8b263480..00000000 --- a/packages/lending/src/models/instructions/withdrawObligationCollateral.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@oyster/common'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from './../../utils/layout'; -import { LendingInstruction } from './instruction'; - -/// 9 -/// Withdraw collateral from an obligation. Requires a refreshed obligation and reserve. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source withdraw reserve collateral supply SPL Token account. -/// 1. `[writable]` Destination collateral token account. -/// Minted by withdraw reserve collateral mint. -/// 2. `[]` Withdraw reserve account - refreshed. -/// 3. `[writable]` Obligation account - refreshed. -/// 4. `[]` Lending market account. -/// 5. `[]` Derived lending market authority. -/// 6. `[signer]` Obligation owner. -/// 7. `[]` Clock sysvar. -/// 8. `[]` Token program id. -/// -/// WithdrawObligationCollateral { -/// /// Amount of collateral tokens to withdraw - u64::MAX for up to 100% of deposited amount -/// collateral_amount: u64, -/// }, -export const withdrawObligationCollateralInstruction = ( - collateralAmount: number | BN, - sourceCollateral: PublicKey, - destinationCollateral: PublicKey, - withdrawReserve: PublicKey, - obligation: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - obligationOwner: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('collateralAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.WithdrawObligationCollateral, - collateralAmount: new BN(collateralAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceCollateral, isSigner: false, isWritable: true }, - { pubkey: destinationCollateral, isSigner: false, isWritable: true }, - { pubkey: withdrawReserve, isSigner: false, isWritable: false }, - { pubkey: obligation, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: obligationOwner, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: LENDING_PROGRAM_ID, - data, - }); -}; diff --git a/packages/lending/src/models/state/lastUpdate.ts b/packages/lending/src/models/state/lastUpdate.ts deleted file mode 100644 index 885cbfa1..00000000 --- a/packages/lending/src/models/state/lastUpdate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; - -export interface LastUpdate { - slot: BN; - stale: boolean; -} - -export const LastUpdateLayout = BufferLayout.struct( - [Layout.uint64('slot'), BufferLayout.u8('stale')], - 'lastUpdate' -); diff --git a/packages/lending/src/models/state/lendingMarket.ts b/packages/lending/src/models/state/lendingMarket.ts index 9562b5b6..ce136fba 100644 --- a/packages/lending/src/models/state/lendingMarket.ts +++ b/packages/lending/src/models/state/lendingMarket.ts @@ -1,46 +1,14 @@ +import { AccountParser } from '@oyster/common'; +import { parseLendingMarket } from '@solana/spl-token-lending'; import { AccountInfo, PublicKey } from '@solana/web3.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -export interface LendingMarket { - version: number; - bumpSeed: number; - owner: PublicKey; - quoteCurrency: Buffer; - tokenProgramId: PublicKey; - oracleProgramId: PublicKey; -} - -export const LendingMarketLayout = BufferLayout.struct( - [ - BufferLayout.u8('version'), - BufferLayout.u8('bumpSeed'), - Layout.publicKey('owner'), - BufferLayout.blob(32, 'quoteCurrency'), - Layout.publicKey('tokenProgramId'), - Layout.publicKey('oracleProgramId'), - BufferLayout.blob(128, 'padding'), - ], -); - -export const isLendingMarket = (info: AccountInfo) => { - return info.data.length === LendingMarketLayout.span; -}; - -export const LendingMarketParser = ( +export const LendingMarketParser: AccountParser = ( pubkey: PublicKey, info: AccountInfo, ) => { - const buffer = Buffer.from(info.data); - const lendingMarket = LendingMarketLayout.decode(buffer); - - const details = { - pubkey, - account: { - ...info, - }, - info: lendingMarket, - }; - - return details; + const parsed = parseLendingMarket(pubkey, info); + if (parsed) { + const { pubkey, info: account, data: info } = parsed; + return { pubkey, account, info }; + } }; diff --git a/packages/lending/src/models/state/obligation.ts b/packages/lending/src/models/state/obligation.ts index 1540f45a..ae81586f 100644 --- a/packages/lending/src/models/state/obligation.ts +++ b/packages/lending/src/models/state/obligation.ts @@ -1,151 +1,16 @@ +import { AccountParser } from '@oyster/common'; +import { parseObligation } from '@solana/spl-token-lending'; import { AccountInfo, PublicKey } from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LastUpdate, LastUpdateLayout } from './lastUpdate'; -export interface Obligation { - version: number; - lastUpdate: LastUpdate; - lendingMarket: PublicKey; - owner: PublicKey; - // @FIXME: check usages - deposits: ObligationCollateral[]; - // @FIXME: check usages - borrows: ObligationLiquidity[]; - depositedValue: BN; // decimals - borrowedValue: BN; // decimals - allowedBorrowValue: BN; // decimals - unhealthyBorrowValue: BN; // decimals -} - -export interface ObligationCollateral { - depositReserve: PublicKey; - depositedAmount: BN; - marketValue: BN; // decimals -} - -export interface ObligationLiquidity { - borrowReserve: PublicKey; - cumulativeBorrowRateWads: BN; // decimals - borrowedAmountWads: BN; // decimals - marketValue: BN; // decimals -} - -export interface ProtoObligation { - version: number; - lastUpdate: LastUpdate; - lendingMarket: PublicKey; - owner: PublicKey; - depositedValue: BN; // decimals - borrowedValue: BN; // decimals - allowedBorrowValue: BN; // decimals - unhealthyBorrowValue: BN; // decimals - depositsLen: number; - borrowsLen: number; - dataFlat: Buffer; -} - -export const ObligationLayout = BufferLayout.struct( - [ - BufferLayout.u8('version'), - - LastUpdateLayout, - - Layout.publicKey('lendingMarket'), - Layout.publicKey('owner'), - Layout.uint128('depositedValue'), - Layout.uint128('borrowedValue'), - Layout.uint128('allowedBorrowValue'), - Layout.uint128('unhealthyBorrowValue'), - - BufferLayout.u8('depositsLen'), - BufferLayout.u8('borrowsLen'), - BufferLayout.blob(776, 'dataFlat'), - ], -); - -export const ObligationCollateralLayout = BufferLayout.struct( - [ - Layout.publicKey('depositReserve'), - Layout.uint64('depositedAmount'), - Layout.uint128('marketValue'), - ], -); - -export const ObligationLiquidityLayout = BufferLayout.struct( - [ - Layout.publicKey('borrowReserve'), - Layout.uint128('cumulativeBorrowRateWads'), - Layout.uint128('borrowedAmountWads'), - Layout.uint128('marketValue'), - ], -); - -export const isObligation = (info: AccountInfo) => { - return info.data.length === ObligationLayout.span; -}; - -export const ObligationParser = ( +export const ObligationParser: AccountParser = ( pubkey: PublicKey, info: AccountInfo, ) => { - const buffer = Buffer.from(info.data); - const { - version, - lastUpdate, - lendingMarket, - owner, - depositedValue, - borrowedValue, - allowedBorrowValue, - unhealthyBorrowValue, - depositsLen, - borrowsLen, - dataFlat, - } = ObligationLayout.decode(buffer); - - if (lastUpdate.slot.isZero()) { - return; + const parsed = parseObligation(pubkey, info); + if (parsed) { + const { pubkey, info: account, data: info } = parsed; + return { pubkey, account, info }; } - - const depositsSpan = depositsLen * ObligationCollateralLayout.span; - const borrowsSpan = borrowsLen * ObligationLiquidityLayout.span; - - const depositsBuffer = dataFlat.slice(0, depositsSpan); - const deposits = BufferLayout.seq( - ObligationCollateralLayout, - depositsLen, - ).decode(depositsBuffer); - - const borrowsBuffer = dataFlat.slice(depositsSpan, depositsSpan + borrowsSpan); - const borrows = BufferLayout.seq( - ObligationLiquidityLayout, - borrowsLen, - ).decode(borrowsBuffer); - - const obligation = { - version, - lastUpdate, - lendingMarket, - owner, - depositedValue, - borrowedValue, - allowedBorrowValue, - unhealthyBorrowValue, - deposits, - borrows, - } as Obligation; - - const details = { - pubkey, - account: { - ...info, - }, - info: obligation, - }; - - return details; }; // @TODO: implement diff --git a/packages/lending/src/models/state/reserve.ts b/packages/lending/src/models/state/reserve.ts index e642c00d..9ec748ea 100644 --- a/packages/lending/src/models/state/reserve.ts +++ b/packages/lending/src/models/state/reserve.ts @@ -1,129 +1,17 @@ -import { wadToLamports } from '@oyster/common'; +import { AccountParser, wadToLamports } from '@oyster/common'; +import { parseReserve, Reserve } from '@solana/spl-token-lending'; import { AccountInfo, PublicKey } from '@solana/web3.js'; import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import * as Layout from '../../utils/layout'; -import { LastUpdate, LastUpdateLayout } from './lastUpdate'; -export interface Reserve { - version: number; - lastUpdate: LastUpdate; - lendingMarket: PublicKey; - liquidity: ReserveLiquidity; - collateral: ReserveCollateral; - config: ReserveConfig; -} - -export interface ReserveLiquidity { - mintPubkey: PublicKey; - mintDecimals: number; - supplyPubkey: PublicKey; - feeReceiver: PublicKey; - oraclePubkey: PublicKey; - availableAmount: BN; - borrowedAmountWads: BN; // decimals - cumulativeBorrowRateWads: BN; // decimals - marketPrice: BN; // decimals -} - -export interface ReserveCollateral { - mintPubkey: PublicKey; - mintTotalSupply: BN; - supplyPubkey: PublicKey; -} - -export interface ReserveConfig { - optimalUtilizationRate: number; - loanToValueRatio: number; - liquidationBonus: number; - liquidationThreshold: number; - minBorrowRate: number; - optimalBorrowRate: number; - maxBorrowRate: number; - fees: { - borrowFeeWad: BN; - hostFeePercentage: number; - }; -} - -export const ReserveLayout = BufferLayout.struct( - [ - BufferLayout.u8('version'), - - LastUpdateLayout, - - Layout.publicKey('lendingMarket'), - - BufferLayout.struct( - [ - Layout.publicKey('mintPubkey'), - BufferLayout.u8('mintDecimals'), - Layout.publicKey('supplyPubkey'), - Layout.publicKey('feeReceiver'), - Layout.publicKey('oraclePubkey'), - Layout.uint64('availableAmount'), - Layout.uint128('borrowedAmountWads'), - Layout.uint128('cumulativeBorrowRateWads'), - Layout.uint128('marketPrice'), - ], - 'liquidity', - ), - - BufferLayout.struct( - [ - Layout.publicKey('mintPubkey'), - Layout.uint64('mintTotalSupply'), - Layout.publicKey('supplyPubkey'), - ], - 'collateral' - ), - - BufferLayout.struct( - [ - BufferLayout.u8('optimalUtilizationRate'), - BufferLayout.u8('loanToValueRatio'), - BufferLayout.u8('liquidationBonus'), - BufferLayout.u8('liquidationThreshold'), - BufferLayout.u8('minBorrowRate'), - BufferLayout.u8('optimalBorrowRate'), - BufferLayout.u8('maxBorrowRate'), - BufferLayout.struct( - [ - Layout.uint64('borrowFeeWad'), - Layout.uint64('flashLoanFeeWad'), - BufferLayout.u8('hostFeePercentage') - ], - 'fees', - ), - ], - 'config' - ), - - BufferLayout.blob(248, 'padding'), - ], -); - -export const isReserve = (info: AccountInfo) => { - return info.data.length === ReserveLayout.span; -}; - -export const ReserveParser = (pubkey: PublicKey, info: AccountInfo) => { - const buffer = Buffer.from(info.data); - const reserve = ReserveLayout.decode(buffer); - - if (reserve.lastUpdate.slot.isZero()) { - return; +export const ReserveParser: AccountParser = ( + pubkey: PublicKey, + info: AccountInfo, +) => { + const parsed = parseReserve(pubkey, info); + if (parsed) { + const { pubkey, info: account, data: info } = parsed; + return { pubkey, account, info }; } - - const details = { - pubkey, - account: { - ...info, - }, - info: reserve, - }; - - return details; }; export const calculateUtilizationRatio = (reserve: Reserve) => { @@ -174,3 +62,37 @@ export const liquidityToCollateral = ( : liquidityAmount.toNumber(); return Math.floor(amount * collateralExchangeRate(reserve)); }; + +// deposit APY utilization currentUtilizationRate * borrowAPY + +export const calculateBorrowAPY = (reserve: Reserve) => { + const currentUtilization = calculateUtilizationRatio(reserve); + const optimalUtilization = reserve.config.optimalUtilizationRate / 100; + + let borrowAPY; + if (optimalUtilization === 1.0 || currentUtilization < optimalUtilization) { + const normalizedFactor = currentUtilization / optimalUtilization; + const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; + const minBorrowRate = reserve.config.minBorrowRate / 100; + borrowAPY = + normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate; + } else { + const normalizedFactor = + (currentUtilization - optimalUtilization) / (1 - optimalUtilization); + const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; + const maxBorrowRate = reserve.config.maxBorrowRate / 100; + borrowAPY = + normalizedFactor * (maxBorrowRate - optimalBorrowRate) + + optimalBorrowRate; + } + + return borrowAPY; +}; + +export const calculateDepositAPY = (reserve: Reserve) => { + const currentUtilization = calculateUtilizationRatio(reserve); + + const borrowAPY = calculateBorrowAPY(reserve); + return currentUtilization * borrowAPY; +}; + From 944758b7d75b7fbb95b5b769256d4f7a6b41e959 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 1 Jul 2021 20:59:12 -0500 Subject: [PATCH 56/58] refactor after lib move --- .../lending/src/actions/borrowObligationLiquidity.tsx | 4 ++-- packages/lending/src/actions/createObligation.tsx | 4 ++-- packages/lending/src/actions/createReserve.tsx | 4 ++-- .../src/actions/depositObligationCollateral.tsx | 2 +- .../lending/src/actions/depositReserveLiquidity.tsx | 2 +- packages/lending/src/actions/index.ts | 1 + packages/lending/src/actions/initObligation.tsx | 4 ++-- packages/lending/src/actions/initReserve.tsx | 2 +- packages/lending/src/actions/liquidateObligation.tsx | 4 ++-- .../lending/src/actions/redeemReserveCollateral.tsx | 2 +- .../{helpers => }/refreshObligationAndReserves.tsx | 6 +++--- .../lending/src/actions/repayObligationLiquidity.tsx | 4 ++-- .../src/actions/withdrawObligationCollateral.tsx | 4 ++-- .../lending/src/components/CollateralInput/index.tsx | 3 ++- .../src/components/CollateralSelector/index.tsx | 2 +- .../lending/src/components/DepositInfoLine/index.tsx | 3 ++- packages/lending/src/components/DepositInput/index.tsx | 2 +- .../lending/src/components/ReserveStatus/index.tsx | 3 ++- .../src/components/ReserveUtilizationChart/index.tsx | 2 +- .../src/components/SideReserveOverview/index.tsx | 2 +- .../lending/src/components/UserLendingCard/index.tsx | 2 +- .../lending/src/components/WithdrawInput/index.tsx | 2 +- packages/lending/src/contexts/lending.tsx | 10 ++++++---- packages/lending/src/contexts/market.tsx | 3 ++- packages/lending/src/hooks/useCollateralBalance.ts | 3 ++- packages/lending/src/hooks/useLendingMarket.ts | 3 ++- packages/lending/src/hooks/useLendingObligations.ts | 3 ++- packages/lending/src/hooks/useLendingReserves.ts | 3 ++- packages/lending/src/hooks/useUserDeposits.ts | 3 ++- packages/lending/src/views/borrow/item.tsx | 3 ++- packages/lending/src/views/deposit/view/item.tsx | 3 ++- packages/lending/src/views/home/item.tsx | 2 +- packages/lending/src/views/margin/item.tsx | 3 ++- .../src/views/margin/newPosition/NewPositionForm.tsx | 3 ++- .../src/views/margin/newPosition/interfaces.tsx | 2 +- packages/lending/src/views/margin/newPosition/utils.ts | 3 ++- 36 files changed, 64 insertions(+), 47 deletions(-) rename packages/lending/src/actions/{helpers => }/refreshObligationAndReserves.tsx (94%) diff --git a/packages/lending/src/actions/borrowObligationLiquidity.tsx b/packages/lending/src/actions/borrowObligationLiquidity.tsx index 6b1790fd..9c480ab8 100644 --- a/packages/lending/src/actions/borrowObligationLiquidity.tsx +++ b/packages/lending/src/actions/borrowObligationLiquidity.tsx @@ -18,8 +18,8 @@ import { borrowObligationLiquidityInstruction, Obligation, Reserve, -} from '../models'; -import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; +} from '@solana/spl-token-lending'; +import { refreshObligationAndReserves } from './refreshObligationAndReserves'; const { cache, MintParser } = contexts.Accounts; const { sendTransaction } = contexts.Connection; diff --git a/packages/lending/src/actions/createObligation.tsx b/packages/lending/src/actions/createObligation.tsx index dda8a029..eb587009 100644 --- a/packages/lending/src/actions/createObligation.tsx +++ b/packages/lending/src/actions/createObligation.tsx @@ -1,5 +1,5 @@ +import { OBLIGATION_SIZE } from '@solana/spl-token-lending'; import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js'; -import { ObligationLayout } from '../models'; import { createAccount } from './createAccount'; export function createObligation( @@ -13,6 +13,6 @@ export function createObligation( payer, amount, signers, - ObligationLayout.span, + OBLIGATION_SIZE, ); } diff --git a/packages/lending/src/actions/createReserve.tsx b/packages/lending/src/actions/createReserve.tsx index 71e8ec98..170a8360 100644 --- a/packages/lending/src/actions/createReserve.tsx +++ b/packages/lending/src/actions/createReserve.tsx @@ -1,5 +1,5 @@ +import { RESERVE_SIZE } from '@solana/spl-token-lending'; import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js'; -import { ReserveLayout } from '../models'; import { createAccount } from './createAccount'; export function createReserve( @@ -13,6 +13,6 @@ export function createReserve( payer, amount, signers, - ReserveLayout.span, + RESERVE_SIZE, ); } diff --git a/packages/lending/src/actions/depositObligationCollateral.tsx b/packages/lending/src/actions/depositObligationCollateral.tsx index 5120499d..e30a2931 100644 --- a/packages/lending/src/actions/depositObligationCollateral.tsx +++ b/packages/lending/src/actions/depositObligationCollateral.tsx @@ -14,7 +14,7 @@ import { import { depositObligationCollateralInstruction, Reserve, -} from '../models'; +} from '@solana/spl-token-lending'; const { approve } = models; diff --git a/packages/lending/src/actions/depositReserveLiquidity.tsx b/packages/lending/src/actions/depositReserveLiquidity.tsx index 7cdd1d63..1526ed19 100644 --- a/packages/lending/src/actions/depositReserveLiquidity.tsx +++ b/packages/lending/src/actions/depositReserveLiquidity.tsx @@ -18,7 +18,7 @@ import { depositReserveLiquidityInstruction, refreshReserveInstruction, Reserve, -} from '../models'; +} from '@solana/spl-token-lending'; const { approve } = models; diff --git a/packages/lending/src/actions/index.ts b/packages/lending/src/actions/index.ts index cf62d2f3..b340a257 100644 --- a/packages/lending/src/actions/index.ts +++ b/packages/lending/src/actions/index.ts @@ -8,6 +8,7 @@ export { initObligation } from './initObligation'; export { initReserve } from './initReserve'; export { liquidateObligation } from './liquidateObligation'; export { redeemReserveCollateral } from './redeemReserveCollateral'; +export { refreshObligationAndReserves } from './refreshObligationAndReserves'; export { repayObligationLiquidity } from './repayObligationLiquidity'; export { withdrawObligationCollateral } from './withdrawObligationCollateral'; diff --git a/packages/lending/src/actions/initObligation.tsx b/packages/lending/src/actions/initObligation.tsx index 041fe587..46229cb7 100644 --- a/packages/lending/src/actions/initObligation.tsx +++ b/packages/lending/src/actions/initObligation.tsx @@ -5,7 +5,7 @@ import { PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { initObligationInstruction, ObligationLayout } from '../models'; +import { initObligationInstruction, OBLIGATION_SIZE } from '@solana/spl-token-lending'; import { createObligation } from './createObligation'; export const initObligation = async ( @@ -24,7 +24,7 @@ export const initObligation = async ( const cleanupInstructions: TransactionInstruction[] = []; const obligationRentExempt = await connection.getMinimumBalanceForRentExemption( - ObligationLayout.span, + OBLIGATION_SIZE, ); const obligationAddress = createObligation( diff --git a/packages/lending/src/actions/initReserve.tsx b/packages/lending/src/actions/initReserve.tsx index 59166037..b383d6ab 100644 --- a/packages/lending/src/actions/initReserve.tsx +++ b/packages/lending/src/actions/initReserve.tsx @@ -14,7 +14,7 @@ import { PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { initReserveInstruction, Reserve } from '../models'; +import { initReserveInstruction, Reserve } from '@solana/spl-token-lending'; const { approve } = models; diff --git a/packages/lending/src/actions/liquidateObligation.tsx b/packages/lending/src/actions/liquidateObligation.tsx index 96a495cd..6ee50648 100644 --- a/packages/lending/src/actions/liquidateObligation.tsx +++ b/packages/lending/src/actions/liquidateObligation.tsx @@ -15,8 +15,8 @@ import { PublicKey, TransactionInstruction, } from '@solana/web3.js'; -import { liquidateObligationInstruction, Obligation, Reserve } from '../models'; -import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; +import { liquidateObligationInstruction, Obligation, Reserve } from '@solana/spl-token-lending'; +import { refreshObligationAndReserves } from './refreshObligationAndReserves'; const { approve } = models; diff --git a/packages/lending/src/actions/redeemReserveCollateral.tsx b/packages/lending/src/actions/redeemReserveCollateral.tsx index 84eaa9b1..8b84ea60 100644 --- a/packages/lending/src/actions/redeemReserveCollateral.tsx +++ b/packages/lending/src/actions/redeemReserveCollateral.tsx @@ -17,7 +17,7 @@ import { redeemReserveCollateralInstruction, refreshReserveInstruction, Reserve, -} from '../models'; +} from '@solana/spl-token-lending'; const { approve } = models; diff --git a/packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx b/packages/lending/src/actions/refreshObligationAndReserves.tsx similarity index 94% rename from packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx rename to packages/lending/src/actions/refreshObligationAndReserves.tsx index f59ac5ed..17120921 100644 --- a/packages/lending/src/actions/helpers/refreshObligationAndReserves.tsx +++ b/packages/lending/src/actions/refreshObligationAndReserves.tsx @@ -1,12 +1,12 @@ import { contexts, ParsedAccount } from '@oyster/common'; -import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js'; import { Obligation, refreshObligationInstruction, refreshReserveInstruction, Reserve, - ReserveParser, -} from '../../models'; +} from '@solana/spl-token-lending'; +import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { ReserveParser } from '../models'; const { cache } = contexts.Accounts; diff --git a/packages/lending/src/actions/repayObligationLiquidity.tsx b/packages/lending/src/actions/repayObligationLiquidity.tsx index bee8001d..ef9f8f58 100644 --- a/packages/lending/src/actions/repayObligationLiquidity.tsx +++ b/packages/lending/src/actions/repayObligationLiquidity.tsx @@ -13,8 +13,8 @@ import { Obligation, repayObligationLiquidityInstruction, Reserve, -} from '../models'; -import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; +} from '@solana/spl-token-lending'; +import { refreshObligationAndReserves } from './refreshObligationAndReserves'; const { approve } = models; diff --git a/packages/lending/src/actions/withdrawObligationCollateral.tsx b/packages/lending/src/actions/withdrawObligationCollateral.tsx index a163b70b..2ca5b3e2 100644 --- a/packages/lending/src/actions/withdrawObligationCollateral.tsx +++ b/packages/lending/src/actions/withdrawObligationCollateral.tsx @@ -17,8 +17,8 @@ import { Obligation, Reserve, withdrawObligationCollateralInstruction, -} from '../models'; -import { refreshObligationAndReserves } from './helpers/refreshObligationAndReserves'; +} from '@solana/spl-token-lending'; +import { refreshObligationAndReserves } from './refreshObligationAndReserves'; export const withdrawObligationCollateral = async ( connection: Connection, diff --git a/packages/lending/src/components/CollateralInput/index.tsx b/packages/lending/src/components/CollateralInput/index.tsx index 19a85a71..c73fb86d 100644 --- a/packages/lending/src/components/CollateralInput/index.tsx +++ b/packages/lending/src/components/CollateralInput/index.tsx @@ -6,6 +6,7 @@ import { TokenDisplay, TokenIcon, } from '@oyster/common'; +import { LendingMarket, Reserve } from '@solana/spl-token-lending'; import { Card, Select } from 'antd'; import React, { useEffect, useState } from 'react'; import { @@ -13,7 +14,7 @@ import { useUserBalance, useUserDeposits, } from '../../hooks'; -import { LendingMarket, Reserve, ReserveParser } from '../../models'; +import { ReserveParser } from '../../models'; import './style.less'; const { cache } = contexts.Accounts; diff --git a/packages/lending/src/components/CollateralSelector/index.tsx b/packages/lending/src/components/CollateralSelector/index.tsx index 59b93536..0a97f200 100644 --- a/packages/lending/src/components/CollateralSelector/index.tsx +++ b/packages/lending/src/components/CollateralSelector/index.tsx @@ -5,10 +5,10 @@ import { ParsedAccount, TokenIcon, } from '@oyster/common'; +import { LendingMarket, Reserve } from '@solana/spl-token-lending'; import { Select } from 'antd'; import React from 'react'; import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks'; -import { LendingMarket, Reserve } from '../../models'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; diff --git a/packages/lending/src/components/DepositInfoLine/index.tsx b/packages/lending/src/components/DepositInfoLine/index.tsx index cb92ea2b..794cd082 100644 --- a/packages/lending/src/components/DepositInfoLine/index.tsx +++ b/packages/lending/src/components/DepositInfoLine/index.tsx @@ -1,9 +1,10 @@ import { formatNumber, formatPct, useTokenName } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Card, Col, Row, Statistic } from 'antd'; import React, { useMemo } from 'react'; import { GUTTER } from '../../constants'; -import { calculateDepositAPY, Reserve } from '../../models'; +import { calculateDepositAPY } from '../../models'; import { useUserBalance, useUserCollateralBalance } from './../../hooks'; import './style.less'; diff --git a/packages/lending/src/components/DepositInput/index.tsx b/packages/lending/src/components/DepositInput/index.tsx index 87a798a4..062fb966 100644 --- a/packages/lending/src/components/DepositInput/index.tsx +++ b/packages/lending/src/components/DepositInput/index.tsx @@ -1,11 +1,11 @@ import { ActionConfirmation, ConnectButton, contexts } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Card, Slider } from 'antd'; import React, { useCallback, useState } from 'react'; import { depositReserveLiquidity } from '../../actions'; import { LABELS, marks } from '../../constants'; import { InputType, useSliderInput, useUserBalance } from '../../hooks'; -import { Reserve } from '../../models'; import CollateralInput from '../CollateralInput'; import './style.less'; diff --git a/packages/lending/src/components/ReserveStatus/index.tsx b/packages/lending/src/components/ReserveStatus/index.tsx index b488bdc9..2413ba84 100644 --- a/packages/lending/src/components/ReserveStatus/index.tsx +++ b/packages/lending/src/components/ReserveStatus/index.tsx @@ -5,12 +5,13 @@ import { TokenIcon, wadToLamports, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Card, Col, Row, Statistic } from 'antd'; import React, { useMemo } from 'react'; import { GUTTER, LABELS } from '../../constants'; import { usePrice } from '../../contexts/pyth'; -import { calculateDepositAPY, Reserve } from '../../models'; +import { calculateDepositAPY } from '../../models'; import { ReserveUtilizationChart } from './../../components/ReserveUtilizationChart'; import './style.less'; diff --git a/packages/lending/src/components/ReserveUtilizationChart/index.tsx b/packages/lending/src/components/ReserveUtilizationChart/index.tsx index 2a65f5ce..b0a59015 100644 --- a/packages/lending/src/components/ReserveUtilizationChart/index.tsx +++ b/packages/lending/src/components/ReserveUtilizationChart/index.tsx @@ -1,7 +1,7 @@ import { contexts, fromLamports, wadToLamports } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { Statistic } from 'antd'; import React, { useMemo } from 'react'; -import { Reserve } from '../../models'; import { WaterWave } from './../WaterWave'; const { useMint } = contexts.Accounts; diff --git a/packages/lending/src/components/SideReserveOverview/index.tsx b/packages/lending/src/components/SideReserveOverview/index.tsx index 6c516138..f0fc07cc 100644 --- a/packages/lending/src/components/SideReserveOverview/index.tsx +++ b/packages/lending/src/components/SideReserveOverview/index.tsx @@ -7,6 +7,7 @@ import { TokenIcon, useTokenName, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { Card, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; @@ -15,7 +16,6 @@ import { calculateBorrowAPY, calculateDepositAPY, calculateUtilizationRatio, - Reserve, } from '../../models'; const { useMint } = contexts.Accounts; diff --git a/packages/lending/src/components/UserLendingCard/index.tsx b/packages/lending/src/components/UserLendingCard/index.tsx index f9f07908..cff4053a 100644 --- a/packages/lending/src/components/UserLendingCard/index.tsx +++ b/packages/lending/src/components/UserLendingCard/index.tsx @@ -1,10 +1,10 @@ import { formatNumber, useTokenName } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Button, Card, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { LABELS } from '../../constants'; -import { Reserve } from '../../models'; import { useBorrowedAmount, useBorrowingPower, diff --git a/packages/lending/src/components/WithdrawInput/index.tsx b/packages/lending/src/components/WithdrawInput/index.tsx index b6861b89..cc9c5a3c 100644 --- a/packages/lending/src/components/WithdrawInput/index.tsx +++ b/packages/lending/src/components/WithdrawInput/index.tsx @@ -1,4 +1,5 @@ import { ActionConfirmation, ConnectButton, contexts } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Card, Slider } from 'antd'; import React, { useCallback, useState } from 'react'; @@ -10,7 +11,6 @@ import { useUserBalance, useUserCollateralBalance, } from '../../hooks'; -import { Reserve } from '../../models'; import CollateralInput from '../CollateralInput'; import './style.less'; diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index 552d265b..5af566dd 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -1,14 +1,16 @@ import { contexts, LENDING_PROGRAM_ID, ParsedAccount } from '@oyster/common'; -import { AccountInfo, PublicKey } from '@solana/web3.js'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useLendingReserves } from '../hooks'; import { isLendingMarket, isObligation, isReserve, + Reserve, +} from '@solana/spl-token-lending'; +import { AccountInfo, PublicKey } from '@solana/web3.js'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useLendingReserves } from '../hooks'; +import { LendingMarketParser, ObligationParser, - Reserve, ReserveParser, } from '../models'; import { usePrecacheMarket } from './market'; diff --git a/packages/lending/src/contexts/market.tsx b/packages/lending/src/contexts/market.tsx index 2fe84e04..33dd80fe 100644 --- a/packages/lending/src/contexts/market.tsx +++ b/packages/lending/src/contexts/market.tsx @@ -8,6 +8,7 @@ import { STABLE_COINS, } from '@oyster/common'; import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum'; +import { Reserve } from '@solana/spl-token-lending'; import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'; import React, { useCallback, @@ -16,7 +17,7 @@ import React, { useMemo, useState, } from 'react'; -import { PoolInfo, Reserve } from '../models'; +import { PoolInfo } from '../models'; import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from '../utils/pools'; import { getPoolName } from '../utils/utils'; import { POOLS_WITH_AIRDROP } from './../models/airdrops'; diff --git a/packages/lending/src/hooks/useCollateralBalance.ts b/packages/lending/src/hooks/useCollateralBalance.ts index b3487c5b..17d3c670 100644 --- a/packages/lending/src/hooks/useCollateralBalance.ts +++ b/packages/lending/src/hooks/useCollateralBalance.ts @@ -1,8 +1,9 @@ import { contexts, fromLamports } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; -import { Reserve, reserveMarketCap } from '../models'; import { usePrice } from '../contexts/pyth'; +import { reserveMarketCap } from '../models'; import { useUserBalance } from './useUserBalance'; const { useMint } = contexts.Accounts; diff --git a/packages/lending/src/hooks/useLendingMarket.ts b/packages/lending/src/hooks/useLendingMarket.ts index 0042be2f..e00c78e4 100644 --- a/packages/lending/src/hooks/useLendingMarket.ts +++ b/packages/lending/src/hooks/useLendingMarket.ts @@ -1,7 +1,8 @@ import { contexts, ParsedAccount } from '@oyster/common'; +import { LendingMarket } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useState } from 'react'; -import { LendingMarket, LendingMarketParser } from '../models'; +import { LendingMarketParser } from '../models'; const { cache } = contexts.Accounts; diff --git a/packages/lending/src/hooks/useLendingObligations.ts b/packages/lending/src/hooks/useLendingObligations.ts index 292dca52..07dc218e 100644 --- a/packages/lending/src/hooks/useLendingObligations.ts +++ b/packages/lending/src/hooks/useLendingObligations.ts @@ -1,7 +1,8 @@ import { contexts, ParsedAccount } from '@oyster/common'; +import { Obligation } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useState } from 'react'; -import { Obligation, ObligationParser } from '../models'; +import { ObligationParser } from '../models'; const { cache } = contexts.Accounts; diff --git a/packages/lending/src/hooks/useLendingReserves.ts b/packages/lending/src/hooks/useLendingReserves.ts index 93e491d2..6191a4bb 100644 --- a/packages/lending/src/hooks/useLendingReserves.ts +++ b/packages/lending/src/hooks/useLendingReserves.ts @@ -1,8 +1,9 @@ import { contexts, getTokenByName, ParsedAccount } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { TokenInfo } from '@solana/spl-token-registry'; import { PublicKey } from '@solana/web3.js'; import { useEffect, useMemo, useState } from 'react'; -import { Reserve, ReserveParser } from '../models'; +import { ReserveParser } from '../models'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; diff --git a/packages/lending/src/hooks/useUserDeposits.ts b/packages/lending/src/hooks/useUserDeposits.ts index dd64302c..72fc820c 100644 --- a/packages/lending/src/hooks/useUserDeposits.ts +++ b/packages/lending/src/hooks/useUserDeposits.ts @@ -7,9 +7,10 @@ import { useUserAccounts, } from '@oyster/common'; import { MintInfo } from '@solana/spl-token'; +import { Reserve } from '@solana/spl-token-lending'; import { useEffect, useMemo, useState } from 'react'; import { usePyth } from '../contexts/pyth'; -import { calculateDepositAPY, Reserve } from '../models'; +import { calculateDepositAPY } from '../models'; import { calculateCollateralBalance } from './useCollateralBalance'; import { useLendingReserves } from './useLendingReserves'; diff --git a/packages/lending/src/views/borrow/item.tsx b/packages/lending/src/views/borrow/item.tsx index 46bc541d..2a918159 100644 --- a/packages/lending/src/views/borrow/item.tsx +++ b/packages/lending/src/views/borrow/item.tsx @@ -4,6 +4,7 @@ import { TokenIcon, useTokenName, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Button } from 'antd'; import React from 'react'; @@ -11,7 +12,7 @@ import { Link } from 'react-router-dom'; import { LABELS } from '../../constants'; import { usePrice } from '../../contexts/pyth'; import { useBorrowingPower } from '../../hooks'; -import { calculateBorrowAPY, Reserve } from '../../models'; +import { calculateBorrowAPY } from '../../models'; export const BorrowItem = (props: { reserve: Reserve; address: PublicKey }) => { const name = useTokenName(props.reserve.liquidity.mintPubkey); diff --git a/packages/lending/src/views/deposit/view/item.tsx b/packages/lending/src/views/deposit/view/item.tsx index c7ee0434..7e3e71c3 100644 --- a/packages/lending/src/views/deposit/view/item.tsx +++ b/packages/lending/src/views/deposit/view/item.tsx @@ -4,13 +4,14 @@ import { TokenIcon, useTokenName, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Button } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { LABELS } from '../../../constants'; import { useUserBalance, useUserCollateralBalance } from '../../../hooks'; -import { calculateDepositAPY, Reserve } from '../../../models'; +import { calculateDepositAPY } from '../../../models'; export const ReserveItem = (props: { reserve: Reserve; diff --git a/packages/lending/src/views/home/item.tsx b/packages/lending/src/views/home/item.tsx index 3ab23beb..9dc6a9f9 100644 --- a/packages/lending/src/views/home/item.tsx +++ b/packages/lending/src/views/home/item.tsx @@ -7,13 +7,13 @@ import { useTokenName, wadToLamports, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; import { calculateBorrowAPY, calculateDepositAPY, - Reserve, TotalItem, } from '../../models'; diff --git a/packages/lending/src/views/margin/item.tsx b/packages/lending/src/views/margin/item.tsx index 1a1511d8..e1c37c01 100644 --- a/packages/lending/src/views/margin/item.tsx +++ b/packages/lending/src/views/margin/item.tsx @@ -4,6 +4,7 @@ import { TokenIcon, useTokenName, } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { PublicKey } from '@solana/web3.js'; import { Button } from 'antd'; import React from 'react'; @@ -11,7 +12,7 @@ import { Link } from 'react-router-dom'; import { LABELS } from '../../constants'; import { usePrice } from '../../contexts/pyth'; import { useBorrowingPower } from '../../hooks'; -import { calculateBorrowAPY, Reserve } from '../../models'; +import { calculateBorrowAPY } from '../../models'; export const MarginTradeItem = (props: { reserve: Reserve; diff --git a/packages/lending/src/views/margin/newPosition/NewPositionForm.tsx b/packages/lending/src/views/margin/newPosition/NewPositionForm.tsx index 9ad4c2ac..205de428 100644 --- a/packages/lending/src/views/margin/newPosition/NewPositionForm.tsx +++ b/packages/lending/src/views/margin/newPosition/NewPositionForm.tsx @@ -1,11 +1,12 @@ import { ArrowDownOutlined } from '@ant-design/icons'; import { components, contexts, ParsedAccount } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { Button, Card } from 'antd'; import React, { useState } from 'react'; import CollateralInput from '../../../components/CollateralInput'; import { LABELS } from '../../../constants'; import { UserDeposit } from '../../../hooks'; -import { Reserve, ReserveParser } from '../../../models'; +import { ReserveParser } from '../../../models'; import { Position } from './interfaces'; import { useLeverage } from './leverage'; import { usePoolAndTradeInfoFrom } from './utils'; diff --git a/packages/lending/src/views/margin/newPosition/interfaces.tsx b/packages/lending/src/views/margin/newPosition/interfaces.tsx index 233aba62..a8fd25d0 100644 --- a/packages/lending/src/views/margin/newPosition/interfaces.tsx +++ b/packages/lending/src/views/margin/newPosition/interfaces.tsx @@ -1,5 +1,5 @@ import { ParsedAccount } from '@oyster/common'; -import { Reserve } from '../../../models'; +import { Reserve } from '@solana/spl-token-lending'; export interface Token { mintAddress: string; diff --git a/packages/lending/src/views/margin/newPosition/utils.ts b/packages/lending/src/views/margin/newPosition/utils.ts index 78d8f6a2..01040c3b 100644 --- a/packages/lending/src/views/margin/newPosition/utils.ts +++ b/packages/lending/src/views/margin/newPosition/utils.ts @@ -1,7 +1,8 @@ import { ParsedAccount } from '@oyster/common'; +import { Reserve } from '@solana/spl-token-lending'; import { useEnrichedPools } from '../../../contexts/market'; import { UserDeposit, useUserDeposits } from '../../../hooks'; -import { PoolInfo, Reserve } from '../../../models'; +import { PoolInfo } from '../../../models'; import { usePoolForBasket } from '../../../utils/pools'; import { Position } from './interfaces'; From dde74ae8898a4c806ba0373494fc82615157d130 Mon Sep 17 00:00:00 2001 From: jordansexton Date: Thu, 1 Jul 2021 20:59:37 -0500 Subject: [PATCH 57/58] more ui refactoring after views removed --- packages/lending/src/hooks/index.ts | 1 - .../lending/src/hooks/useBorrowedAmount.ts | 30 +++++------- .../lending/src/hooks/useUserObligations.ts | 49 ++++++++----------- packages/lending/src/views/home/index.tsx | 2 +- packages/lending/src/views/index.tsx | 4 -- 5 files changed, 32 insertions(+), 54 deletions(-) diff --git a/packages/lending/src/hooks/index.ts b/packages/lending/src/hooks/index.ts index 8f6439f6..bfd998e5 100644 --- a/packages/lending/src/hooks/index.ts +++ b/packages/lending/src/hooks/index.ts @@ -1,7 +1,6 @@ export * from './useBorrowedAmount'; export * from './useBorrowingPower'; export * from './useCollateralBalance'; -export * from './useEnrichedLendingObligations'; export * from './useLendingMarket'; export * from './useLendingObligations'; export * from './useLendingReserves'; diff --git a/packages/lending/src/hooks/useBorrowedAmount.ts b/packages/lending/src/hooks/useBorrowedAmount.ts index 992780e4..afd9b769 100644 --- a/packages/lending/src/hooks/useBorrowedAmount.ts +++ b/packages/lending/src/hooks/useBorrowedAmount.ts @@ -13,7 +13,7 @@ export function useBorrowedAmount(address?: string | PublicKey) { const [borrowedInfo, setBorrowedInfo] = useState({ borrowedLamports: 0, borrowedInUSD: 0, - colateralInUSD: 0, + collateralInUSD: 0, ltv: 0, health: 0, }); @@ -24,43 +24,35 @@ export function useBorrowedAmount(address?: string | PublicKey) { setBorrowedInfo({ borrowedLamports: 0, borrowedInUSD: 0, - colateralInUSD: 0, + collateralInUSD: 0, ltv: 0, health: 0, }); (async () => { const result = { - borrowedLamports: 0, + borrowedLamports: wadToLamports(reserve.info.liquidity.borrowedAmountWads).toNumber(), borrowedInUSD: 0, - colateralInUSD: 0, + collateralInUSD: 0, ltv: 0, health: 0, }; - let liquidationThreshold = 0; + let liquidationThreshold = reserve.info.config.liquidationThreshold; + // @FIXME: see if this requires obligations userObligationsByReserve.forEach(item => { - // @FIXME: support multiple borrows, and decimals may be different than lamports - const borrowedLamports = wadToLamports( - item.obligation.info.borrows[0].borrowedAmountWads, - ).toNumber(); - - // @FIXME: obligation tokens - result.borrowedLamports += borrowedLamports; - result.borrowedInUSD += item.obligation.info.borrowedInQuote; - result.colateralInUSD += item.obligation.info.collateralInQuote; - // @FIXME: BigNumber - liquidationThreshold = item.obligation.info.liquidationThreshold; + result.borrowedInUSD += item.obligation.info.borrowedValue; + result.collateralInUSD += item.obligation.info.depositedValue; }, 0); if (userObligationsByReserve.length === 1) { result.ltv = userObligationsByReserve[0].obligation.info.ltv; result.health = userObligationsByReserve[0].obligation.info.health; } else { - result.ltv = (100 * result.borrowedInUSD) / result.colateralInUSD; + result.ltv = (100 * result.borrowedInUSD) / result.collateralInUSD; result.health = - (result.colateralInUSD * liquidationThreshold) / + (result.collateralInUSD * liquidationThreshold) / 100 / result.borrowedInUSD; result.health = Number.isFinite(result.health) ? result.health : 0; @@ -68,7 +60,7 @@ export function useBorrowedAmount(address?: string | PublicKey) { setBorrowedInfo(result); })(); - }, [connection, userObligationsByReserve, setBorrowedInfo]); + }, [connection, reserve, userObligationsByReserve, setBorrowedInfo]); return { borrowed: fromLamports(borrowedInfo.borrowedLamports, liquidityMint), diff --git a/packages/lending/src/hooks/useUserObligations.ts b/packages/lending/src/hooks/useUserObligations.ts index dbcfd76a..3e24ed76 100644 --- a/packages/lending/src/hooks/useUserObligations.ts +++ b/packages/lending/src/hooks/useUserObligations.ts @@ -1,46 +1,37 @@ -import { hooks, TokenAccount } from '@oyster/common'; +import { useWallet } from '@oyster/common'; import { useMemo } from 'react'; -import { useEnrichedLendingObligations } from './useEnrichedLendingObligations'; - -const { useUserAccounts } = hooks; +import { useLendingObligations } from './useLendingObligations'; export function useUserObligations() { - const { userAccounts } = useUserAccounts(); - const { obligations } = useEnrichedLendingObligations(); - - // @FIXME: obligation tokens were removed, simplify this - const accountsByMint = useMemo(() => { - return userAccounts.reduce((res, acc) => { - const id = acc.info.mint.toBase58(); - res.set(id, [...(res.get(id) || []), acc]); - return res; - }, new Map()); - }, [userAccounts]); + const { wallet } = useWallet(); + const { obligations } = useLendingObligations(); const userObligations = useMemo(() => { - if (accountsByMint.size === 0) { - return []; - } - - // @FIXME: obligation tokens were removed, simplify this return obligations - .map(ob => { - return { - obligation: ob, - userAccounts: [], - }; - }) + .filter( + obligation => + obligation.info.owner.toBase58() === wallet?.publicKey?.toBase58(), + ) + .map(obligation => ({ obligation })) .sort( (a, b) => - b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote, + b.obligation.info.borrowedValue.toNumber() - + a.obligation.info.borrowedValue.toNumber(), ); - }, [accountsByMint, obligations]); + }, [obligations]); return { userObligations, totalInQuote: userObligations.reduce( - (result, item) => result + item.obligation.info.borrowedInQuote, + (result, item) => result + item.obligation.info.borrowedValue.toNumber(), 0, ), }; } + +export const useUserObligation = (address: string) => { + const userObligations = useUserObligations(); + return userObligations.userObligations.find( + obligation => obligation.obligation.pubkey.toBase58() === address, + ); +}; diff --git a/packages/lending/src/views/home/index.tsx b/packages/lending/src/views/home/index.tsx index a7c7efde..3ba09072 100644 --- a/packages/lending/src/views/home/index.tsx +++ b/packages/lending/src/views/home/index.tsx @@ -57,7 +57,7 @@ export const HomeView = () => { marketSize: fromLamports(marketCapLamports, liquidityMint.info) * price, borrowed: fromLamports( - wadToLamports(item.info.liquidity.borrowedAmountWads).toNumber(), + wadToLamports(item.info.liquidity.borrowedAmountWads), liquidityMint.info, ) * price, name: getTokenName(tokenMap, mint), diff --git a/packages/lending/src/views/index.tsx b/packages/lending/src/views/index.tsx index f082c793..f7a0a73a 100644 --- a/packages/lending/src/views/index.tsx +++ b/packages/lending/src/views/index.tsx @@ -1,14 +1,10 @@ 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 { RepayReserveView } from './repayReserve'; -export { LiquidateView } from './liquidate'; -export { LiquidateReserveView } from './liquidateReserve'; export { MarginTrading } from './margin'; export { ObligationsView } from './obligations'; From e8fae316f8b86c40be512a3f3f4aef1146c48a8f Mon Sep 17 00:00:00 2001 From: jordansexton Date: Fri, 2 Jul 2021 11:49:08 -0500 Subject: [PATCH 58/58] more refactoring --- packages/lending/package.json | 2 +- .../src/components/CollateralInput/index.tsx | 4 +- .../components/CollateralSelector/index.tsx | 4 +- packages/lending/src/contexts/lending.tsx | 4 +- packages/lending/src/hooks/index.ts | 4 +- .../lending/src/hooks/useBorrowedAmount.ts | 69 --------- ...endingObligations.ts => useObligations.ts} | 8 +- .../{useLendingReserves.ts => useReserves.ts} | 12 +- packages/lending/src/hooks/useUserDeposits.ts | 4 +- .../src/hooks/useUserObligationByReserve.ts | 46 ------ .../lending/src/hooks/useUserObligations.ts | 13 +- packages/lending/src/types/buffer-layout.d.ts | 88 ----------- packages/lending/src/utils/layout.ts | 137 ------------------ packages/lending/src/utils/utils.ts | 32 +++- packages/lending/src/views/borrow/index.tsx | 4 +- .../lending/src/views/deposit/view/index.tsx | 4 +- .../src/views/depositReserve/index.tsx | 4 +- packages/lending/src/views/home/index.tsx | 4 +- packages/lending/src/views/margin/index.tsx | 4 +- .../src/views/margin/newPosition/index.tsx | 4 +- packages/lending/src/views/reserve/index.tsx | 4 +- packages/lending/src/views/withdraw/index.tsx | 4 +- 22 files changed, 76 insertions(+), 383 deletions(-) delete mode 100644 packages/lending/src/hooks/useBorrowedAmount.ts rename packages/lending/src/hooks/{useLendingObligations.ts => useObligations.ts} (85%) rename packages/lending/src/hooks/{useLendingReserves.ts => useReserves.ts} (87%) delete mode 100644 packages/lending/src/hooks/useUserObligationByReserve.ts delete mode 100644 packages/lending/src/types/buffer-layout.d.ts delete mode 100644 packages/lending/src/utils/layout.ts diff --git a/packages/lending/package.json b/packages/lending/package.json index f5528900..8d730f06 100644 --- a/packages/lending/package.json +++ b/packages/lending/package.json @@ -25,9 +25,9 @@ "@types/testing-library__react": "^10.2.0", "@welldone-software/why-did-you-render": "^6.0.5", "antd": "^4.6.6", + "bignumber.js": "^9.0.1", "bn.js": "^5.1.3", "bs58": "^4.0.1", - "buffer-layout": "^1.2.1", "chart.js": "^2.9.4", "craco-alias": "^2.1.1", "craco-babel-loader": "^0.1.4", diff --git a/packages/lending/src/components/CollateralInput/index.tsx b/packages/lending/src/components/CollateralInput/index.tsx index c73fb86d..d3e14472 100644 --- a/packages/lending/src/components/CollateralInput/index.tsx +++ b/packages/lending/src/components/CollateralInput/index.tsx @@ -10,7 +10,7 @@ import { LendingMarket, Reserve } from '@solana/spl-token-lending'; import { Card, Select } from 'antd'; import React, { useEffect, useState } from 'react'; import { - useLendingReserves, + useReserves, useUserBalance, useUserDeposits, } from '../../hooks'; @@ -41,7 +41,7 @@ export default function CollateralInput(props: { const { balance: tokenBalance } = useUserBalance( props.reserve.liquidity.mintPubkey, ); - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); const { tokenMap } = useConnectionConfig(); const [depositReserve, setCollateralReserve] = useState(); const [balance, setBalance] = useState(0); diff --git a/packages/lending/src/components/CollateralSelector/index.tsx b/packages/lending/src/components/CollateralSelector/index.tsx index 0a97f200..9b7bbc07 100644 --- a/packages/lending/src/components/CollateralSelector/index.tsx +++ b/packages/lending/src/components/CollateralSelector/index.tsx @@ -8,7 +8,7 @@ import { import { LendingMarket, Reserve } from '@solana/spl-token-lending'; import { Select } from 'antd'; import React from 'react'; -import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks'; +import { useReserves, UserDeposit, useUserDeposits } from '../../hooks'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; @@ -42,7 +42,7 @@ export const CollateralSelector = (props: { disabled?: boolean; onCollateralReserve?: (id: string) => void; }) => { - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); const { tokenMap } = useConnectionConfig(); const { userDeposits } = useUserDeposits(); diff --git a/packages/lending/src/contexts/lending.tsx b/packages/lending/src/contexts/lending.tsx index 5af566dd..1eef1a43 100644 --- a/packages/lending/src/contexts/lending.tsx +++ b/packages/lending/src/contexts/lending.tsx @@ -7,7 +7,7 @@ import { } from '@solana/spl-token-lending'; import { AccountInfo, PublicKey } from '@solana/web3.js'; import React, { useCallback, useEffect, useState } from 'react'; -import { useLendingReserves } from '../hooks'; +import { useReserves } from '../hooks'; import { LendingMarketParser, ObligationParser, @@ -38,7 +38,7 @@ export function LendingProvider({ children = null as any }) { export const useLending = () => { const connection = useConnection(); const [lendingAccounts, setLendingAccounts] = useState([]); - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); const precacheMarkets = usePrecacheMarket(); // TODO: query for all the dex from reserves diff --git a/packages/lending/src/hooks/index.ts b/packages/lending/src/hooks/index.ts index bfd998e5..e6d8cef1 100644 --- a/packages/lending/src/hooks/index.ts +++ b/packages/lending/src/hooks/index.ts @@ -2,8 +2,8 @@ export * from './useBorrowedAmount'; export * from './useBorrowingPower'; export * from './useCollateralBalance'; export * from './useLendingMarket'; -export * from './useLendingObligations'; -export * from './useLendingReserves'; +export * from './useObligations'; +export * from './useReserves'; export * from './useSliderInput'; export * from './useUserBalance'; export * from './useUserDeposits'; diff --git a/packages/lending/src/hooks/useBorrowedAmount.ts b/packages/lending/src/hooks/useBorrowedAmount.ts deleted file mode 100644 index afd9b769..00000000 --- a/packages/lending/src/hooks/useBorrowedAmount.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { contexts, fromLamports, wadToLamports } from '@oyster/common'; -import { PublicKey } from '@solana/web3.js'; -import { useEffect, useState } from 'react'; -import { useLendingReserve } from './useLendingReserves'; -import { useUserObligationByReserve } from './useUserObligationByReserve'; - -const { useMint } = contexts.Accounts; -const { useConnection } = contexts.Connection; - -export function useBorrowedAmount(address?: string | PublicKey) { - const connection = useConnection(); - const { userObligationsByReserve } = useUserObligationByReserve(address); - const [borrowedInfo, setBorrowedInfo] = useState({ - borrowedLamports: 0, - borrowedInUSD: 0, - collateralInUSD: 0, - ltv: 0, - health: 0, - }); - const reserve = useLendingReserve(address); - const liquidityMint = useMint(reserve?.info.liquidity.mintPubkey); - - useEffect(() => { - setBorrowedInfo({ - borrowedLamports: 0, - borrowedInUSD: 0, - collateralInUSD: 0, - ltv: 0, - health: 0, - }); - - (async () => { - const result = { - borrowedLamports: wadToLamports(reserve.info.liquidity.borrowedAmountWads).toNumber(), - borrowedInUSD: 0, - collateralInUSD: 0, - ltv: 0, - health: 0, - }; - - let liquidationThreshold = reserve.info.config.liquidationThreshold; - - // @FIXME: see if this requires obligations - userObligationsByReserve.forEach(item => { - result.borrowedInUSD += item.obligation.info.borrowedValue; - result.collateralInUSD += item.obligation.info.depositedValue; - }, 0); - - if (userObligationsByReserve.length === 1) { - result.ltv = userObligationsByReserve[0].obligation.info.ltv; - result.health = userObligationsByReserve[0].obligation.info.health; - } else { - result.ltv = (100 * result.borrowedInUSD) / result.collateralInUSD; - result.health = - (result.collateralInUSD * liquidationThreshold) / - 100 / - result.borrowedInUSD; - result.health = Number.isFinite(result.health) ? result.health : 0; - } - - setBorrowedInfo(result); - })(); - }, [connection, reserve, userObligationsByReserve, setBorrowedInfo]); - - return { - borrowed: fromLamports(borrowedInfo.borrowedLamports, liquidityMint), - ...borrowedInfo, - }; -} diff --git a/packages/lending/src/hooks/useLendingObligations.ts b/packages/lending/src/hooks/useObligations.ts similarity index 85% rename from packages/lending/src/hooks/useLendingObligations.ts rename to packages/lending/src/hooks/useObligations.ts index 07dc218e..7db600a0 100644 --- a/packages/lending/src/hooks/useLendingObligations.ts +++ b/packages/lending/src/hooks/useObligations.ts @@ -6,20 +6,20 @@ import { ObligationParser } from '../models'; const { cache } = contexts.Accounts; -const getLendingObligations = () => { +const getObligations = () => { return cache .byParser(ObligationParser) .map(id => cache.get(id)) .filter(acc => acc !== undefined) as ParsedAccount[]; }; -export function useLendingObligations() { - const [obligations, setObligations] = useState(getLendingObligations()); +export function useObligations() { + const [obligations, setObligations] = useState(getObligations()); useEffect(() => { const dispose = cache.emitter.onCache(args => { if (args.parser === ObligationParser) { - setObligations(getLendingObligations()); + setObligations(getObligations()); } }); diff --git a/packages/lending/src/hooks/useLendingReserves.ts b/packages/lending/src/hooks/useReserves.ts similarity index 87% rename from packages/lending/src/hooks/useLendingReserves.ts rename to packages/lending/src/hooks/useReserves.ts index 6191a4bb..d407d062 100644 --- a/packages/lending/src/hooks/useLendingReserves.ts +++ b/packages/lending/src/hooks/useReserves.ts @@ -8,22 +8,22 @@ import { ReserveParser } from '../models'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; -export const getLendingReserves = () => { +export const getReserves = () => { return cache .byParser(ReserveParser) .map(id => cache.get(id)) .filter(acc => acc !== undefined) as ParsedAccount[]; }; -export function useLendingReserves() { +export function useReserves() { const [reserveAccounts, setReserveAccounts] = useState< ParsedAccount[] - >(getLendingReserves()); + >(getReserves()); useEffect(() => { const dispose = cache.emitter.onCache(args => { if (args.parser === ReserveParser) { - setReserveAccounts(getLendingReserves()); + setReserveAccounts(getReserves()); } }); @@ -37,9 +37,9 @@ export function useLendingReserves() { }; } -export function useLendingReserve(address?: string | PublicKey) { +export function useReserve(address?: string | PublicKey) { const { tokenMap } = useConnectionConfig(); - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); let addressName = address; if (typeof address === 'string') { const token: TokenInfo | null = getTokenByName(tokenMap, address); diff --git a/packages/lending/src/hooks/useUserDeposits.ts b/packages/lending/src/hooks/useUserDeposits.ts index 72fc820c..b44036d1 100644 --- a/packages/lending/src/hooks/useUserDeposits.ts +++ b/packages/lending/src/hooks/useUserDeposits.ts @@ -12,7 +12,7 @@ import { useEffect, useMemo, useState } from 'react'; import { usePyth } from '../contexts/pyth'; import { calculateDepositAPY } from '../models'; import { calculateCollateralBalance } from './useCollateralBalance'; -import { useLendingReserves } from './useLendingReserves'; +import { useReserves } from './useReserves'; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; @@ -31,7 +31,7 @@ export interface UserDeposit { export function useUserDeposits(exclude?: Set, include?: Set) { const { userAccounts } = useUserAccounts(); - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); const [userDeposits, setUserDeposits] = useState([]); const { getPrice } = usePyth(); const { tokenMap } = useConnectionConfig(); diff --git a/packages/lending/src/hooks/useUserObligationByReserve.ts b/packages/lending/src/hooks/useUserObligationByReserve.ts deleted file mode 100644 index 58c4b11e..00000000 --- a/packages/lending/src/hooks/useUserObligationByReserve.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PublicKey } from '@solana/web3.js'; -import { useMemo } from 'react'; -import { useUserObligations } from './useUserObligations'; - -export function useUserObligationByReserve( - borrowReserve?: string | PublicKey, - depositReserve?: string | PublicKey, -) { - const { userObligations } = useUserObligations(); - - const userObligationsByReserve = useMemo(() => { - const borrowReservePubkey = - typeof borrowReserve === 'string' - ? borrowReserve - : borrowReserve?.toBase58(); - const depositReservePubkey = - typeof depositReserve === 'string' - ? depositReserve - : depositReserve?.toBase58(); - // @FIXME: support multiple deposits/borrows - return userObligations.filter(item => { - // @FIXME: borrows and deposits may be empty - if (borrowReservePubkey && depositReservePubkey) { - return ( - item.obligation.info.borrows[0].borrowReserve.toBase58() === - borrowReservePubkey && - item.obligation.info.deposits[0].depositReserve.toBase58() === - depositReservePubkey - ); - } else { - return ( - (borrowReservePubkey && - item.obligation.info.borrows[0].borrowReserve.toBase58() === - borrowReservePubkey) || - (depositReservePubkey && - item.obligation.info.deposits[0].depositReserve.toBase58() === - depositReservePubkey) - ); - } - }); - }, [borrowReserve, depositReserve, userObligations]); - - return { - userObligationsByReserve, - }; -} diff --git a/packages/lending/src/hooks/useUserObligations.ts b/packages/lending/src/hooks/useUserObligations.ts index 3e24ed76..bd62467a 100644 --- a/packages/lending/src/hooks/useUserObligations.ts +++ b/packages/lending/src/hooks/useUserObligations.ts @@ -1,10 +1,10 @@ import { useWallet } from '@oyster/common'; import { useMemo } from 'react'; -import { useLendingObligations } from './useLendingObligations'; +import { useObligations } from './useObligations'; export function useUserObligations() { const { wallet } = useWallet(); - const { obligations } = useLendingObligations(); + const { obligations } = useObligations(); const userObligations = useMemo(() => { return obligations @@ -15,14 +15,17 @@ export function useUserObligations() { .map(obligation => ({ obligation })) .sort( (a, b) => - b.obligation.info.borrowedValue.toNumber() - - a.obligation.info.borrowedValue.toNumber(), + b.obligation.info.borrowedValue.minus(a.obligation.info.borrowedValue).toNumber(), ); }, [obligations]); return { userObligations, - totalInQuote: userObligations.reduce( + totalDepositedValue: userObligations.reduce( + (result, item) => result + item.obligation.info.depositedValue.toNumber(), + 0, + ), + totalBorrowedValue: userObligations.reduce( (result, item) => result + item.obligation.info.borrowedValue.toNumber(), 0, ), diff --git a/packages/lending/src/types/buffer-layout.d.ts b/packages/lending/src/types/buffer-layout.d.ts deleted file mode 100644 index 94dd907a..00000000 --- a/packages/lending/src/types/buffer-layout.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -// https://github.com/project-serum/anchor/blob/master/ts/types/buffer-layout/index.d.ts -declare module 'buffer-layout' { - // TODO: remove `any`. - export class Layout { - span: number; - property?: string; - - constructor(span: number, property?: string); - - decode(b: Buffer, offset?: number): T; - encode(src: T, b: Buffer, offset?: number): number; - getSpan(b: Buffer, offset?: number): number; - replicate(name: string): this; - } - // TODO: remove any. - export class Structure extends Layout { - span: any; - } - export function greedy( - elementSpan?: number, - property?: string, - ): Layout; - export function offset( - layout: Layout, - offset?: number, - property?: string, - ): Layout; - export function u8(property?: string): Layout; - export function u16(property?: string): Layout; - export function u24(property?: string): Layout; - export function u32(property?: string): Layout; - export function u40(property?: string): Layout; - export function u48(property?: string): Layout; - export function nu64(property?: string): Layout; - export function u16be(property?: string): Layout; - export function u24be(property?: string): Layout; - export function u32be(property?: string): Layout; - export function u40be(property?: string): Layout; - export function u48be(property?: string): Layout; - export function nu64be(property?: string): Layout; - export function s8(property?: string): Layout; - export function s16(property?: string): Layout; - export function s24(property?: string): Layout; - export function s32(property?: string): Layout; - export function s40(property?: string): Layout; - export function s48(property?: string): Layout; - export function ns64(property?: string): Layout; - export function s16be(property?: string): Layout; - export function s24be(property?: string): Layout; - export function s32be(property?: string): Layout; - export function s40be(property?: string): Layout; - export function s48be(property?: string): Layout; - export function ns64be(property?: string): Layout; - export function f32(property?: string): Layout; - export function f32be(property?: string): Layout; - export function f64(property?: string): Layout; - export function f64be(property?: string): Layout; - export function struct( - fields: Layout[], - property?: string, - decodePrefixes?: boolean, - ): Layout; - export function bits( - word: Layout, - msb?: boolean, - property?: string, - ): any; - export function seq( - elementLayout: Layout, - count: number | Layout, - property?: string, - ): Layout; - export function union( - discr: Layout, - defaultLayout?: any, - property?: string, - ): any; - export function unionLayoutDiscriminator( - layout: Layout, - property?: string, - ): any; - export function blob( - length: number | Layout, - property?: string, - ): Layout; - export function cstr(property?: string): Layout; - export function utf8(maxSpan: number, property?: string): Layout; -} diff --git a/packages/lending/src/utils/layout.ts b/packages/lending/src/utils/layout.ts deleted file mode 100644 index e1e926d7..00000000 --- a/packages/lending/src/utils/layout.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { PublicKey } from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; - -/** - * Layout for a public key - */ -export const publicKey = (property = 'publicKey') => { - const layout = BufferLayout.blob(32, property); - - const _decode = layout.decode.bind(layout); - const _encode = layout.encode.bind(layout); - - const publicKeyLayout = layout as BufferLayout.Layout as BufferLayout.Layout; - - publicKeyLayout.decode = (buffer: Buffer, offset: number) => { - const data = _decode(buffer, offset); - return new PublicKey(data); - }; - - publicKeyLayout.encode = (key: PublicKey, buffer: Buffer, offset: number) => { - return _encode(key.toBuffer(), buffer, offset); - }; - - return publicKeyLayout; -}; - -/** - * Layout for a 64bit unsigned value - */ -export const uint64 = (property = 'uint64') => { - const layout = BufferLayout.blob(8, property); - - const _decode = layout.decode.bind(layout); - const _encode = layout.encode.bind(layout); - - const bnLayout = layout as BufferLayout.Layout as BufferLayout.Layout; - - bnLayout.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, - ); - }; - - bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => { - const a = num.toArray().reverse(); - let b = Buffer.from(a); - if (b.length !== 8) { - const zeroPad = Buffer.alloc(8); - b.copy(zeroPad); - b = zeroPad; - } - return _encode(b, buffer, offset); - }; - - return bnLayout; -}; - -// TODO: wrap in BN (what about decimals?) -export const uint128 = (property = 'uint128') => { - const layout = BufferLayout.blob(16, property); - - const _decode = layout.decode.bind(layout); - const _encode = layout.encode.bind(layout); - - const bnLayout = layout as BufferLayout.Layout as BufferLayout.Layout; - - bnLayout.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, - ); - }; - - bnLayout.encode = (num: BN, buffer: Buffer, offset: number) => { - const a = num.toArray().reverse(); - let b = Buffer.from(a); - if (b.length !== 16) { - const zeroPad = Buffer.alloc(16); - b.copy(zeroPad); - b = zeroPad; - } - - return _encode(b, buffer, offset); - }; - - return bnLayout; -}; - -interface RustString { - length: number; - lengthPadding: number; - chars: Buffer; -} - -/** - * Layout for a Rust String type - */ -export const rustString = (property = 'string') => { - const layout = BufferLayout.struct( - [ - BufferLayout.u32('length'), - BufferLayout.u32('lengthPadding'), - BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'), - ], - property, - ); - - const _decode = layout.decode.bind(layout); - const _encode = layout.encode.bind(layout); - - const stringLayout = layout as BufferLayout.Layout as BufferLayout.Layout; - - stringLayout.decode = (buffer: Buffer, offset: number) => { - const data = _decode(buffer, offset); - return data.chars.toString('utf8'); - }; - - stringLayout.encode = (str: string, buffer: Buffer, offset: number) => { - // @TODO: does this need length/padding? - const data = { - chars: Buffer.from(str, 'utf8'), - } as RustString; - return _encode(data, buffer, offset); - }; - - return stringLayout; -}; diff --git a/packages/lending/src/utils/utils.ts b/packages/lending/src/utils/utils.ts index b7ec20c8..e865466e 100644 --- a/packages/lending/src/utils/utils.ts +++ b/packages/lending/src/utils/utils.ts @@ -1,6 +1,11 @@ -import { KnownTokenMap, utils } from '@oyster/common'; +import { KnownTokenMap, TokenAccount, utils } from '@oyster/common'; +import { MintInfo } from '@solana/spl-token'; +import BigNumber from 'bignumber.js'; import { PoolInfo } from '../models'; +const ZERO = new BigNumber(0); +const WAD = new BigNumber('1e+18'); + export function getPoolName( map: KnownTokenMap, pool: PoolInfo, @@ -9,3 +14,28 @@ export function getPoolName( const sorted = pool.pubkeys.holdingMints.map(a => a.toBase58()).sort(); return sorted.map(item => utils.getTokenName(map, item, shorten)).join('/'); } + +export function wadToLamports(amount?: BigNumber): BigNumber { + return amount?.div(WAD) || ZERO; +} + +export function fromLamports( + account?: TokenAccount | number | BigNumber, + mint?: MintInfo, + rate: number = 1.0, +): number { + if (!account) { + return 0; + } + + const amount = Math.floor( + typeof account === 'number' + ? account + : BigNumber.isBigNumber(account) + ? account.toNumber() + : account.info.amount.toNumber(), + ); + + const precision = Math.pow(10, mint?.decimals || 0); + return (amount / precision) * rate; +} diff --git a/packages/lending/src/views/borrow/index.tsx b/packages/lending/src/views/borrow/index.tsx index 0000d8be..a4330cd2 100644 --- a/packages/lending/src/views/borrow/index.tsx +++ b/packages/lending/src/views/borrow/index.tsx @@ -1,12 +1,12 @@ import { Card } from 'antd'; import React from 'react'; import { LABELS } from '../../constants'; -import { useLendingReserves } from '../../hooks'; +import { useReserves } from '../../hooks'; import { BorrowItem } from './item'; import './itemStyle.less'; export const BorrowView = () => { - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); return (
diff --git a/packages/lending/src/views/deposit/view/index.tsx b/packages/lending/src/views/deposit/view/index.tsx index d89e3cf7..5dfd9e03 100644 --- a/packages/lending/src/views/deposit/view/index.tsx +++ b/packages/lending/src/views/deposit/view/index.tsx @@ -1,11 +1,11 @@ import { Card } from 'antd'; import React from 'react'; -import { useLendingReserves } from '../../../hooks'; +import { useReserves } from '../../../hooks'; import { ReserveItem } from './item'; import './itemStyle.less'; export const DepositView = () => { - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); return (
diff --git a/packages/lending/src/views/depositReserve/index.tsx b/packages/lending/src/views/depositReserve/index.tsx index 5bee0e1e..cc55e2f8 100644 --- a/packages/lending/src/views/depositReserve/index.tsx +++ b/packages/lending/src/views/depositReserve/index.tsx @@ -9,12 +9,12 @@ import { SideReserveOverviewMode, } from '../../components/SideReserveOverview'; import { GUTTER } from '../../constants'; -import { useLendingReserve } from '../../hooks'; +import { useReserve } from '../../hooks'; import './style.less'; export const DepositReserveView = () => { const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); + const lendingReserve = useReserve(id); const reserve = lendingReserve?.info; if (!reserve || !lendingReserve) { diff --git a/packages/lending/src/views/home/index.tsx b/packages/lending/src/views/home/index.tsx index 3ba09072..c1455a69 100644 --- a/packages/lending/src/views/home/index.tsx +++ b/packages/lending/src/views/home/index.tsx @@ -10,7 +10,7 @@ import { Card, Col, Row, Statistic } from 'antd'; import React, { useEffect, useState } from 'react'; import { GUTTER, LABELS } from '../../constants'; import { usePyth } from '../../contexts/pyth'; -import { useLendingReserves } from '../../hooks'; +import { useReserves } from '../../hooks'; import { reserveMarketCap, Totals } from '../../models'; import { BarChartStatistic } from './../../components/BarChartStatistic'; import { LendingReserveItem } from './item'; @@ -20,7 +20,7 @@ const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; export const HomeView = () => { - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); const { getPrice } = usePyth(); const { tokenMap } = useConnectionConfig(); const [totals, setTotals] = useState({ diff --git a/packages/lending/src/views/margin/index.tsx b/packages/lending/src/views/margin/index.tsx index 69fea976..14713f12 100644 --- a/packages/lending/src/views/margin/index.tsx +++ b/packages/lending/src/views/margin/index.tsx @@ -1,12 +1,12 @@ import { Card } from 'antd'; import React from 'react'; import { LABELS } from '../../constants'; -import { useLendingReserves } from '../../hooks/useLendingReserves'; +import { useReserves } from '../../hooks/useReserves'; import { MarginTradeItem } from './item'; import './itemStyle.less'; export const MarginTrading = () => { - const { reserveAccounts } = useLendingReserves(); + const { reserveAccounts } = useReserves(); return (
diff --git a/packages/lending/src/views/margin/newPosition/index.tsx b/packages/lending/src/views/margin/newPosition/index.tsx index 8b02fb11..d9341c0a 100644 --- a/packages/lending/src/views/margin/newPosition/index.tsx +++ b/packages/lending/src/views/margin/newPosition/index.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { useLendingReserve } from '../../../hooks'; +import { useReserve } from '../../../hooks'; import Breakdown from './Breakdown'; import { Position } from './interfaces'; @@ -10,7 +10,7 @@ import './style.less'; export const NewPosition = () => { const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); + const lendingReserve = useReserve(id); const [newPosition, setNewPosition] = useState({ id: null, leverage: 5, diff --git a/packages/lending/src/views/reserve/index.tsx b/packages/lending/src/views/reserve/index.tsx index 820aac35..4b5b0229 100644 --- a/packages/lending/src/views/reserve/index.tsx +++ b/packages/lending/src/views/reserve/index.tsx @@ -4,12 +4,12 @@ import { useParams } from 'react-router-dom'; import { ReserveStatus } from '../../components/ReserveStatus'; import { UserLendingCard } from '../../components/UserLendingCard'; import { GUTTER } from '../../constants'; -import { useLendingReserve } from '../../hooks'; +import { useReserve } from '../../hooks'; import './style.less'; export const ReserveView = () => { const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); + const lendingReserve = useReserve(id); const reserve = lendingReserve?.info; if (!reserve || !lendingReserve) { diff --git a/packages/lending/src/views/withdraw/index.tsx b/packages/lending/src/views/withdraw/index.tsx index 656eb462..c95b15ce 100644 --- a/packages/lending/src/views/withdraw/index.tsx +++ b/packages/lending/src/views/withdraw/index.tsx @@ -7,12 +7,12 @@ import { } from '../../components/SideReserveOverview'; import { WithdrawInput } from '../../components/WithdrawInput'; -import { useLendingReserve } from '../../hooks'; +import { useReserve } from '../../hooks'; import './style.less'; export const WithdrawView = () => { const { id } = useParams<{ id: string }>(); - const lendingReserve = useLendingReserve(id); + const lendingReserve = useReserve(id); const reserve = lendingReserve?.info; if (!reserve || !lendingReserve) {