Intermediary step where ive gotten everything at least green and moved to common and linked
This commit is contained in:
parent
27c62ce9d3
commit
7e2749b9bc
|
@ -2,7 +2,12 @@
|
|||
|
||||
# dependencies
|
||||
*/node_modules
|
||||
.yarn/
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
"**/.pnp.*": true
|
||||
},
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"prettier.prettierPath": ".vscode/pnpify/prettier/index.js"
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
|||
# This file is automatically generated by PnPify.
|
||||
# Manual changes will be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsc
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsc your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsc`);
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsserver
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsserver your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsserver`);
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsc.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/tsc.js`);
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes is much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = pnpApi.resolveVirtual(str);
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = str.replace(/\\/g, `/`)
|
||||
str = str.replace(/^\/?/, `/`);
|
||||
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
if (str.match(/\.zip\//)) {
|
||||
str = `${isVSCode ? `^` : ``}zip:${str}`;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?zip:\//, ``)
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
}
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let isVSCode = false;
|
||||
|
||||
return Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
parsedMessage.arguments.hostInfo === `vscode`
|
||||
) {
|
||||
isVSCode = true;
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||
}));
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/typescript.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "typescript",
|
||||
"version": "4.1.3-pnpify",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
|
@ -32,8 +32,7 @@
|
|||
"react-github-btn": "^1.2.0",
|
||||
"react-intl": "^5.10.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.3",
|
||||
"typescript": "^4.0.0"
|
||||
"react-scripts": "3.4.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
|
||||
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from '../utils/ids';
|
||||
import { LendingObligationLayout, TokenAccount } from '../models';
|
||||
import { cache, TokenAccountParser } from './../contexts/accounts';
|
||||
import { TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from '../utils/ids';
|
||||
import { TokenAccount } from '../models/account';
|
||||
import { cache, TokenAccountParser } from '../contexts/accounts';
|
||||
|
||||
export function ensureSplAccount(
|
||||
instructions: TransactionInstruction[],
|
||||
|
@ -51,28 +51,6 @@ export function createTempMemoryAccount(
|
|||
return account.publicKey;
|
||||
}
|
||||
|
||||
export function createUninitializedObligation(
|
||||
instructions: TransactionInstruction[],
|
||||
payer: PublicKey,
|
||||
amount: number,
|
||||
signers: Account[]
|
||||
) {
|
||||
const account = new Account();
|
||||
instructions.push(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: payer,
|
||||
newAccountPubkey: account.publicKey,
|
||||
lamports: amount,
|
||||
space: LendingObligationLayout.span,
|
||||
programId: LENDING_PROGRAM_ID,
|
||||
})
|
||||
);
|
||||
|
||||
signers.push(account);
|
||||
|
||||
return account.publicKey;
|
||||
}
|
||||
|
||||
export function createUninitializedMint(
|
||||
instructions: TransactionInstruction[],
|
||||
payer: PublicKey,
|
|
@ -0,0 +1 @@
|
|||
export * from './account';
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { Typography } from "antd";
|
||||
import { shortenAddress } from "../../utils/utils";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import React from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import { shortenAddress } from '../../utils/utils';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const ExplorerLink = (props: {
|
||||
address: string | PublicKey;
|
||||
|
@ -12,10 +12,7 @@ export const ExplorerLink = (props: {
|
|||
}) => {
|
||||
const { type, code } = props;
|
||||
|
||||
const address =
|
||||
typeof props.address === "string"
|
||||
? props.address
|
||||
: props.address?.toBase58();
|
||||
const address = typeof props.address === 'string' ? props.address : props.address?.toBase58();
|
||||
|
||||
if (!address) {
|
||||
return null;
|
||||
|
@ -27,7 +24,7 @@ export const ExplorerLink = (props: {
|
|||
<a
|
||||
href={`https://explorer.solana.com/${type}/${address}`}
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
target="_blank"
|
||||
target='_blank'
|
||||
title={address}
|
||||
style={props.style}
|
||||
>
|
|
@ -0,0 +1 @@
|
|||
export * from './math';
|
|
@ -1,23 +1,13 @@
|
|||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useConnection } from "./connection";
|
||||
import { useWallet } from "./wallet";
|
||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||
import { AccountLayout, u64, MintInfo, MintLayout } from "@solana/spl-token";
|
||||
import { PoolInfo, TokenAccount } from "./../models";
|
||||
import { chunks } from "./../utils/utils";
|
||||
import { EventEmitter } from "./../utils/eventEmitter";
|
||||
import { useUserAccounts } from "../hooks/useUserAccounts";
|
||||
import {
|
||||
WRAPPED_SOL_MINT,
|
||||
programIds,
|
||||
LEND_HOST_FEE_ADDRESS,
|
||||
} from "../utils/ids";
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useConnection } from '../contexts/connection';
|
||||
import { useWallet } from '../contexts/wallet';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { AccountLayout, u64, MintInfo, MintLayout } from '@solana/spl-token';
|
||||
import { TokenAccount } from '../models';
|
||||
import { chunks } from '../utils/utils';
|
||||
import { EventEmitter } from '../utils/eventEmitter';
|
||||
import { useUserAccounts } from '../hooks/useUserAccounts';
|
||||
import { WRAPPED_SOL_MINT, programIds, LEND_HOST_FEE_ADDRESS } from 'common/src/utils/ids';
|
||||
|
||||
const AccountsContext = React.createContext<any>(null);
|
||||
|
||||
|
@ -32,10 +22,7 @@ export interface ParsedAccountBase {
|
|||
info: any; // TODO: change to unkown
|
||||
}
|
||||
|
||||
export type AccountParser = (
|
||||
pubkey: PublicKey,
|
||||
data: AccountInfo<Buffer>
|
||||
) => ParsedAccountBase | undefined;
|
||||
export type AccountParser = (pubkey: PublicKey, data: AccountInfo<Buffer>) => ParsedAccountBase | undefined;
|
||||
|
||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||
info: T;
|
||||
|
@ -44,7 +31,7 @@ export interface ParsedAccount<T> extends ParsedAccountBase {
|
|||
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
||||
const info = await connection.getAccountInfo(pubKey);
|
||||
if (info === null) {
|
||||
throw new Error("Failed to find mint account");
|
||||
throw new Error('Failed to find mint account');
|
||||
}
|
||||
|
||||
const data = Buffer.from(info.data);
|
||||
|
@ -68,10 +55,7 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
|||
return details;
|
||||
};
|
||||
|
||||
export const TokenAccountParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = deserializeAccount(buffer);
|
||||
|
||||
|
@ -86,10 +70,7 @@ export const TokenAccountParser = (
|
|||
return details;
|
||||
};
|
||||
|
||||
export const GenericAccountParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const GenericAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const details = {
|
||||
|
@ -107,13 +88,9 @@ export const keyToAccountParser = new Map<string, AccountParser>();
|
|||
|
||||
export const cache = {
|
||||
emitter: new EventEmitter(),
|
||||
query: async (
|
||||
connection: Connection,
|
||||
pubKey: string | PublicKey,
|
||||
parser?: AccountParser
|
||||
) => {
|
||||
query: async (connection: Connection, pubKey: string | PublicKey, parser?: AccountParser) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === "string") {
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
|
@ -134,7 +111,7 @@ export const cache = {
|
|||
// TODO: refactor to use multiple accounts query with flush like behavior
|
||||
query = connection.getAccountInfo(id).then((data) => {
|
||||
if (!data) {
|
||||
throw new Error("Account not found");
|
||||
throw new Error('Account not found');
|
||||
}
|
||||
|
||||
return cache.add(id, data, parser);
|
||||
|
@ -143,21 +120,15 @@ export const cache = {
|
|||
|
||||
return query;
|
||||
},
|
||||
add: (
|
||||
id: PublicKey | string,
|
||||
obj: AccountInfo<Buffer>,
|
||||
parser?: AccountParser
|
||||
) => {
|
||||
add: (id: PublicKey | string, obj: AccountInfo<Buffer>, parser?: AccountParser) => {
|
||||
if (obj.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const address = typeof id === "string" ? id : id?.toBase58();
|
||||
const address = typeof id === 'string' ? id : id?.toBase58();
|
||||
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
||||
if (!deserialize) {
|
||||
throw new Error(
|
||||
"Deserializer needs to be registered or passed as a parameter"
|
||||
);
|
||||
throw new Error('Deserializer needs to be registered or passed as a parameter');
|
||||
}
|
||||
|
||||
cache.registerParser(id, deserialize);
|
||||
|
@ -175,7 +146,7 @@ export const cache = {
|
|||
},
|
||||
get: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== "string") {
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -185,7 +156,7 @@ export const cache = {
|
|||
},
|
||||
delete: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== "string") {
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -211,7 +182,7 @@ export const cache = {
|
|||
},
|
||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||
if (pubkey) {
|
||||
const address = typeof pubkey === "string" ? pubkey : pubkey?.toBase58();
|
||||
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||
keyToAccountParser.set(address, parser);
|
||||
}
|
||||
|
||||
|
@ -219,7 +190,7 @@ export const cache = {
|
|||
},
|
||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === "string") {
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
|
@ -248,7 +219,7 @@ export const cache = {
|
|||
},
|
||||
getMint: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== "string") {
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -270,10 +241,7 @@ export const useAccountsContext = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
function wrapNativeAccount(
|
||||
pubkey: PublicKey,
|
||||
account?: AccountInfo<Buffer>
|
||||
): TokenAccount | undefined {
|
||||
function wrapNativeAccount(pubkey: PublicKey, account?: AccountInfo<Buffer>): TokenAccount | undefined {
|
||||
if (!account) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -296,22 +264,7 @@ function wrapNativeAccount(
|
|||
};
|
||||
}
|
||||
|
||||
export function useCachedPool(legacy = false) {
|
||||
const context = useContext(AccountsContext);
|
||||
|
||||
const allPools = context.pools as PoolInfo[];
|
||||
const pools = useMemo(() => {
|
||||
return allPools.filter((p) => p.legacy === legacy);
|
||||
}, [allPools, legacy]);
|
||||
|
||||
return {
|
||||
pools,
|
||||
};
|
||||
}
|
||||
|
||||
export const getCachedAccount = (
|
||||
predicate: (account: TokenAccount) => boolean
|
||||
) => {
|
||||
export const getCachedAccount = (predicate: (account: TokenAccount) => boolean) => {
|
||||
for (const account of genericCache.values()) {
|
||||
if (predicate(account)) {
|
||||
return account as TokenAccount;
|
||||
|
@ -361,10 +314,7 @@ const UseNativeAccount = () => {
|
|||
};
|
||||
|
||||
const PRECACHED_OWNERS = new Set<string>();
|
||||
const precacheUserTokenAccounts = async (
|
||||
connection: Connection,
|
||||
owner?: PublicKey
|
||||
) => {
|
||||
const precacheUserTokenAccounts = async (connection: Connection, owner?: PublicKey) => {
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
@ -392,16 +342,12 @@ export function AccountsProvider({ children = null as any }) {
|
|||
return cache
|
||||
.byParser(TokenAccountParser)
|
||||
.map((id) => cache.get(id))
|
||||
.filter(
|
||||
(a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58()
|
||||
)
|
||||
.filter((a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58())
|
||||
.map((a) => a as TokenAccount);
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = selectUserAccounts().filter(
|
||||
(a) => a !== undefined
|
||||
) as TokenAccount[];
|
||||
const accounts = selectUserAccounts().filter((a) => a !== undefined) as TokenAccount[];
|
||||
setUserAccounts(accounts);
|
||||
}, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
|
||||
|
||||
|
@ -451,7 +397,7 @@ export function AccountsProvider({ children = null as any }) {
|
|||
}
|
||||
}
|
||||
},
|
||||
"singleGossip"
|
||||
'singleGossip'
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
@ -479,15 +425,9 @@ export function useNativeAccount() {
|
|||
};
|
||||
}
|
||||
|
||||
export const getMultipleAccounts = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string
|
||||
) => {
|
||||
export const getMultipleAccounts = async (connection: any, keys: string[], commitment: string) => {
|
||||
const result = await Promise.all(
|
||||
chunks(keys, 99).map((chunk) =>
|
||||
getMultipleAccountsCore(connection, chunk, commitment)
|
||||
)
|
||||
chunks(keys, 99).map((chunk) => getMultipleAccountsCore(connection, chunk, commitment))
|
||||
);
|
||||
|
||||
const array = result
|
||||
|
@ -502,7 +442,7 @@ export const getMultipleAccounts = async (
|
|||
const { data, ...rest } = acc;
|
||||
const obj = {
|
||||
...rest,
|
||||
data: Buffer.from(data[0], "base64"),
|
||||
data: Buffer.from(data[0], 'base64'),
|
||||
} as AccountInfo<Buffer>;
|
||||
return obj;
|
||||
})
|
||||
|
@ -512,18 +452,12 @@ export const getMultipleAccounts = async (
|
|||
return { keys, array };
|
||||
};
|
||||
|
||||
const getMultipleAccountsCore = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string
|
||||
) => {
|
||||
const args = connection._buildArgs([keys], commitment, "base64");
|
||||
const getMultipleAccountsCore = async (connection: any, keys: string[], commitment: string) => {
|
||||
const args = connection._buildArgs([keys], commitment, 'base64');
|
||||
|
||||
const unsafeRes = await connection._rpcRequest("getMultipleAccounts", args);
|
||||
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
|
||||
if (unsafeRes.error) {
|
||||
throw new Error(
|
||||
"failed to get info about account " + unsafeRes.error.message
|
||||
);
|
||||
throw new Error('failed to get info about account ' + unsafeRes.error.message);
|
||||
}
|
||||
|
||||
if (unsafeRes.result.value) {
|
||||
|
@ -539,7 +473,7 @@ export function useMint(key?: string | PublicKey) {
|
|||
const connection = useConnection();
|
||||
const [mint, setMint] = useState<MintInfo>();
|
||||
|
||||
const id = typeof key === "string" ? key : key?.toBase58();
|
||||
const id = typeof key === 'string' ? key : key?.toBase58();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
|
@ -554,9 +488,7 @@ export function useMint(key?: string | PublicKey) {
|
|||
const dispose = cache.emitter.onCache((e) => {
|
||||
const event = e;
|
||||
if (event.id === id) {
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then((mint) => setMint(mint.info as any));
|
||||
cache.query(connection, id, MintParser).then((mint) => setMint(mint.info as any));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
|
@ -569,9 +501,7 @@ export function useMint(key?: string | PublicKey) {
|
|||
|
||||
export const useAccountByMint = (mint: string) => {
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const index = userAccounts.findIndex(
|
||||
(acc) => acc.info.mint.toBase58() === mint
|
||||
);
|
||||
const index = userAccounts.findIndex((acc) => acc.info.mint.toBase58() === mint);
|
||||
|
||||
if (index !== -1) {
|
||||
return userAccounts[index];
|
||||
|
@ -592,9 +522,7 @@ export function useAccount(pubKey?: PublicKey) {
|
|||
return;
|
||||
}
|
||||
|
||||
const acc = await cache
|
||||
.query(connection, key, TokenAccountParser)
|
||||
.catch((err) => console.log(err));
|
||||
const acc = await cache.query(connection, key, TokenAccountParser).catch((err) => console.log(err));
|
||||
if (acc) {
|
||||
setAccount(acc);
|
||||
}
|
||||
|
@ -657,7 +585,7 @@ const deserializeAccount = (data: Buffer) => {
|
|||
// TODO: expose in spl package
|
||||
const deserializeMint = (data: Buffer) => {
|
||||
if (data.length !== MintLayout.span) {
|
||||
throw new Error("Not a valid Mint");
|
||||
throw new Error('Not a valid Mint');
|
||||
}
|
||||
|
||||
const mintInfo = MintLayout.decode(data);
|
|
@ -1,40 +1,29 @@
|
|||
import { KnownToken, useLocalStorageState } from "./../utils/utils";
|
||||
import {
|
||||
Account,
|
||||
clusterApiUrl,
|
||||
Connection,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { notify } from "./../utils/notifications";
|
||||
import { ExplorerLink } from "../components/ExplorerLink";
|
||||
import LocalTokens from "../config/tokens.json";
|
||||
import { setProgramIds } from "../utils/ids";
|
||||
import { KnownToken, useLocalStorageState } from '../utils/utils';
|
||||
import { Account, clusterApiUrl, Connection, Transaction, TransactionInstruction } from '@solana/web3.js';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { notify } from '../utils/notifications';
|
||||
import { ExplorerLink } from '../components/ExplorerLink';
|
||||
import LocalTokens from '../config/tokens.json';
|
||||
import { setProgramIds } from 'common/src/utils/ids';
|
||||
|
||||
export type ENV =
|
||||
| "mainnet-beta"
|
||||
| "testnet"
|
||||
| "devnet"
|
||||
| "localnet"
|
||||
| "lending";
|
||||
export type ENV = 'mainnet-beta' | 'testnet' | 'devnet' | 'localnet' | 'lending';
|
||||
|
||||
export const ENDPOINTS = [
|
||||
{
|
||||
name: "mainnet-beta" as ENV,
|
||||
endpoint: "https://solana-api.projectserum.com/",
|
||||
name: 'mainnet-beta' as ENV,
|
||||
endpoint: 'https://solana-api.projectserum.com/',
|
||||
},
|
||||
{
|
||||
name: "Oyster Dev" as ENV,
|
||||
endpoint: "http://oyster-dev.solana.com/",
|
||||
name: 'Oyster Dev' as ENV,
|
||||
endpoint: 'http://oyster-dev.solana.com/',
|
||||
},
|
||||
{
|
||||
name: "Lending" as ENV,
|
||||
endpoint: "https://tln.solana.com/",
|
||||
name: 'Lending' as ENV,
|
||||
endpoint: 'https://tln.solana.com/',
|
||||
},
|
||||
{ name: "testnet" as ENV, endpoint: clusterApiUrl("testnet") },
|
||||
{ name: "devnet" as ENV, endpoint: clusterApiUrl("devnet") },
|
||||
{ name: "localnet" as ENV, endpoint: "http://127.0.0.1:8899" },
|
||||
{ name: 'testnet' as ENV, endpoint: clusterApiUrl('testnet') },
|
||||
{ name: 'devnet' as ENV, endpoint: clusterApiUrl('devnet') },
|
||||
{ name: 'localnet' as ENV, endpoint: 'http://127.0.0.1:8899' },
|
||||
];
|
||||
|
||||
const DEFAULT = ENDPOINTS[0].endpoint;
|
||||
|
@ -57,43 +46,29 @@ const ConnectionContext = React.createContext<ConnectionConfig>({
|
|||
setEndpoint: () => {},
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
setSlippage: (val: number) => {},
|
||||
connection: new Connection(DEFAULT, "recent"),
|
||||
sendConnection: new Connection(DEFAULT, "recent"),
|
||||
connection: new Connection(DEFAULT, 'recent'),
|
||||
sendConnection: new Connection(DEFAULT, 'recent'),
|
||||
env: ENDPOINTS[0].name,
|
||||
tokens: [],
|
||||
tokenMap: new Map<string, KnownToken>(),
|
||||
});
|
||||
|
||||
export function ConnectionProvider({ children = undefined as any }) {
|
||||
const [endpoint, setEndpoint] = useLocalStorageState(
|
||||
"connectionEndpts",
|
||||
ENDPOINTS[0].endpoint
|
||||
);
|
||||
const [endpoint, setEndpoint] = useLocalStorageState('connectionEndpts', ENDPOINTS[0].endpoint);
|
||||
|
||||
const [slippage, setSlippage] = useLocalStorageState(
|
||||
"slippage",
|
||||
DEFAULT_SLIPPAGE.toString()
|
||||
);
|
||||
const [slippage, setSlippage] = useLocalStorageState('slippage', 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;
|
||||
const env = ENDPOINTS.find((end) => end.endpoint === endpoint)?.name || ENDPOINTS[0].name;
|
||||
|
||||
const [tokens, setTokens] = useState<KnownToken[]>([]);
|
||||
const [tokenMap, setTokenMap] = useState<Map<string, KnownToken>>(new Map());
|
||||
useEffect(() => {
|
||||
// fetch token files
|
||||
window
|
||||
.fetch(
|
||||
`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`
|
||||
)
|
||||
.fetch(`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`)
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
|
@ -129,10 +104,7 @@ export function ConnectionProvider({ children = undefined as any }) {
|
|||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
const id = sendConnection.onAccountChange(
|
||||
new Account().publicKey,
|
||||
() => {}
|
||||
);
|
||||
const id = sendConnection.onAccountChange(new Account().publicKey, () => {});
|
||||
return () => {
|
||||
sendConnection.removeAccountChangeListener(id);
|
||||
};
|
||||
|
@ -190,7 +162,7 @@ export function useSlippageConfig() {
|
|||
|
||||
const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
||||
// wait for all confirmation before geting transaction
|
||||
await connection.confirmTransaction(txid, "max");
|
||||
await connection.confirmTransaction(txid, 'max');
|
||||
|
||||
const tx = await connection.getParsedConfirmedTransaction(txid);
|
||||
|
||||
|
@ -224,9 +196,7 @@ export const sendTransaction = async (
|
|||
) => {
|
||||
let transaction = new Transaction();
|
||||
instructions.forEach((instruction) => transaction.add(instruction));
|
||||
transaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash("max")
|
||||
).blockhash;
|
||||
transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash;
|
||||
transaction.setSigners(
|
||||
// fee payied by the wallet owner
|
||||
wallet.publicKey,
|
||||
|
@ -239,37 +209,30 @@ export const sendTransaction = async (
|
|||
const rawTransaction = transaction.serialize();
|
||||
let options = {
|
||||
skipPreflight: true,
|
||||
commitment: "singleGossip",
|
||||
commitment: 'singleGossip',
|
||||
};
|
||||
|
||||
const txid = await connection.sendRawTransaction(rawTransaction, options);
|
||||
|
||||
if (awaitConfirmation) {
|
||||
const status = (
|
||||
await connection.confirmTransaction(
|
||||
txid,
|
||||
options && (options.commitment as any)
|
||||
)
|
||||
).value;
|
||||
const status = (await connection.confirmTransaction(txid, options && (options.commitment as any))).value;
|
||||
|
||||
if (status?.err) {
|
||||
const errors = await getErrorForTransaction(connection, txid);
|
||||
notify({
|
||||
message: "Transaction failed...",
|
||||
message: 'Transaction failed...',
|
||||
description: (
|
||||
<>
|
||||
{errors.map((err) => (
|
||||
<div>{err}</div>
|
||||
))}
|
||||
<ExplorerLink address={txid} type="transaction" />
|
||||
<ExplorerLink address={txid} type='transaction' />
|
||||
</>
|
||||
),
|
||||
type: "error",
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
throw new Error(
|
||||
`Raw transaction ${txid} failed (${JSON.stringify(status)})`
|
||||
);
|
||||
throw new Error(`Raw transaction ${txid} failed (${JSON.stringify(status)})`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,24 @@
|
|||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { notify } from "./../utils/notifications";
|
||||
import { useConnectionConfig } from "./connection";
|
||||
import { useLocalStorageState } from "./../utils/utils";
|
||||
import { SolongAdapter } from "./../wallet-adapters/solong_adapter";
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import Wallet from '@project-serum/sol-wallet-adapter';
|
||||
import { notify } from '../utils/notifications';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { useLocalStorageState } from '../utils/utils';
|
||||
import { SolongAdapter } from '../wallet-adapters/solong_adapter';
|
||||
|
||||
export const WALLET_PROVIDERS = [
|
||||
{ name: "sollet.io", url: "https://www.sollet.io" },
|
||||
{ name: "solongwallet.com", url: "http://solongwallet.com" },
|
||||
{ name: "solflare.com", url: "https://solflare.com/access-wallet" },
|
||||
{ name: "mathwallet.org", url: "https://www.mathwallet.org" },
|
||||
{ name: 'sollet.io', url: 'https://www.sollet.io' },
|
||||
{ name: 'solongwallet.com', url: 'http://solongwallet.com' },
|
||||
{ name: 'solflare.com', url: 'https://solflare.com/access-wallet' },
|
||||
{ name: 'mathwallet.org', url: 'https://www.mathwallet.org' },
|
||||
];
|
||||
|
||||
const WalletContext = React.createContext<any>(null);
|
||||
|
||||
export function WalletProvider({ children = null as any }) {
|
||||
const { endpoint } = useConnectionConfig();
|
||||
const [providerUrl, setProviderUrl] = useLocalStorageState(
|
||||
"walletProvider",
|
||||
"https://www.sollet.io"
|
||||
);
|
||||
const [providerUrl, setProviderUrl] = useLocalStorageState('walletProvider', 'https://www.sollet.io');
|
||||
const wallet = useMemo(() => {
|
||||
if (providerUrl === "http://solongwallet.com") {
|
||||
if (providerUrl === 'http://solongwallet.com') {
|
||||
return new SolongAdapter(providerUrl, endpoint);
|
||||
} else {
|
||||
return new Wallet(providerUrl, endpoint);
|
||||
|
@ -30,7 +27,7 @@ export function WalletProvider({ children = null as any }) {
|
|||
|
||||
const [connected, setConnected] = useState(false);
|
||||
useEffect(() => {
|
||||
wallet.on("connect", () => {
|
||||
wallet.on('connect', () => {
|
||||
setConnected(true);
|
||||
let walletPublicKey = wallet.publicKey.toBase58();
|
||||
let keyToDisplay =
|
||||
|
@ -42,15 +39,15 @@ export function WalletProvider({ children = null as any }) {
|
|||
: walletPublicKey;
|
||||
|
||||
notify({
|
||||
message: "Wallet update",
|
||||
description: "Connected to wallet " + keyToDisplay,
|
||||
message: 'Wallet update',
|
||||
description: 'Connected to wallet ' + keyToDisplay,
|
||||
});
|
||||
});
|
||||
wallet.on("disconnect", () => {
|
||||
wallet.on('disconnect', () => {
|
||||
setConnected(false);
|
||||
notify({
|
||||
message: "Wallet update",
|
||||
description: "Disconnected from wallet",
|
||||
message: 'Wallet update',
|
||||
description: 'Disconnected from wallet',
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
|
@ -65,9 +62,7 @@ export function WalletProvider({ children = null as any }) {
|
|||
connected,
|
||||
providerUrl,
|
||||
setProviderUrl,
|
||||
providerName:
|
||||
WALLET_PROVIDERS.find(({ url }) => url === providerUrl)?.name ??
|
||||
providerUrl,
|
||||
providerName: WALLET_PROVIDERS.find(({ url }) => url === providerUrl)?.name ?? providerUrl,
|
||||
}}
|
||||
>
|
||||
{children}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './useUserAccounts';
|
||||
export * from './useAccountByMint';
|
||||
export * from './useTokenName';
|
|
@ -0,0 +1,15 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useUserAccounts } from '../hooks/useUserAccounts';
|
||||
|
||||
export const useAccountByMint = (mint?: string | PublicKey) => {
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const mintAddress = typeof mint === 'string' ? mint : mint?.toBase58();
|
||||
|
||||
const index = userAccounts.findIndex((acc) => acc.info.mint.toBase58() === mintAddress);
|
||||
|
||||
if (index !== -1) {
|
||||
return userAccounts[index];
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { getTokenName } from '../utils/utils';
|
||||
|
||||
export function useTokenName(mintAddress?: string | PublicKey) {
|
||||
const { tokenMap } = useConnectionConfig();
|
||||
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
|
||||
return getTokenName(tokenMap, address);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { TokenAccount } from "../models";
|
||||
import { useAccountsContext } from "./../contexts/accounts";
|
||||
import { TokenAccount } from '../models';
|
||||
import { useAccountsContext } from 'common/src/contexts/accounts';
|
||||
|
||||
export function useUserAccounts() {
|
||||
const context = useAccountsContext();
|
|
@ -0,0 +1,2 @@
|
|||
export * from './account';
|
||||
export * from './tokenSwap';
|
|
@ -0,0 +1,68 @@
|
|||
import * as BufferLayout from 'buffer-layout';
|
||||
import { publicKey, uint64 } from '../utils/layout';
|
||||
|
||||
export { TokenSwap } from '@solana/spl-token-swap';
|
||||
|
||||
const FEE_LAYOUT = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.nu64('tradeFeeNumerator'),
|
||||
BufferLayout.nu64('tradeFeeDenominator'),
|
||||
BufferLayout.nu64('ownerTradeFeeNumerator'),
|
||||
BufferLayout.nu64('ownerTradeFeeDenominator'),
|
||||
BufferLayout.nu64('ownerWithdrawFeeNumerator'),
|
||||
BufferLayout.nu64('ownerWithdrawFeeDenominator'),
|
||||
BufferLayout.nu64('hostFeeNumerator'),
|
||||
BufferLayout.nu64('hostFeeDenominator'),
|
||||
],
|
||||
'fees'
|
||||
);
|
||||
|
||||
export const TokenSwapLayoutLegacyV0 = BufferLayout.struct([
|
||||
BufferLayout.u8('isInitialized'),
|
||||
BufferLayout.u8('nonce'),
|
||||
publicKey('tokenAccountA'),
|
||||
publicKey('tokenAccountB'),
|
||||
publicKey('tokenPool'),
|
||||
uint64('feesNumerator'),
|
||||
uint64('feesDenominator'),
|
||||
]);
|
||||
|
||||
export const TokenSwapLayoutV1: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||
BufferLayout.u8('isInitialized'),
|
||||
BufferLayout.u8('nonce'),
|
||||
publicKey('tokenProgramId'),
|
||||
publicKey('tokenAccountA'),
|
||||
publicKey('tokenAccountB'),
|
||||
publicKey('tokenPool'),
|
||||
publicKey('mintA'),
|
||||
publicKey('mintB'),
|
||||
publicKey('feeAccount'),
|
||||
BufferLayout.u8('curveType'),
|
||||
uint64('tradeFeeNumerator'),
|
||||
uint64('tradeFeeDenominator'),
|
||||
uint64('ownerTradeFeeNumerator'),
|
||||
uint64('ownerTradeFeeDenominator'),
|
||||
uint64('ownerWithdrawFeeNumerator'),
|
||||
uint64('ownerWithdrawFeeDenominator'),
|
||||
BufferLayout.blob(16, 'padding'),
|
||||
]);
|
||||
|
||||
const CURVE_NODE = BufferLayout.union(BufferLayout.u8(), BufferLayout.blob(32), 'curve');
|
||||
CURVE_NODE.addVariant(0, BufferLayout.struct([]), 'constantProduct');
|
||||
CURVE_NODE.addVariant(1, BufferLayout.struct([BufferLayout.nu64('token_b_price')]), 'constantPrice');
|
||||
CURVE_NODE.addVariant(2, BufferLayout.struct([]), 'stable');
|
||||
CURVE_NODE.addVariant(3, BufferLayout.struct([BufferLayout.nu64('token_b_offset')]), 'offset');
|
||||
|
||||
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||
BufferLayout.u8('isInitialized'),
|
||||
BufferLayout.u8('nonce'),
|
||||
publicKey('tokenProgramId'),
|
||||
publicKey('tokenAccountA'),
|
||||
publicKey('tokenAccountB'),
|
||||
publicKey('tokenPool'),
|
||||
publicKey('mintA'),
|
||||
publicKey('mintB'),
|
||||
publicKey('feeAccount'),
|
||||
FEE_LAYOUT,
|
||||
CURVE_NODE,
|
||||
]);
|
|
@ -1,16 +1,10 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { TokenSwapLayout, TokenSwapLayoutV1 } from "../models";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { TokenSwapLayout, TokenSwapLayoutV1 } from '../models/tokenSwap';
|
||||
|
||||
export const WRAPPED_SOL_MINT = new PublicKey(
|
||||
"So11111111111111111111111111111111111111112"
|
||||
);
|
||||
export let TOKEN_PROGRAM_ID = new PublicKey(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
);
|
||||
export const WRAPPED_SOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
|
||||
export let TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
||||
|
||||
export let LENDING_PROGRAM_ID = new PublicKey(
|
||||
"TokenLending1111111111111111111111111111111"
|
||||
);
|
||||
export let LENDING_PROGRAM_ID = new PublicKey('TokenLending1111111111111111111111111111111');
|
||||
|
||||
let SWAP_PROGRAM_ID: PublicKey;
|
||||
let SWAP_PROGRAM_LEGACY_IDS: PublicKey[];
|
||||
|
@ -27,10 +21,10 @@ export const ENABLE_FEES_INPUT = false;
|
|||
// legacy pools are used to show users contributions in those pools to allow for withdrawals of funds
|
||||
export const PROGRAM_IDS = [
|
||||
{
|
||||
name: "mainnet-beta",
|
||||
name: 'mainnet-beta',
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey("9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL"),
|
||||
pubkey: new PublicKey('9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL'),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [
|
||||
|
@ -40,30 +34,30 @@ export const PROGRAM_IDS = [
|
|||
}),
|
||||
},
|
||||
{
|
||||
name: "testnet",
|
||||
name: 'testnet',
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey("2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg"),
|
||||
pubkey: new PublicKey('2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg'),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "devnet",
|
||||
name: 'devnet',
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey("6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj"),
|
||||
pubkey: new PublicKey('6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj'),
|
||||
layout: TokenSwapLayout,
|
||||
},
|
||||
legacy: [new PublicKey("BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ")],
|
||||
legacy: [new PublicKey('BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ')],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "localnet",
|
||||
name: 'localnet',
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey("369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7"),
|
||||
pubkey: new PublicKey('369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7'),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [],
|
||||
|
@ -83,10 +77,8 @@ export const setProgramIds = (envName: string) => {
|
|||
SWAP_PROGRAM_LAYOUT = swap.current.layout;
|
||||
SWAP_PROGRAM_LEGACY_IDS = swap.legacy;
|
||||
|
||||
if (envName === "mainnet-beta") {
|
||||
LENDING_PROGRAM_ID = new PublicKey(
|
||||
"LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi"
|
||||
);
|
||||
if (envName === 'mainnet-beta') {
|
||||
LENDING_PROGRAM_ID = new PublicKey('LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
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"): unknown => {
|
||||
export const publicKey = (property = 'publicKey'): unknown => {
|
||||
const publicKeyLayout = BufferLayout.blob(32, property);
|
||||
|
||||
const _decode = publicKeyLayout.decode.bind(publicKeyLayout);
|
||||
|
@ -26,7 +26,7 @@ export const publicKey = (property = "publicKey"): unknown => {
|
|||
/**
|
||||
* Layout for a 64bit unsigned value
|
||||
*/
|
||||
export const uint64 = (property = "uint64"): unknown => {
|
||||
export const uint64 = (property = 'uint64'): unknown => {
|
||||
const layout = BufferLayout.blob(8, property);
|
||||
|
||||
const _decode = layout.decode.bind(layout);
|
||||
|
@ -38,7 +38,7 @@ export const uint64 = (property = "uint64"): unknown => {
|
|||
[...data]
|
||||
.reverse()
|
||||
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||
.join(""),
|
||||
.join(''),
|
||||
16
|
||||
);
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ export const uint64 = (property = "uint64"): unknown => {
|
|||
};
|
||||
|
||||
// TODO: wrap in BN (what about decimals?)
|
||||
export const uint128 = (property = "uint128"): unknown => {
|
||||
export const uint128 = (property = 'uint128'): unknown => {
|
||||
const layout = BufferLayout.blob(16, property);
|
||||
|
||||
const _decode = layout.decode.bind(layout);
|
||||
|
@ -70,7 +70,7 @@ export const uint128 = (property = "uint128"): unknown => {
|
|||
[...data]
|
||||
.reverse()
|
||||
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||
.join(""),
|
||||
.join(''),
|
||||
16
|
||||
);
|
||||
};
|
||||
|
@ -93,12 +93,12 @@ export const uint128 = (property = "uint128"): unknown => {
|
|||
/**
|
||||
* Layout for a Rust String type
|
||||
*/
|
||||
export const rustString = (property = "string"): unknown => {
|
||||
export const rustString = (property = 'string'): unknown => {
|
||||
const rsl = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u32("length"),
|
||||
BufferLayout.u32("lengthPadding"),
|
||||
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), "chars"),
|
||||
BufferLayout.u32('length'),
|
||||
BufferLayout.u32('lengthPadding'),
|
||||
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
|
||||
],
|
||||
property
|
||||
);
|
||||
|
@ -107,12 +107,12 @@ export const rustString = (property = "string"): unknown => {
|
|||
|
||||
rsl.decode = (buffer: Buffer, offset: number) => {
|
||||
const data = _decode(buffer, offset);
|
||||
return data.chars.toString("utf8");
|
||||
return data.chars.toString('utf8');
|
||||
};
|
||||
|
||||
rsl.encode = (str: string, buffer: Buffer, offset: number) => {
|
||||
const data = {
|
||||
chars: Buffer.from(str, "utf8"),
|
||||
chars: Buffer.from(str, 'utf8'),
|
||||
};
|
||||
return _encode(data, buffer, offset);
|
||||
};
|
|
@ -0,0 +1,204 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
|
||||
import { TokenAccount } from './../models';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import { WAD, ZERO } from '../constants';
|
||||
|
||||
export interface KnownToken {
|
||||
tokenSymbol: string;
|
||||
tokenName: string;
|
||||
icon: string;
|
||||
mintAddress: string;
|
||||
}
|
||||
|
||||
export type KnownTokenMap = Map<string, KnownToken>;
|
||||
|
||||
export const formatPriceNumber = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 8,
|
||||
});
|
||||
|
||||
export function useLocalStorageState(key: string, defaultState?: string) {
|
||||
const [state, setState] = useState(() => {
|
||||
// NOTE: Not sure if this is ok
|
||||
const storedState = localStorage.getItem(key);
|
||||
if (storedState) {
|
||||
return JSON.parse(storedState);
|
||||
}
|
||||
return defaultState;
|
||||
});
|
||||
|
||||
const setLocalStorageState = useCallback(
|
||||
(newState) => {
|
||||
const changed = state !== newState;
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
setState(newState);
|
||||
if (newState === null) {
|
||||
localStorage.removeItem(key);
|
||||
} else {
|
||||
localStorage.setItem(key, JSON.stringify(newState));
|
||||
}
|
||||
},
|
||||
[state, key]
|
||||
);
|
||||
|
||||
return [state, setLocalStorageState];
|
||||
}
|
||||
|
||||
// shorten the checksummed version of the input address to have 4 characters at start and end
|
||||
export function shortenAddress(address: string, chars = 4): string {
|
||||
return `${address.slice(0, chars)}...${address.slice(-chars)}`;
|
||||
}
|
||||
|
||||
export function getTokenName(map: KnownTokenMap, mint?: string | PublicKey, shorten = true): string {
|
||||
const mintAddress = typeof mint === 'string' ? mint : mint?.toBase58();
|
||||
|
||||
if (!mintAddress) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
const knownSymbol = map.get(mintAddress)?.tokenSymbol;
|
||||
if (knownSymbol) {
|
||||
return knownSymbol;
|
||||
}
|
||||
|
||||
return shorten ? `${mintAddress.substring(0, 5)}...` : mintAddress;
|
||||
}
|
||||
|
||||
export function getTokenByName(tokenMap: KnownTokenMap, name: string) {
|
||||
let token: KnownToken | null = null;
|
||||
for (const val of tokenMap.values()) {
|
||||
if (val.tokenSymbol === name) {
|
||||
token = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getTokenIcon(map: KnownTokenMap, mintAddress?: string | PublicKey): string | undefined {
|
||||
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
return map.get(address)?.icon;
|
||||
}
|
||||
|
||||
export function isKnownMint(map: KnownTokenMap, mintAddress: string) {
|
||||
return !!map.get(mintAddress);
|
||||
}
|
||||
|
||||
export const STABLE_COINS = new Set(['USDC', 'wUSDC', 'USDT']);
|
||||
|
||||
export function chunks<T>(array: T[], size: number): T[][] {
|
||||
return Array.apply<number, T[], T[][]>(0, new Array(Math.ceil(array.length / size))).map((_, index) =>
|
||||
array.slice(index * size, (index + 1) * size)
|
||||
);
|
||||
}
|
||||
|
||||
export function toLamports(account?: TokenAccount | number, mint?: MintInfo): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
return Math.floor(amount * precision);
|
||||
}
|
||||
|
||||
export function wadToLamports(amount?: BN): BN {
|
||||
return amount?.div(WAD) || ZERO;
|
||||
}
|
||||
|
||||
export function fromLamports(account?: TokenAccount | number | BN, mint?: MintInfo, rate: number = 1.0): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount = Math.floor(
|
||||
typeof account === 'number' ? account : BN.isBN(account) ? account.toNumber() : account.info.amount.toNumber()
|
||||
);
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
return (amount / precision) * rate;
|
||||
}
|
||||
|
||||
var SI_SYMBOL = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
|
||||
|
||||
const abbreviateNumber = (number: number, precision: number) => {
|
||||
let tier = (Math.log10(number) / 3) | 0;
|
||||
let scaled = number;
|
||||
let suffix = SI_SYMBOL[tier];
|
||||
if (tier !== 0) {
|
||||
let scale = Math.pow(10, tier * 3);
|
||||
scaled = number / scale;
|
||||
}
|
||||
|
||||
return scaled.toFixed(precision) + suffix;
|
||||
};
|
||||
|
||||
export const formatAmount = (val: number, precision: number = 6, abbr: boolean = true) =>
|
||||
abbr ? abbreviateNumber(val, precision) : val.toFixed(precision);
|
||||
|
||||
export function formatTokenAmount(
|
||||
account?: TokenAccount,
|
||||
mint?: MintInfo,
|
||||
rate: number = 1.0,
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
precision = 6,
|
||||
abbr = false
|
||||
): string {
|
||||
if (!account) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${[prefix]}${formatAmount(fromLamports(account, mint, rate), precision, abbr)}${suffix}`;
|
||||
}
|
||||
|
||||
export const formatUSD = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
});
|
||||
|
||||
const numberFormater = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export const formatNumber = {
|
||||
format: (val?: number) => {
|
||||
if (!val) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
return numberFormater.format(val);
|
||||
},
|
||||
};
|
||||
|
||||
export const formatPct = new Intl.NumberFormat('en-US', {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export function convert(account?: TokenAccount | number, mint?: MintInfo, rate: number = 1.0): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
let result = (amount / precision) * rate;
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"include": ["src"]
|
||||
}
|
Binary file not shown.
|
@ -2,6 +2,7 @@
|
|||
"name": "lending",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.4.0",
|
||||
"@ant-design/pro-layout": "^6.7.0",
|
||||
"@craco/craco": "^5.7.0",
|
||||
"@project-serum/serum": "^0.13.11",
|
||||
|
@ -21,6 +22,7 @@
|
|||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"common": "workspace:common",
|
||||
"craco-less": "^1.17.0",
|
||||
"echarts": "^4.9.0",
|
||||
"eventemitter3": "^4.0.7",
|
||||
|
|
|
@ -1,37 +1,28 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending/reserve";
|
||||
import { AccountLayout, MintInfo, MintLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID, LEND_HOST_FEE_ADDRESS } from "../utils/ids";
|
||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { sendTransaction } from 'common/src/contexts/connection';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||
import { AccountLayout, MintInfo, MintLayout } from '@solana/spl-token';
|
||||
import { LENDING_PROGRAM_ID, LEND_HOST_FEE_ADDRESS } from 'common/src/utils/ids';
|
||||
import {
|
||||
createTempMemoryAccount,
|
||||
createUninitializedAccount,
|
||||
createUninitializedMint,
|
||||
createUninitializedObligation,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from "./account";
|
||||
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
||||
} from 'common/src/actions/account';
|
||||
import { createUninitializedObligation } from './obligation';
|
||||
import { cache, MintParser, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import {
|
||||
TokenAccount,
|
||||
LendingObligationLayout,
|
||||
borrowInstruction,
|
||||
LendingMarket,
|
||||
BorrowAmountType,
|
||||
LendingObligation,
|
||||
approve,
|
||||
initObligationInstruction,
|
||||
} from "../models";
|
||||
import { toLamports } from "../utils/utils";
|
||||
} from '../models';
|
||||
import { TokenAccount, approve } from 'common/src/models';
|
||||
import { toLamports } from 'common/src/utils/utils';
|
||||
|
||||
export const borrow = async (
|
||||
connection: Connection,
|
||||
|
@ -50,9 +41,9 @@ export const borrow = async (
|
|||
obligationAccount?: PublicKey
|
||||
) => {
|
||||
notify({
|
||||
message: "Borrowing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Borrowing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
let signers: Account[] = [];
|
||||
|
@ -65,18 +56,14 @@ export const borrow = async (
|
|||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
|
||||
const obligation = existingObligation
|
||||
? existingObligation.pubkey
|
||||
: createUninitializedObligation(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
await connection.getMinimumBalanceForRentExemption(
|
||||
LendingObligationLayout.span
|
||||
),
|
||||
await connection.getMinimumBalanceForRentExemption(LendingObligationLayout.span),
|
||||
signers
|
||||
);
|
||||
|
||||
|
@ -91,12 +78,7 @@ export const borrow = async (
|
|||
|
||||
const obligationTokenOutput = obligationAccount
|
||||
? obligationAccount
|
||||
: createUninitializedAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
signers
|
||||
);
|
||||
: createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||
|
||||
if (!obligationAccount) {
|
||||
instructions.push(
|
||||
|
@ -173,21 +155,19 @@ export const borrow = async (
|
|||
|
||||
if (instructions.length > 0) {
|
||||
// create all accounts in one transaction
|
||||
let tx = await sendTransaction(connection, wallet, instructions, [
|
||||
...signers,
|
||||
]);
|
||||
let tx = await sendTransaction(connection, wallet, instructions, [...signers]);
|
||||
|
||||
notify({
|
||||
message: "Obligation accounts created",
|
||||
message: 'Obligation accounts created',
|
||||
description: `Transaction ${tx}`,
|
||||
type: "success",
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
notify({
|
||||
message: "Borrowing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Borrowing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
signers = [];
|
||||
|
@ -214,25 +194,14 @@ export const borrow = async (
|
|||
throw new Error(`Dex market doesn't exist.`);
|
||||
}
|
||||
|
||||
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
||||
depositReserve.info.liquidityMint
|
||||
)
|
||||
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
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
|
||||
);
|
||||
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey)
|
||||
);
|
||||
instructions.push(accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey));
|
||||
// borrow
|
||||
instructions.push(
|
||||
borrowInstruction(
|
||||
|
@ -264,17 +233,11 @@ export const borrow = async (
|
|||
)
|
||||
);
|
||||
try {
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
|
||||
notify({
|
||||
message: "Funds borrowed.",
|
||||
type: "success",
|
||||
message: 'Funds borrowed.',
|
||||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch (ex) {
|
||||
|
|
|
@ -1,25 +1,16 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { sendTransaction } from 'common/src/contexts/connection';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
depositInstruction,
|
||||
initReserveInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending";
|
||||
import { AccountLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
||||
import {
|
||||
createUninitializedAccount,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from "./account";
|
||||
import { approve, TokenAccount } from "../models";
|
||||
} from './../models/lending';
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||
import { approve, TokenAccount } from 'common/src/models';
|
||||
|
||||
export const deposit = async (
|
||||
from: TokenAccount,
|
||||
|
@ -30,9 +21,9 @@ export const deposit = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: "Depositing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Depositing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
const isInitalized = true; // TODO: finish reserve init
|
||||
|
@ -42,9 +33,7 @@ export const deposit = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[reserve.lendingMarket.toBuffer()], // which account should be authority
|
||||
|
@ -61,13 +50,7 @@ export const deposit = async (
|
|||
);
|
||||
|
||||
// create approval for transfer transactions
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
cleanupInstructions,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
amountLamports
|
||||
);
|
||||
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||
|
||||
signers.push(transferAuthority);
|
||||
|
||||
|
@ -84,12 +67,7 @@ export const deposit = async (
|
|||
signers
|
||||
);
|
||||
} else {
|
||||
toAccount = createUninitializedAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
signers
|
||||
);
|
||||
toAccount = createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||
}
|
||||
|
||||
if (isInitalized) {
|
||||
|
@ -132,17 +110,11 @@ export const deposit = async (
|
|||
}
|
||||
|
||||
try {
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
|
||||
notify({
|
||||
message: "Funds deposited.",
|
||||
type: "success",
|
||||
message: 'Funds deposited.',
|
||||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export { borrow } from "./borrow";
|
||||
export { deposit } from "./deposit";
|
||||
export { repay } from "./repay";
|
||||
export { withdraw } from "./withdraw";
|
||||
export { liquidate } from "./liquidate";
|
||||
export * from "./account";
|
||||
export { borrow } from './borrow';
|
||||
export { deposit } from './deposit';
|
||||
export { repay } from './repay';
|
||||
export { withdraw } from './withdraw';
|
||||
export { liquidate } from './liquidate';
|
||||
|
|
|
@ -1,30 +1,14 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending/reserve";
|
||||
import { liquidateInstruction } from "./../models/lending/liquidate";
|
||||
import { AccountLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
||||
import {
|
||||
createTempMemoryAccount,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from "./account";
|
||||
import {
|
||||
approve,
|
||||
LendingMarket,
|
||||
LendingObligation,
|
||||
TokenAccount,
|
||||
} from "../models";
|
||||
import { cache, ParsedAccount } from "../contexts/accounts";
|
||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { sendTransaction } from 'common/src/contexts/connection';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||
import { liquidateInstruction } from './../models/lending/liquidate';
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||
import { LendingMarket, LendingObligation } from '../models';
|
||||
import { approve, TokenAccount } from 'common/src/models';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
|
||||
export const liquidate = async (
|
||||
connection: Connection,
|
||||
|
@ -40,9 +24,9 @@ export const liquidate = async (
|
|||
withdrawReserve: ParsedAccount<LendingReserve>
|
||||
) => {
|
||||
notify({
|
||||
message: "Repaying funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Repaying funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -50,9 +34,7 @@ export const liquidate = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[repayReserve.info.lendingMarket.toBuffer()],
|
||||
|
@ -69,13 +51,7 @@ export const liquidate = async (
|
|||
);
|
||||
|
||||
// create approval for transfer transactions
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
cleanupInstructions,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
amountLamports
|
||||
);
|
||||
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||
signers.push(transferAuthority);
|
||||
|
||||
// get destination account
|
||||
|
@ -98,26 +74,15 @@ export const liquidate = async (
|
|||
throw new Error(`Dex market doesn't exist.`);
|
||||
}
|
||||
|
||||
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
||||
repayReserve.info.liquidityMint
|
||||
)
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(repayReserve.info.liquidityMint)
|
||||
? dexMarket?.info.asks
|
||||
: dexMarket?.info.bids;
|
||||
|
||||
const memory = createTempMemoryAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
signers,
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
||||
);
|
||||
instructions.push(accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey));
|
||||
|
||||
instructions.push(
|
||||
liquidateInstruction(
|
||||
|
@ -138,17 +103,11 @@ export const liquidate = async (
|
|||
)
|
||||
);
|
||||
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
|
||||
notify({
|
||||
message: "Funds liquidated.",
|
||||
type: "success",
|
||||
message: 'Funds liquidated.',
|
||||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
||||
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { LendingObligationLayout } from '../models';
|
||||
|
||||
export function createUninitializedObligation(
|
||||
instructions: TransactionInstruction[],
|
||||
payer: PublicKey,
|
||||
amount: number,
|
||||
signers: Account[]
|
||||
) {
|
||||
const account = new Account();
|
||||
instructions.push(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: payer,
|
||||
newAccountPubkey: account.publicKey,
|
||||
lamports: amount,
|
||||
space: LendingObligationLayout.span,
|
||||
programId: LENDING_PROGRAM_ID,
|
||||
})
|
||||
);
|
||||
|
||||
signers.push(account);
|
||||
|
||||
return account.publicKey;
|
||||
}
|
|
@ -1,21 +1,14 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending/reserve";
|
||||
import { repayInstruction } from "./../models/lending/repay";
|
||||
import { AccountLayout, Token, NATIVE_MINT } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../utils/ids";
|
||||
import { createTokenAccount, findOrCreateAccountByMint } from "./account";
|
||||
import { approve, LendingObligation, TokenAccount } from "../models";
|
||||
import { ParsedAccount } from "../contexts/accounts";
|
||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { sendTransaction } from 'common/src/contexts/connection';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||
import { repayInstruction } from './../models/lending/repay';
|
||||
import { AccountLayout, Token, NATIVE_MINT } from '@solana/spl-token';
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { createTokenAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||
import { LendingObligation } from '../models';
|
||||
import { approve, TokenAccount } from 'common/src/models';
|
||||
import { ParsedAccount } from 'common/src/contexts/accounts';
|
||||
|
||||
export const repay = async (
|
||||
from: TokenAccount,
|
||||
|
@ -34,9 +27,9 @@ export const repay = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: "Repaying funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Repaying funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -44,9 +37,7 @@ export const repay = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[repayReserve.info.lendingMarket.toBuffer()],
|
||||
|
@ -54,10 +45,7 @@ export const repay = async (
|
|||
);
|
||||
|
||||
let fromAccount = from.pubkey;
|
||||
if (
|
||||
wallet.publicKey.equals(fromAccount) &&
|
||||
repayReserve.info.liquidityMint.equals(NATIVE_MINT)
|
||||
) {
|
||||
if (wallet.publicKey.equals(fromAccount) && repayReserve.info.liquidityMint.equals(NATIVE_MINT)) {
|
||||
fromAccount = createTokenAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
|
@ -67,24 +55,12 @@ export const repay = async (
|
|||
signers
|
||||
);
|
||||
cleanupInstructions.push(
|
||||
Token.createCloseAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
[]
|
||||
)
|
||||
Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, fromAccount, wallet.publicKey, wallet.publicKey, [])
|
||||
);
|
||||
}
|
||||
|
||||
// create approval for transfer transactions
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
cleanupInstructions,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
repayAmount
|
||||
);
|
||||
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, repayAmount);
|
||||
signers.push(transferAuthority);
|
||||
|
||||
// get destination account
|
||||
|
@ -110,9 +86,7 @@ export const repay = async (
|
|||
transferAuthority.publicKey
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
||||
);
|
||||
instructions.push(accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey));
|
||||
|
||||
instructions.push(
|
||||
repayInstruction(
|
||||
|
@ -132,17 +106,11 @@ export const repay = async (
|
|||
)
|
||||
);
|
||||
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
|
||||
notify({
|
||||
message: "Funds repaid.",
|
||||
type: "success",
|
||||
message: 'Funds repaid.',
|
||||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
withdrawInstruction,
|
||||
} from "./../models/lending";
|
||||
import { AccountLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
||||
import { findOrCreateAccountByMint } from "./account";
|
||||
import { approve, TokenAccount } from "../models";
|
||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { sendTransaction } from 'common/src/contexts/connection';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { accrueInterestInstruction, LendingReserve, withdrawInstruction } from './../models/lending';
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||
import { approve, TokenAccount } from 'common/src/models';
|
||||
|
||||
export const withdraw = async (
|
||||
from: TokenAccount, // CollateralAccount
|
||||
|
@ -25,9 +16,9 @@ export const withdraw = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: "Withdrawing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
message: 'Withdrawing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -35,25 +26,14 @@ export const withdraw = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[reserve.lendingMarket.toBuffer()],
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
const [authority] = await PublicKey.findProgramAddress([reserve.lendingMarket.toBuffer()], LENDING_PROGRAM_ID);
|
||||
|
||||
const fromAccount = from.pubkey;
|
||||
|
||||
// create approval for transfer transactions
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
cleanupInstructions,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
amountLamports
|
||||
);
|
||||
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||
|
||||
signers.push(transferAuthority);
|
||||
|
||||
|
@ -85,17 +65,11 @@ export const withdraw = async (
|
|||
);
|
||||
|
||||
try {
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
|
||||
notify({
|
||||
message: "Funds deposited.",
|
||||
type: "success",
|
||||
message: 'Funds deposited.',
|
||||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch {
|
||||
|
|
|
@ -1,48 +1,32 @@
|
|||
import React from "react";
|
||||
import { Button, Popover } from "antd";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { CurrentUserBadge } from "../CurrentUserBadge";
|
||||
import { SettingOutlined } from "@ant-design/icons";
|
||||
import { Settings } from "../Settings";
|
||||
import { LABELS } from "../../constants";
|
||||
import React from 'react';
|
||||
import { Button, Popover } from 'antd';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { CurrentUserBadge } from '../CurrentUserBadge';
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Settings } from '../Settings';
|
||||
import { LABELS } from '../../constants';
|
||||
|
||||
export const AppBar = (props: { left?: JSX.Element; right?: JSX.Element }) => {
|
||||
const { connected, wallet } = useWallet();
|
||||
|
||||
const TopBar = (
|
||||
<div className="App-Bar-right">
|
||||
<div className='App-Bar-right'>
|
||||
<CurrentUserBadge />
|
||||
<div>
|
||||
{!connected && (
|
||||
<Button
|
||||
type="text"
|
||||
size="large"
|
||||
type='text'
|
||||
size='large'
|
||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||
style={{ color: "#2abdd2" }}
|
||||
style={{ color: '#2abdd2' }}
|
||||
>
|
||||
{LABELS.CONNECT_BUTTON}
|
||||
</Button>
|
||||
)}
|
||||
{connected && (
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
title={LABELS.WALLET_TOOLTIP}
|
||||
trigger="click"
|
||||
></Popover>
|
||||
)}
|
||||
{connected && <Popover placement='bottomRight' title={LABELS.WALLET_TOOLTIP} trigger='click'></Popover>}
|
||||
</div>
|
||||
<Popover
|
||||
placement="topRight"
|
||||
title={LABELS.SETTINGS_TOOLTIP}
|
||||
content={<Settings />}
|
||||
trigger="click"
|
||||
>
|
||||
<Button
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="text"
|
||||
icon={<SettingOutlined />}
|
||||
/>
|
||||
<Popover placement='topRight' title={LABELS.SETTINGS_TOOLTIP} content={<Settings />} trigger='click'>
|
||||
<Button shape='circle' size='large' type='text' icon={<SettingOutlined />} />
|
||||
</Popover>
|
||||
{props.right}
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,25 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
useSliderInput,
|
||||
useUserBalance,
|
||||
useUserDeposits,
|
||||
useUserObligationByReserve,
|
||||
} from "../../hooks";
|
||||
import {
|
||||
BorrowAmountType,
|
||||
LendingReserve,
|
||||
LendingReserveParser,
|
||||
} from "../../models";
|
||||
import { Card } from "antd";
|
||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { borrow } from "../../actions";
|
||||
import "./style.less";
|
||||
import { LABELS } from "../../constants";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
import { BackButton } from "./../BackButton";
|
||||
import { ConnectButton } from "../ConnectButton";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
import { RiskSlider } from "../RiskSlider";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSliderInput, useUserBalance, useUserDeposits, useUserObligationByReserve } from '../../hooks';
|
||||
import { BorrowAmountType, LendingReserve, LendingReserveParser } from '../../models';
|
||||
import { Card } from 'antd';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { borrow } from '../../actions';
|
||||
import './style.less';
|
||||
import { LABELS } from '../../constants';
|
||||
import { ActionConfirmation } from './../ActionConfirmation';
|
||||
import { BackButton } from './../BackButton';
|
||||
import { ConnectButton } from '../ConnectButton';
|
||||
import CollateralInput from '../CollateralInput';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
import { RiskSlider } from '../RiskSlider';
|
||||
|
||||
export const BorrowInput = (props: {
|
||||
className?: string;
|
||||
reserve: ParsedAccount<LendingReserve>;
|
||||
}) => {
|
||||
export const BorrowInput = (props: { className?: string; reserve: ParsedAccount<LendingReserve> }) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [collateralValue, setCollateralValue] = useState("");
|
||||
const [lastTyped, setLastTyped] = useState("collateral");
|
||||
const [collateralValue, setCollateralValue] = useState('');
|
||||
const [lastTyped, setLastTyped] = useState('collateral');
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
|
@ -40,24 +28,14 @@ export const BorrowInput = (props: {
|
|||
const [collateralReserveKey, setCollateralReserveKey] = useState<string>();
|
||||
|
||||
const collateralReserve = useMemo(() => {
|
||||
const id: string =
|
||||
cache
|
||||
.byParser(LendingReserveParser)
|
||||
.find((acc) => acc === collateralReserveKey) || "";
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserveKey) || '';
|
||||
|
||||
return cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
}, [collateralReserveKey]);
|
||||
const borrowPrice = useMidPriceInUSD(
|
||||
borrowReserve.info.liquidityMint.toBase58()
|
||||
).price;
|
||||
const collateralPrice = useMidPriceInUSD(
|
||||
collateralReserve?.info.liquidityMint.toBase58()
|
||||
)?.price;
|
||||
const borrowPrice = useMidPriceInUSD(borrowReserve.info.liquidityMint.toBase58()).price;
|
||||
const collateralPrice = useMidPriceInUSD(collateralReserve?.info.liquidityMint.toBase58())?.price;
|
||||
|
||||
const include = useMemo(
|
||||
() => new Set([collateralReserve?.pubkey.toBase58()]),
|
||||
[collateralReserve]
|
||||
);
|
||||
const include = useMemo(() => new Set([collateralReserve?.pubkey.toBase58()]), [collateralReserve]);
|
||||
|
||||
const exclude = useMemo(() => new Set([]), []);
|
||||
|
||||
|
@ -67,7 +45,7 @@ export const BorrowInput = (props: {
|
|||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
const minAmount = Math.min(tokenBalance, Infinity);
|
||||
if (typeof val === "string") {
|
||||
if (typeof val === 'string') {
|
||||
return (parseFloat(val) / minAmount) * 100;
|
||||
} else {
|
||||
return (val * minAmount) / 100;
|
||||
|
@ -79,7 +57,7 @@ export const BorrowInput = (props: {
|
|||
const { value, setValue, pct } = useSliderInput(convert);
|
||||
|
||||
useEffect(() => {
|
||||
if (collateralReserve && lastTyped === "collateral") {
|
||||
if (collateralReserve && lastTyped === 'collateral') {
|
||||
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
||||
|
||||
if (collateralValue) {
|
||||
|
@ -88,21 +66,13 @@ export const BorrowInput = (props: {
|
|||
const borrowAmount = borrowInUSD / borrowPrice;
|
||||
setValue(borrowAmount.toString());
|
||||
} else {
|
||||
setValue("");
|
||||
setValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
lastTyped,
|
||||
collateralReserve,
|
||||
collateralPrice,
|
||||
borrowPrice,
|
||||
borrowReserve,
|
||||
collateralValue,
|
||||
setValue,
|
||||
]);
|
||||
}, [lastTyped, collateralReserve, collateralPrice, borrowPrice, borrowReserve, collateralValue, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (collateralReserve && lastTyped === "borrow") {
|
||||
if (collateralReserve && lastTyped === 'borrow') {
|
||||
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
||||
|
||||
if (value) {
|
||||
|
@ -111,25 +81,13 @@ export const BorrowInput = (props: {
|
|||
const collateralAmount = borrowInUSD / ltv / collateralPrice;
|
||||
setCollateralValue(collateralAmount.toString());
|
||||
} else {
|
||||
setCollateralValue("");
|
||||
setCollateralValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
lastTyped,
|
||||
collateralReserve,
|
||||
collateralPrice,
|
||||
borrowPrice,
|
||||
borrowReserve,
|
||||
value,
|
||||
]);
|
||||
}, [lastTyped, collateralReserve, collateralPrice, borrowPrice, borrowReserve, value]);
|
||||
|
||||
const { userObligationsByReserve } = useUserObligationByReserve(
|
||||
borrowReserve?.pubkey,
|
||||
collateralReserve?.pubkey
|
||||
);
|
||||
const { accounts: fromAccounts } = useUserBalance(
|
||||
collateralReserve?.info.collateralMint
|
||||
);
|
||||
const { userObligationsByReserve } = useUserObligationByReserve(borrowReserve?.pubkey, collateralReserve?.pubkey);
|
||||
const { accounts: fromAccounts } = useUserBalance(collateralReserve?.info.collateralMint);
|
||||
const onBorrow = useCallback(() => {
|
||||
if (!collateralReserve) {
|
||||
return;
|
||||
|
@ -151,17 +109,13 @@ export const BorrowInput = (props: {
|
|||
collateralReserve,
|
||||
|
||||
// TODO: select exsisting obligations by collateral reserve
|
||||
userObligationsByReserve.length > 0
|
||||
? userObligationsByReserve[0].obligation.account
|
||||
: undefined,
|
||||
userObligationsByReserve.length > 0 ? userObligationsByReserve[0].obligation.account : undefined,
|
||||
|
||||
userObligationsByReserve.length > 0
|
||||
? userObligationsByReserve[0].userAccounts[0].pubkey
|
||||
: undefined
|
||||
userObligationsByReserve.length > 0 ? userObligationsByReserve[0].userAccounts[0].pubkey : undefined
|
||||
);
|
||||
|
||||
setValue("");
|
||||
setCollateralValue("");
|
||||
setValue('');
|
||||
setCollateralValue('');
|
||||
setShowConfirmation(true);
|
||||
} catch {
|
||||
// TODO:
|
||||
|
@ -183,11 +137,11 @@ export const BorrowInput = (props: {
|
|||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -197,27 +151,27 @@ export const BorrowInput = (props: {
|
|||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<div className="borrow-input-title">{LABELS.BORROW_QUESTION}</div>
|
||||
<div className='borrow-input-title'>{LABELS.BORROW_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Collateral (estimated)"
|
||||
title='Collateral (estimated)'
|
||||
reserve={borrowReserve.info}
|
||||
amount={parseFloat(collateralValue) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setCollateralValue(val?.toString() || "");
|
||||
setLastTyped("collateral");
|
||||
setCollateralValue(val?.toString() || '');
|
||||
setLastTyped('collateral');
|
||||
}}
|
||||
onCollateralReserve={(key) => {
|
||||
setCollateralReserveKey(key);
|
||||
|
@ -228,35 +182,33 @@ export const BorrowInput = (props: {
|
|||
<RiskSlider value={pct} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Borrow Amount"
|
||||
title='Borrow Amount'
|
||||
reserve={borrowReserve.info}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setLastTyped("borrow");
|
||||
setValue(val?.toString() || '');
|
||||
setLastTyped('borrow');
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
/>
|
||||
</div>
|
||||
<ConnectButton
|
||||
size="large"
|
||||
type="primary"
|
||||
size='large'
|
||||
type='primary'
|
||||
onClick={onBorrow}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
>
|
||||
{fromAccounts.length === 0
|
||||
? LABELS.NO_COLLATERAL
|
||||
: LABELS.BORROW_ACTION}
|
||||
{fromAccounts.length === 0 ? LABELS.NO_COLLATERAL : LABELS.BORROW_ACTION}
|
||||
</ConnectButton>
|
||||
<BackButton />
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import {
|
||||
useLendingReserves,
|
||||
useUserBalance,
|
||||
useUserDeposits,
|
||||
} from "../../hooks";
|
||||
import {
|
||||
LendingReserve,
|
||||
LendingMarket,
|
||||
LendingReserveParser,
|
||||
} from "../../models";
|
||||
import { getTokenName } from "../../utils/utils";
|
||||
import { Card, Select } from "antd";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import { NumericInput } from "../Input/numeric";
|
||||
import "./style.less";
|
||||
import { TokenDisplay } from "../TokenDisplay";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { useLendingReserves, useUserBalance, useUserDeposits } from '../../hooks';
|
||||
import { LendingReserve, LendingMarket, LendingReserveParser } from '../../models';
|
||||
import { getTokenName } from 'common/src/utils/utils';
|
||||
import { Card, Select } from 'antd';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
import { NumericInput } from '../Input/numeric';
|
||||
import './style.less';
|
||||
import { TokenDisplay } from '../TokenDisplay';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -41,24 +33,19 @@ export default function CollateralInput(props: {
|
|||
const { tokenMap } = useConnectionConfig();
|
||||
const [collateralReserve, setCollateralReserve] = useState<string>();
|
||||
const [balance, setBalance] = useState<number>(0);
|
||||
const [lastAmount, setLastAmount] = useState<string>("");
|
||||
const [lastAmount, setLastAmount] = useState<string>('');
|
||||
const userDeposits = useUserDeposits();
|
||||
|
||||
useEffect(() => {
|
||||
if (props.useWalletBalance) {
|
||||
setBalance(tokenBalance);
|
||||
} else {
|
||||
const id: string =
|
||||
cache
|
||||
.byParser(LendingReserveParser)
|
||||
.find((acc) => acc === collateralReserve) || "";
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserve) || '';
|
||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
|
||||
if (parser) {
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) =>
|
||||
u.reserve.info.liquidityMint.toBase58() ===
|
||||
parser.info.liquidityMint.toBase58()
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() === parser.info.liquidityMint.toBase58()
|
||||
);
|
||||
if (collateralDeposit) setBalance(collateralDeposit.info.amount);
|
||||
else setBalance(0);
|
||||
|
@ -66,28 +53,16 @@ export default function CollateralInput(props: {
|
|||
}
|
||||
}, [collateralReserve, userDeposits, tokenBalance, props.useWalletBalance]);
|
||||
|
||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
if (!market) return null;
|
||||
|
||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(
|
||||
market?.info?.quoteMint
|
||||
);
|
||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
|
||||
|
||||
const filteredReserveAccounts = reserveAccounts
|
||||
.filter((reserve) => reserve.info !== props.reserve)
|
||||
.filter(
|
||||
(reserve) =>
|
||||
!onlyQuoteAllowed ||
|
||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
||||
);
|
||||
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint));
|
||||
|
||||
if (
|
||||
!collateralReserve &&
|
||||
props.useFirstReserve &&
|
||||
filteredReserveAccounts.length
|
||||
) {
|
||||
if (!collateralReserve && props.useFirstReserve && filteredReserveAccounts.length) {
|
||||
const address = filteredReserveAccounts[0].pubkey.toBase58();
|
||||
setCollateralReserve(address);
|
||||
}
|
||||
|
@ -97,7 +72,7 @@ export default function CollateralInput(props: {
|
|||
const name = getTokenName(tokenMap, mint);
|
||||
return (
|
||||
<Option key={address} value={address} name={name} title={address}>
|
||||
<div key={address} style={{ display: "flex", alignItems: "center" }}>
|
||||
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<TokenIcon mintAddress={mint} />
|
||||
{name}
|
||||
</div>
|
||||
|
@ -106,30 +81,19 @@ export default function CollateralInput(props: {
|
|||
});
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="ccy-input"
|
||||
style={{ borderRadius: 20 }}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<div className="ccy-input-header">
|
||||
<div className="ccy-input-header-left">{props.title}</div>
|
||||
<Card className='ccy-input' style={{ borderRadius: 20 }} bodyStyle={{ padding: 0 }}>
|
||||
<div className='ccy-input-header'>
|
||||
<div className='ccy-input-header-left'>{props.title}</div>
|
||||
|
||||
{!props.hideBalance && (
|
||||
<div
|
||||
className="ccy-input-header-right"
|
||||
onClick={(e) => props.onInputChange && props.onInputChange(balance)}
|
||||
>
|
||||
<div className='ccy-input-header-right' onClick={(e) => props.onInputChange && props.onInputChange(balance)}>
|
||||
Balance: {balance.toFixed(6)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ccy-input-header" style={{ padding: "0px 10px 5px 7px" }}>
|
||||
<div className='ccy-input-header' style={{ padding: '0px 10px 5px 7px' }}>
|
||||
<NumericInput
|
||||
value={
|
||||
parseFloat(lastAmount || "0.00") === props.amount
|
||||
? lastAmount
|
||||
: props.amount?.toFixed(6)?.toString()
|
||||
}
|
||||
value={parseFloat(lastAmount || '0.00') === props.amount ? lastAmount : props.amount?.toFixed(6)?.toString()}
|
||||
onChange={(val: string) => {
|
||||
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||
|
@ -139,19 +103,19 @@ export default function CollateralInput(props: {
|
|||
}}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
boxShadow: "none",
|
||||
borderColor: "transparent",
|
||||
outline: "transparent",
|
||||
boxShadow: 'none',
|
||||
borderColor: 'transparent',
|
||||
outline: 'transparent',
|
||||
}}
|
||||
placeholder="0.00"
|
||||
placeholder='0.00'
|
||||
/>
|
||||
<div className="ccy-input-header-right" style={{ display: "flex" }}>
|
||||
<div className='ccy-input-header-right' style={{ display: 'flex' }}>
|
||||
{props.showLeverageSelector && (
|
||||
<Select
|
||||
size="large"
|
||||
size='large'
|
||||
showSearch
|
||||
style={{ width: 80 }}
|
||||
placeholder="CCY"
|
||||
placeholder='CCY'
|
||||
value={props.leverage}
|
||||
onChange={(item: number) => {
|
||||
if (props.onLeverage) props.onLeverage(item);
|
||||
|
@ -162,22 +126,12 @@ export default function CollateralInput(props: {
|
|||
props.onLeverage(parseFloat(item));
|
||||
}
|
||||
}}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
>
|
||||
{[1, 2, 3, 4, 5].map((val) => (
|
||||
<Option
|
||||
key={val}
|
||||
value={val}
|
||||
name={val + "x"}
|
||||
title={val + "x"}
|
||||
>
|
||||
<div
|
||||
key={val}
|
||||
style={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
{val + "x"}
|
||||
<Option key={val} value={val} name={val + 'x'} title={val + 'x'}>
|
||||
<div key={val} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{val + 'x'}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
|
@ -185,28 +139,23 @@ export default function CollateralInput(props: {
|
|||
)}
|
||||
{!props.disabled ? (
|
||||
<Select
|
||||
size="large"
|
||||
size='large'
|
||||
showSearch
|
||||
style={{ minWidth: 150 }}
|
||||
placeholder="CCY"
|
||||
placeholder='CCY'
|
||||
value={collateralReserve}
|
||||
onChange={(item) => {
|
||||
if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||
setCollateralReserve(item);
|
||||
}}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
>
|
||||
{renderReserveAccounts}
|
||||
</Select>
|
||||
) : (
|
||||
<TokenDisplay
|
||||
key={props.reserve.liquidityMint.toBase58()}
|
||||
name={getTokenName(
|
||||
tokenMap,
|
||||
props.reserve.liquidityMint.toBase58()
|
||||
)}
|
||||
name={getTokenName(tokenMap, props.reserve.liquidityMint.toBase58())}
|
||||
mintAddress={props.reserve.liquidityMint.toBase58()}
|
||||
showBalance={false}
|
||||
/>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from "react";
|
||||
import { useLendingReserves, UserDeposit, useUserDeposits } from "../../hooks";
|
||||
import { LendingMarket, LendingReserve } from "../../models";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import { formatAmount, getTokenName } from "../../utils/utils";
|
||||
import { Select } from "antd";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||
import React from 'react';
|
||||
import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks';
|
||||
import { LendingMarket, LendingReserve } from '../../models';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
import { formatAmount, getTokenName } from 'common/src/utils/utils';
|
||||
import { Select } from 'antd';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -19,12 +19,10 @@ export const CollateralItem = (props: {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<TokenIcon mintAddress={mint} />
|
||||
{name}
|
||||
<span className="token-balance">
|
||||
{userDeposit ? formatAmount(userDeposit.info.amount) : "--"}
|
||||
</span>
|
||||
<span className='token-balance'> {userDeposit ? formatAmount(userDeposit.info.amount) : '--'}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -40,22 +38,19 @@ export const CollateralSelector = (props: {
|
|||
const { tokenMap } = useConnectionConfig();
|
||||
const { userDeposits } = useUserDeposits();
|
||||
|
||||
const market = cache.get(props.reserve?.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const market = cache.get(props.reserve?.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
if (!market) return null;
|
||||
|
||||
const quoteMintAddress = market?.info?.quoteMint?.toBase58();
|
||||
|
||||
const onlyQuoteAllowed =
|
||||
props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress;
|
||||
const onlyQuoteAllowed = props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress;
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="large"
|
||||
size='large'
|
||||
showSearch
|
||||
style={{ minWidth: 300, margin: "5px 0px" }}
|
||||
placeholder="Collateral"
|
||||
style={{ minWidth: 300, margin: '5px 0px' }}
|
||||
placeholder='Collateral'
|
||||
value={props.collateralReserve}
|
||||
disabled={props.disabled}
|
||||
defaultValue={props.collateralReserve}
|
||||
|
@ -64,17 +59,11 @@ export const CollateralSelector = (props: {
|
|||
props.onCollateralReserve(item);
|
||||
}
|
||||
}}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
>
|
||||
{reserveAccounts
|
||||
.filter((reserve) => reserve.info !== props.reserve)
|
||||
.filter(
|
||||
(reserve) =>
|
||||
!onlyQuoteAllowed ||
|
||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
||||
)
|
||||
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint))
|
||||
.map((reserve) => {
|
||||
const mint = reserve.info.liquidityMint.toBase58();
|
||||
const address = reserve.pubkey.toBase58();
|
||||
|
@ -84,9 +73,7 @@ export const CollateralSelector = (props: {
|
|||
<Option key={address} value={address} name={name} title={address}>
|
||||
<CollateralItem
|
||||
reserve={reserve}
|
||||
userDeposit={userDeposits.find(
|
||||
(dep) => dep.reserve.pubkey.toBase58() === address
|
||||
)}
|
||||
userDeposit={userDeposits.find((dep) => dep.reserve.pubkey.toBase58() === address)}
|
||||
mint={mint}
|
||||
name={name}
|
||||
/>
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
import { Button } from "antd";
|
||||
import { ButtonProps } from "antd/lib/button";
|
||||
import React from "react";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { LABELS } from "./../../constants";
|
||||
import { Button } from 'antd';
|
||||
import { ButtonProps } from 'antd/lib/button';
|
||||
import React from 'react';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { LABELS } from './../../constants';
|
||||
|
||||
export const ConnectButton = (
|
||||
props: ButtonProps & React.RefAttributes<HTMLElement>
|
||||
) => {
|
||||
export const ConnectButton = (props: ButtonProps & React.RefAttributes<HTMLElement>) => {
|
||||
const { wallet, connected } = useWallet();
|
||||
const { onClick, children, disabled, ...rest } = props;
|
||||
return (
|
||||
<Button
|
||||
{...rest}
|
||||
onClick={connected ? onClick : wallet.connect}
|
||||
disabled={connected && disabled}
|
||||
>
|
||||
<Button {...rest} onClick={connected ? onClick : wallet.connect} disabled={connected && disabled}>
|
||||
{connected ? props.children : LABELS.CONNECT_LABEL}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { formatNumber, shortenAddress } from "../../utils/utils";
|
||||
import { Identicon } from "../Identicon";
|
||||
import { useNativeAccount } from "../../contexts/accounts";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import React from 'react';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { formatNumber, shortenAddress } from 'common/src/utils/utils';
|
||||
import { Identicon } from '../Identicon';
|
||||
import { useNativeAccount } from 'common/src/contexts/accounts';
|
||||
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||
|
||||
export const CurrentUserBadge = (props: {}) => {
|
||||
const { wallet } = useWallet();
|
||||
|
@ -16,16 +16,11 @@ export const CurrentUserBadge = (props: {}) => {
|
|||
// should use SOL ◎ ?
|
||||
|
||||
return (
|
||||
<div className="wallet-wrapper">
|
||||
<span>
|
||||
{formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL
|
||||
</span>
|
||||
<div className="wallet-key">
|
||||
<div className='wallet-wrapper'>
|
||||
<span>{formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL</span>
|
||||
<div className='wallet-key'>
|
||||
{shortenAddress(`${wallet.publicKey}`)}
|
||||
<Identicon
|
||||
address={wallet.publicKey?.toBase58()}
|
||||
style={{ marginLeft: "0.5rem", display: "flex" }}
|
||||
/>
|
||||
<Identicon address={wallet.publicKey?.toBase58()} style={{ marginLeft: '0.5rem', display: 'flex' }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,58 +1,39 @@
|
|||
import React, { useMemo } from "react";
|
||||
import {
|
||||
useTokenName,
|
||||
useUserBalance,
|
||||
useUserCollateralBalance,
|
||||
} from "./../../hooks";
|
||||
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
|
||||
import { formatNumber, formatPct } from "../../utils/utils";
|
||||
import { Card, Col, Row, Statistic } from "antd";
|
||||
import "./style.less";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { GUTTER } from "../../constants";
|
||||
import React, { useMemo } from 'react';
|
||||
import { useUserBalance, useUserCollateralBalance } from './../../hooks';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import { calculateDepositAPY, LendingReserve } from '../../models/lending';
|
||||
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||
import { Card, Col, Row, Statistic } from 'antd';
|
||||
import './style.less';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { GUTTER } from '../../constants';
|
||||
|
||||
export const DepositInfoLine = (props: {
|
||||
className?: string;
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const DepositInfoLine = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||
const name = useTokenName(props.reserve.liquidityMint);
|
||||
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
|
||||
const { balance: collateralBalance } = useUserCollateralBalance(
|
||||
props.reserve
|
||||
);
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
|
||||
props.reserve,
|
||||
]);
|
||||
const { balance: collateralBalance } = useUserCollateralBalance(props.reserve);
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [props.reserve]);
|
||||
|
||||
return (
|
||||
<Row gutter={GUTTER}>
|
||||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic
|
||||
title="Your balance in Oyster"
|
||||
value={formatNumber.format(collateralBalance)}
|
||||
suffix={name}
|
||||
/>
|
||||
<Statistic title='Your balance in Oyster' value={formatNumber.format(collateralBalance)} suffix={name} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic
|
||||
title="Your wallet balance"
|
||||
value={formatNumber.format(tokenBalance)}
|
||||
suffix={name}
|
||||
/>
|
||||
<Statistic title='Your wallet balance' value={formatNumber.format(tokenBalance)} suffix={name} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic title="Health Factor" value="--" />
|
||||
<Statistic title='Health Factor' value='--' />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} xl={9}>
|
||||
<Card className={props.className}>
|
||||
<Statistic title="APY" value={formatPct.format(depositAPY)} />
|
||||
<Statistic title='APY' value={formatPct.format(depositAPY)} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import { InputType, useSliderInput, useUserBalance } from "../../hooks";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { Card, Slider } from "antd";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { deposit } from "../../actions/deposit";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import "./style.less";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
import { LABELS, marks } from "../../constants";
|
||||
import { ConnectButton } from "../ConnectButton";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||
import { LendingReserve } from '../../models/lending';
|
||||
import { Card, Slider } from 'antd';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { deposit } from '../../actions/deposit';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import './style.less';
|
||||
import { ActionConfirmation } from './../ActionConfirmation';
|
||||
import { LABELS, marks } from '../../constants';
|
||||
import { ConnectButton } from '../ConnectButton';
|
||||
import CollateralInput from '../CollateralInput';
|
||||
|
||||
export const DepositInput = (props: {
|
||||
className?: string;
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const DepositInput = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
|
@ -25,13 +21,11 @@ export const DepositInput = (props: {
|
|||
const reserve = props.reserve;
|
||||
const address = props.address;
|
||||
|
||||
const { accounts: fromAccounts, balance, balanceLamports } = useUserBalance(
|
||||
reserve?.liquidityMint
|
||||
);
|
||||
const { accounts: fromAccounts, balance, balanceLamports } = useUserBalance(reserve?.liquidityMint);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
if (typeof val === "string") {
|
||||
if (typeof val === 'string') {
|
||||
return (parseFloat(val) / balance) * 100;
|
||||
} else {
|
||||
return (val * balance) / 100;
|
||||
|
@ -58,7 +52,7 @@ export const DepositInput = (props: {
|
|||
wallet
|
||||
);
|
||||
|
||||
setValue("");
|
||||
setValue('');
|
||||
setShowConfirmation(true);
|
||||
} catch {
|
||||
// TODO:
|
||||
|
@ -66,26 +60,14 @@ export const DepositInput = (props: {
|
|||
setPendingTx(false);
|
||||
}
|
||||
})();
|
||||
}, [
|
||||
connection,
|
||||
setValue,
|
||||
balanceLamports,
|
||||
balance,
|
||||
wallet,
|
||||
value,
|
||||
pct,
|
||||
type,
|
||||
reserve,
|
||||
fromAccounts,
|
||||
address,
|
||||
]);
|
||||
}, [connection, setValue, balanceLamports, balance, wallet, value, pct, type, reserve, fromAccounts, address]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -95,26 +77,26 @@ export const DepositInput = (props: {
|
|||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<div className="deposit-input-title">{LABELS.DEPOSIT_QUESTION}</div>
|
||||
<div className='deposit-input-title'>{LABELS.DEPOSIT_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Amount"
|
||||
title='Amount'
|
||||
reserve={reserve}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setValue(val?.toString() || '');
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
|
@ -124,8 +106,8 @@ export const DepositInput = (props: {
|
|||
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||
|
||||
<ConnectButton
|
||||
size="large"
|
||||
type="primary"
|
||||
size='large'
|
||||
type='primary'
|
||||
onClick={onDeposit}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import "./../../App.less";
|
||||
import { Menu } from "antd";
|
||||
import React from 'react';
|
||||
import './../../App.less';
|
||||
import { Menu } from 'antd';
|
||||
import {
|
||||
PieChartOutlined,
|
||||
GithubOutlined,
|
||||
|
@ -11,103 +11,97 @@ import {
|
|||
RocketOutlined,
|
||||
ForkOutlined,
|
||||
// LineChartOutlined
|
||||
} from "@ant-design/icons";
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import BasicLayout from "@ant-design/pro-layout";
|
||||
import { AppBar } from "./../AppBar";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import { LABELS } from "../../constants";
|
||||
import config from "./../../../package.json";
|
||||
import BasicLayout from '@ant-design/pro-layout';
|
||||
import { AppBar } from './../AppBar';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { LABELS } from '../../constants';
|
||||
import config from './../../../package.json';
|
||||
|
||||
export const AppLayout = React.memo((props: any) => {
|
||||
const { env } = useConnectionConfig();
|
||||
const location = useLocation();
|
||||
|
||||
const paths: { [key: string]: string } = {
|
||||
"/dashboard": "2",
|
||||
"/deposit": "3",
|
||||
"/borrow": "4",
|
||||
"/liquidate": "5",
|
||||
"/margin": "6",
|
||||
"/faucet": "7",
|
||||
'/dashboard': '2',
|
||||
'/deposit': '3',
|
||||
'/borrow': '4',
|
||||
'/liquidate': '5',
|
||||
'/margin': '6',
|
||||
'/faucet': '7',
|
||||
};
|
||||
|
||||
const current =
|
||||
[...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) ||
|
||||
"";
|
||||
const defaultKey = paths[current] || "1";
|
||||
const theme = "dark";
|
||||
const current = [...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) || '';
|
||||
const defaultKey = paths[current] || '1';
|
||||
const theme = 'dark';
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="Banner">
|
||||
<div className="Banner-description">{LABELS.AUDIT_WARNING}</div>
|
||||
<div className='App'>
|
||||
<div className='Banner'>
|
||||
<div className='Banner-description'>{LABELS.AUDIT_WARNING}</div>
|
||||
</div>
|
||||
<BasicLayout
|
||||
title={LABELS.APP_TITLE}
|
||||
footerRender={() => (
|
||||
<div className="footer" title={LABELS.FOOTER}>
|
||||
<div className='footer' title={LABELS.FOOTER}>
|
||||
{LABELS.FOOTER}
|
||||
</div>
|
||||
)}
|
||||
navTheme={theme}
|
||||
headerTheme={theme}
|
||||
theme={theme}
|
||||
layout="mix"
|
||||
layout='mix'
|
||||
fixSiderbar={true}
|
||||
primaryColor="#d83aeb"
|
||||
logo={<div className="App-logo" />}
|
||||
primaryColor='#d83aeb'
|
||||
logo={<div className='App-logo' />}
|
||||
rightContentRender={() => <AppBar />}
|
||||
links={[]}
|
||||
menuContentRender={() => {
|
||||
return (
|
||||
<div className="links">
|
||||
<Menu
|
||||
theme={theme}
|
||||
defaultSelectedKeys={[defaultKey]}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item key="1" icon={<HomeOutlined />}>
|
||||
<div className='links'>
|
||||
<Menu theme={theme} defaultSelectedKeys={[defaultKey]} mode='inline'>
|
||||
<Menu.Item key='1' icon={<HomeOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/",
|
||||
pathname: '/',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_HOME}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<PieChartOutlined />}>
|
||||
<Menu.Item key='2' icon={<PieChartOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/dashboard",
|
||||
pathname: '/dashboard',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_DASHBOARD}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<BankOutlined />}>
|
||||
<Menu.Item key='3' icon={<BankOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/deposit",
|
||||
pathname: '/deposit',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_DEPOSIT}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="4" icon={<LogoutOutlined />}>
|
||||
<Menu.Item key='4' icon={<LogoutOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/borrow",
|
||||
pathname: '/borrow',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_BORROW}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="5" icon={<ShoppingOutlined />}>
|
||||
<Menu.Item key='5' icon={<ShoppingOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/liquidate",
|
||||
pathname: '/liquidate',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_LIQUIDATE}
|
||||
|
@ -123,11 +117,11 @@ export const AppLayout = React.memo((props: any) => {
|
|||
{LABELS.MARGIN_TRADING}
|
||||
</Link>
|
||||
</Menu.Item> */}
|
||||
{env !== "mainnet-beta" && (
|
||||
<Menu.Item key="7" icon={<RocketOutlined />}>
|
||||
{env !== 'mainnet-beta' && (
|
||||
<Menu.Item key='7' icon={<RocketOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: "/faucet",
|
||||
pathname: '/faucet',
|
||||
}}
|
||||
>
|
||||
{LABELS.MENU_FAUCET}
|
||||
|
@ -139,27 +133,17 @@ export const AppLayout = React.memo((props: any) => {
|
|||
theme={theme}
|
||||
defaultSelectedKeys={[defaultKey]}
|
||||
selectable={false}
|
||||
mode="inline"
|
||||
className="bottom-links"
|
||||
>
|
||||
<Menu.Item key="16" icon={<ForkOutlined />}>
|
||||
<a
|
||||
title="Fork"
|
||||
href={`${config.repository.url}/fork`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
mode='inline'
|
||||
className='bottom-links'
|
||||
>
|
||||
<Menu.Item key='16' icon={<ForkOutlined />}>
|
||||
<a title='Fork' href={`${config.repository.url}/fork`} target='_blank' rel='noopener noreferrer'>
|
||||
Fork
|
||||
</a>
|
||||
</Menu.Item>
|
||||
,
|
||||
<Menu.Item key="15" icon={<GithubOutlined />}>
|
||||
<a
|
||||
title="Gtihub"
|
||||
href={config.repository.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Menu.Item key='15' icon={<GithubOutlined />}>
|
||||
<a title='Gtihub' href={config.repository.url} target='_blank' rel='noopener noreferrer'>
|
||||
Github
|
||||
</a>
|
||||
</Menu.Item>
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
import { Slider } from "antd";
|
||||
import Card from "antd/lib/card";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { LABELS, marks } from "../../constants";
|
||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
||||
import {
|
||||
EnrichedLendingObligation,
|
||||
InputType,
|
||||
useSliderInput,
|
||||
useUserBalance,
|
||||
} from "../../hooks";
|
||||
import { LendingReserve } from "../../models";
|
||||
import { ActionConfirmation } from "../ActionConfirmation";
|
||||
import { liquidate } from "../../actions";
|
||||
import "./style.less";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import { notify } from "../../utils/notifications";
|
||||
import { ConnectButton } from "../ConnectButton";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
import { Slider } from 'antd';
|
||||
import Card from 'antd/lib/card';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { LABELS, marks } from '../../constants';
|
||||
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||
import { EnrichedLendingObligation, InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||
import { LendingReserve } from '../../models';
|
||||
import { ActionConfirmation } from '../ActionConfirmation';
|
||||
import { liquidate } from '../../actions';
|
||||
import './style.less';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import CollateralInput from '../CollateralInput';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { ConnectButton } from '../ConnectButton';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
|
||||
export const LiquidateInput = (props: {
|
||||
className?: string;
|
||||
|
@ -31,26 +26,22 @@ export const LiquidateInput = (props: {
|
|||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const { repayReserve, withdrawReserve, obligation } = props;
|
||||
const [lastTyped, setLastTyped] = useState("liquidate");
|
||||
const [lastTyped, setLastTyped] = useState('liquidate');
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const [collateralValue, setCollateralValue] = useState("");
|
||||
const [collateralValue, setCollateralValue] = useState('');
|
||||
|
||||
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
||||
const { accounts: fromAccounts, balance: tokenBalance } = useUserBalance(
|
||||
repayReserve?.info.liquidityMint
|
||||
);
|
||||
const borrowAmountLamports = wadToLamports(
|
||||
obligation.info.borrowAmountWad
|
||||
).toNumber();
|
||||
const { accounts: fromAccounts, balance: tokenBalance } = useUserBalance(repayReserve?.info.liquidityMint);
|
||||
const borrowAmountLamports = wadToLamports(obligation.info.borrowAmountWad).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") {
|
||||
setLastTyped('liquidate');
|
||||
if (typeof val === 'string') {
|
||||
return (parseFloat(val) / minAmount) * 100;
|
||||
} else {
|
||||
return (val * minAmount) / 100;
|
||||
|
@ -73,9 +64,7 @@ export const LiquidateInput = (props: {
|
|||
const toLiquidateLamports =
|
||||
type === InputType.Percent && tokenBalance >= borrowAmount
|
||||
? (pct * borrowAmountLamports) / 100
|
||||
: Math.ceil(
|
||||
borrowAmountLamports * (parseFloat(value) / borrowAmount)
|
||||
);
|
||||
: Math.ceil(borrowAmountLamports * (parseFloat(value) / borrowAmount));
|
||||
await liquidate(
|
||||
connection,
|
||||
wallet,
|
||||
|
@ -87,14 +76,14 @@ export const LiquidateInput = (props: {
|
|||
withdrawReserve
|
||||
);
|
||||
|
||||
setValue("");
|
||||
setCollateralValue("");
|
||||
setValue('');
|
||||
setCollateralValue('');
|
||||
setShowConfirmation(true);
|
||||
} catch (error) {
|
||||
// TODO:
|
||||
notify({
|
||||
message: "Unable to liquidate loan.",
|
||||
type: "error",
|
||||
message: 'Unable to liquidate loan.',
|
||||
type: 'error',
|
||||
description: error.message,
|
||||
});
|
||||
} finally {
|
||||
|
@ -117,12 +106,10 @@ export const LiquidateInput = (props: {
|
|||
type,
|
||||
]);
|
||||
|
||||
const collateralPrice = useMidPriceInUSD(
|
||||
withdrawReserve?.info.liquidityMint.toBase58()
|
||||
)?.price;
|
||||
const collateralPrice = useMidPriceInUSD(withdrawReserve?.info.liquidityMint.toBase58())?.price;
|
||||
|
||||
useEffect(() => {
|
||||
if (withdrawReserve && lastTyped === "liquidate") {
|
||||
if (withdrawReserve && lastTyped === 'liquidate') {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (value) {
|
||||
|
@ -130,29 +117,21 @@ export const LiquidateInput = (props: {
|
|||
const collateralAmount = (borrowRatio * collateral) / 100;
|
||||
setCollateralValue(collateralAmount.toString());
|
||||
} else {
|
||||
setCollateralValue("");
|
||||
setCollateralValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
borrowAmount,
|
||||
collateralPrice,
|
||||
withdrawReserve,
|
||||
lastTyped,
|
||||
obligation.info.collateralInQuote,
|
||||
value,
|
||||
]);
|
||||
}, [borrowAmount, collateralPrice, withdrawReserve, lastTyped, obligation.info.collateralInQuote, value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (withdrawReserve && lastTyped === "collateral") {
|
||||
if (withdrawReserve && lastTyped === 'collateral') {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (collateralValue) {
|
||||
const collateralRatio =
|
||||
(parseFloat(collateralValue) / collateral) * 100;
|
||||
const collateralRatio = (parseFloat(collateralValue) / collateral) * 100;
|
||||
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
||||
setValue(borrowValue.toString());
|
||||
} else {
|
||||
setValue("");
|
||||
setValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
@ -167,11 +146,11 @@ export const LiquidateInput = (props: {
|
|||
|
||||
if (!withdrawReserve) return null;
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -181,27 +160,27 @@ export const LiquidateInput = (props: {
|
|||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<div className="repay-input-title">{LABELS.LIQUIDATE_QUESTION}</div>
|
||||
<div className='repay-input-title'>{LABELS.LIQUIDATE_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Liquidate Amount"
|
||||
title='Liquidate Amount'
|
||||
reserve={repayReserve.info}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setLastTyped("liquidate");
|
||||
setValue(val?.toString() || '');
|
||||
setLastTyped('liquidate');
|
||||
}}
|
||||
disabled={true}
|
||||
useWalletBalance={true}
|
||||
|
@ -210,28 +189,28 @@ export const LiquidateInput = (props: {
|
|||
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Collateral Amount (estimated)"
|
||||
title='Collateral Amount (estimated)'
|
||||
reserve={withdrawReserve?.info}
|
||||
amount={parseFloat(collateralValue) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setCollateralValue(val?.toString() || "");
|
||||
setLastTyped("collateral");
|
||||
setCollateralValue(val?.toString() || '');
|
||||
setLastTyped('collateral');
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
/>
|
||||
</div>
|
||||
<ConnectButton
|
||||
type="primary"
|
||||
size="large"
|
||||
type='primary'
|
||||
size='large'
|
||||
onClick={onLiquidate}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
import { Card, Col, Row, Statistic } from "antd";
|
||||
import {
|
||||
formatNumber,
|
||||
formatPct,
|
||||
fromLamports,
|
||||
wadToLamports,
|
||||
} from "../../utils/utils";
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
EnrichedLendingObligation,
|
||||
useLendingReserve,
|
||||
useTokenName,
|
||||
} from "../../hooks";
|
||||
import { useMint } from "../../contexts/accounts";
|
||||
import { calculateBorrowAPY, collateralToLiquidity } from "../../models";
|
||||
import { GUTTER } from "../../constants";
|
||||
import { Card, Col, Row, Statistic } from 'antd';
|
||||
import { formatNumber, formatPct, fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EnrichedLendingObligation, useLendingReserve } from '../../hooks';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { calculateBorrowAPY, collateralToLiquidity } from '../../models';
|
||||
import { GUTTER } from '../../constants';
|
||||
|
||||
export const LoanInfoLine = (props: {
|
||||
className?: string;
|
||||
obligation: EnrichedLendingObligation;
|
||||
}) => {
|
||||
export const LoanInfoLine = (props: { className?: string; obligation: EnrichedLendingObligation }) => {
|
||||
const obligation = props.obligation;
|
||||
|
||||
const repayReserve = useLendingReserve(obligation?.info.borrowReserve);
|
||||
|
@ -29,21 +18,12 @@ export const LoanInfoLine = (props: {
|
|||
const repayName = useTokenName(repayReserve?.info.liquidityMint);
|
||||
const withdrawName = useTokenName(withdrawReserve?.info.liquidityMint);
|
||||
|
||||
const borrowAPY = useMemo(
|
||||
() => (repayReserve ? calculateBorrowAPY(repayReserve?.info) : 0),
|
||||
[repayReserve]
|
||||
);
|
||||
const borrowAPY = useMemo(() => (repayReserve ? calculateBorrowAPY(repayReserve?.info) : 0), [repayReserve]);
|
||||
if (!obligation || !repayReserve) {
|
||||
return null;
|
||||
}
|
||||
const borrowAmount = fromLamports(
|
||||
wadToLamports(obligation?.info.borrowAmountWad),
|
||||
liquidityMint
|
||||
);
|
||||
const collateralLamports = collateralToLiquidity(
|
||||
obligation?.info.depositedCollateral,
|
||||
repayReserve.info
|
||||
);
|
||||
const borrowAmount = fromLamports(wadToLamports(obligation?.info.borrowAmountWad), liquidityMint);
|
||||
const collateralLamports = collateralToLiquidity(obligation?.info.depositedCollateral, repayReserve.info);
|
||||
const collateral = fromLamports(collateralLamports, collateralMint);
|
||||
|
||||
return (
|
||||
|
@ -51,16 +31,14 @@ export const LoanInfoLine = (props: {
|
|||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic
|
||||
title="Loan Balance"
|
||||
title='Loan Balance'
|
||||
value={obligation.info.borrowedInQuote}
|
||||
formatter={(val) => (
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(borrowAmount)}</em> {repayName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(parseFloat(val.toString()))}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(parseFloat(val.toString()))}</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
@ -69,16 +47,14 @@ export const LoanInfoLine = (props: {
|
|||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic
|
||||
title="Collateral"
|
||||
title='Collateral'
|
||||
value={obligation.info.collateralInQuote}
|
||||
formatter={(val) => (
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateral)}</em> {withdrawName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(parseFloat(val.toString()))}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(parseFloat(val.toString()))}</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
@ -86,15 +62,12 @@ export const LoanInfoLine = (props: {
|
|||
</Col>
|
||||
<Col xs={24} xl={5}>
|
||||
<Card className={props.className}>
|
||||
<Statistic title="APY" value={formatPct.format(borrowAPY)} />
|
||||
<Statistic title='APY' value={formatPct.format(borrowAPY)} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} xl={9}>
|
||||
<Card className={props.className}>
|
||||
<Statistic
|
||||
title="Health Factor"
|
||||
value={obligation.info.health.toFixed(2)}
|
||||
/>
|
||||
<Statistic title='Health Factor' value={obligation.info.health.toFixed(2)} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { Card, Row, Col } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useMint } from "../../contexts/accounts";
|
||||
import { useEnrichedPools } from "../../contexts/market";
|
||||
import { useUserAccounts } from "../../hooks";
|
||||
import { PoolInfo } from "../../models";
|
||||
import { formatPriceNumber } from "../../utils/utils";
|
||||
import { Card, Row, Col } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
import { useUserAccounts } from 'common/src/hooks';
|
||||
import { PoolInfo } from '../../models';
|
||||
import { formatPriceNumber } from 'common/src/utils/utils';
|
||||
|
||||
export const PoolPrice = (props: { pool: PoolInfo }) => {
|
||||
const pool = props.pool;
|
||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [
|
||||
props.pool,
|
||||
]);
|
||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [props.pool]);
|
||||
const enriched = useEnrichedPools(pools)[0];
|
||||
|
||||
const { userAccounts } = useUserAccounts();
|
||||
|
@ -19,37 +17,32 @@ export const PoolPrice = (props: { pool: PoolInfo }) => {
|
|||
const ratio =
|
||||
userAccounts
|
||||
.filter((f) => pool.pubkeys.mint.equals(f.info.mint))
|
||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) /
|
||||
(lpMint?.supply.toNumber() || 0);
|
||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) / (lpMint?.supply.toNumber() || 0);
|
||||
|
||||
if (!enriched) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
className="ccy-input"
|
||||
style={{ borderRadius: 20, width: "100%" }}
|
||||
bodyStyle={{ padding: "7px" }}
|
||||
size="small"
|
||||
title="Prices and pool share"
|
||||
className='ccy-input'
|
||||
style={{ borderRadius: 20, width: '100%' }}
|
||||
bodyStyle={{ padding: '7px' }}
|
||||
size='small'
|
||||
title='Prices and pool share'
|
||||
>
|
||||
<Row style={{ width: "100%" }}>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(
|
||||
parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB)
|
||||
)}
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB))}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(
|
||||
parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA)
|
||||
)}
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA))}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{ratio * 100 < 0.001 && ratio > 0 ? "<" : ""}
|
||||
{ratio * 100 < 0.001 && ratio > 0 ? '<' : ''}
|
||||
{formatPriceNumber.format(ratio * 100)}%
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ width: "100%" }}>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={8}>
|
||||
{enriched.names[0]} per {enriched.names[1]}
|
||||
</Col>
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
EnrichedLendingObligation,
|
||||
InputType,
|
||||
useAccountByMint,
|
||||
useSliderInput,
|
||||
useUserBalance,
|
||||
} from "../../hooks";
|
||||
import { LendingReserve } from "../../models";
|
||||
import { Card, Slider } from "antd";
|
||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { repay } from "../../actions";
|
||||
import "./style.less";
|
||||
import { LABELS, marks } from "../../constants";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
||||
import { notify } from "../../utils/notifications";
|
||||
import { ConnectButton } from "../ConnectButton";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { EnrichedLendingObligation, InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||
import { useAccountByMint } from 'common/src/hooks';
|
||||
import { LendingReserve } from '../../models';
|
||||
import { Card, Slider } from 'antd';
|
||||
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { repay } from '../../actions';
|
||||
import './style.less';
|
||||
import { LABELS, marks } from '../../constants';
|
||||
import { ActionConfirmation } from './../ActionConfirmation';
|
||||
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import { notify } from 'common/src/utils/notifications';
|
||||
import { ConnectButton } from '../ConnectButton';
|
||||
import CollateralInput from '../CollateralInput';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
|
||||
export const RepayInput = (props: {
|
||||
className?: string;
|
||||
|
@ -29,36 +24,30 @@ export const RepayInput = (props: {
|
|||
}) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [lastTyped, setLastTyped] = useState("repay");
|
||||
const [lastTyped, setLastTyped] = useState('repay');
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const [collateralValue, setCollateralValue] = useState("");
|
||||
const [collateralValue, setCollateralValue] = useState('');
|
||||
|
||||
const repayReserve = props.borrowReserve;
|
||||
const obligation = props.obligation;
|
||||
|
||||
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
||||
const { balance: tokenBalance } = useUserBalance(
|
||||
repayReserve.info.liquidityMint
|
||||
);
|
||||
const { balance: tokenBalance } = useUserBalance(repayReserve.info.liquidityMint);
|
||||
|
||||
const borrowAmountLamports = wadToLamports(
|
||||
obligation.info.borrowAmountWad
|
||||
).toNumber();
|
||||
const borrowAmountLamports = wadToLamports(obligation.info.borrowAmountWad).toNumber();
|
||||
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
||||
const collateralReserve = props.collateralReserve;
|
||||
|
||||
const { accounts: fromAccounts } = useUserBalance(
|
||||
repayReserve.info.liquidityMint
|
||||
);
|
||||
const { accounts: fromAccounts } = useUserBalance(repayReserve.info.liquidityMint);
|
||||
|
||||
const obligationAccount = useAccountByMint(obligation?.info.tokenMint);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
||||
setLastTyped("repay");
|
||||
if (typeof val === "string") {
|
||||
setLastTyped('repay');
|
||||
if (typeof val === 'string') {
|
||||
return (parseFloat(val) / minAmount) * 100;
|
||||
} else {
|
||||
return (val * minAmount) / 100;
|
||||
|
@ -70,12 +59,7 @@ export const RepayInput = (props: {
|
|||
const { value, setValue, pct, setPct, type } = useSliderInput(convert);
|
||||
|
||||
const onRepay = useCallback(() => {
|
||||
if (
|
||||
!collateralReserve ||
|
||||
!obligation ||
|
||||
!repayReserve ||
|
||||
!obligationAccount
|
||||
) {
|
||||
if (!collateralReserve || !obligation || !repayReserve || !obligationAccount) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,9 +70,7 @@ export const RepayInput = (props: {
|
|||
const toRepayLamports =
|
||||
type === InputType.Percent
|
||||
? (pct * borrowAmountLamports) / 100
|
||||
: Math.ceil(
|
||||
borrowAmountLamports * (parseFloat(value) / borrowAmount)
|
||||
);
|
||||
: Math.ceil(borrowAmountLamports * (parseFloat(value) / borrowAmount));
|
||||
await repay(
|
||||
fromAccounts[0],
|
||||
toRepayLamports,
|
||||
|
@ -100,13 +82,13 @@ export const RepayInput = (props: {
|
|||
wallet
|
||||
);
|
||||
|
||||
setValue("");
|
||||
setCollateralValue("");
|
||||
setValue('');
|
||||
setCollateralValue('');
|
||||
setShowConfirmation(true);
|
||||
} catch (error) {
|
||||
notify({
|
||||
message: "Unable to repay loan.",
|
||||
type: "error",
|
||||
message: 'Unable to repay loan.',
|
||||
type: 'error',
|
||||
description: error.message,
|
||||
});
|
||||
} finally {
|
||||
|
@ -129,12 +111,10 @@ export const RepayInput = (props: {
|
|||
wallet,
|
||||
]);
|
||||
|
||||
const collateralPrice = useMidPriceInUSD(
|
||||
collateralReserve?.info.liquidityMint.toBase58()
|
||||
)?.price;
|
||||
const collateralPrice = useMidPriceInUSD(collateralReserve?.info.liquidityMint.toBase58())?.price;
|
||||
|
||||
useEffect(() => {
|
||||
if (collateralReserve && lastTyped === "repay") {
|
||||
if (collateralReserve && lastTyped === 'repay') {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (value) {
|
||||
|
@ -142,29 +122,21 @@ export const RepayInput = (props: {
|
|||
const collateralAmount = (borrowRatio * collateral) / 100;
|
||||
setCollateralValue(collateralAmount.toString());
|
||||
} else {
|
||||
setCollateralValue("");
|
||||
setCollateralValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
borrowAmount,
|
||||
collateralPrice,
|
||||
collateralReserve,
|
||||
lastTyped,
|
||||
obligation.info.collateralInQuote,
|
||||
value,
|
||||
]);
|
||||
}, [borrowAmount, collateralPrice, collateralReserve, lastTyped, obligation.info.collateralInQuote, value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (collateralReserve && lastTyped === "collateral") {
|
||||
if (collateralReserve && lastTyped === 'collateral') {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (collateralValue) {
|
||||
const collateralRatio =
|
||||
(parseFloat(collateralValue) / collateral) * 100;
|
||||
const collateralRatio = (parseFloat(collateralValue) / collateral) * 100;
|
||||
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
||||
setValue(borrowValue.toString());
|
||||
} else {
|
||||
setValue("");
|
||||
setValue('');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
@ -178,11 +150,11 @@ export const RepayInput = (props: {
|
|||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -192,27 +164,27 @@ export const RepayInput = (props: {
|
|||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<div className="repay-input-title">{LABELS.REPAY_QUESTION}</div>
|
||||
<div className='repay-input-title'>{LABELS.REPAY_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Repay Amount"
|
||||
title='Repay Amount'
|
||||
reserve={repayReserve.info}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setLastTyped("repay");
|
||||
setValue(val?.toString() || '');
|
||||
setLastTyped('repay');
|
||||
}}
|
||||
disabled={true}
|
||||
useWalletBalance={true}
|
||||
|
@ -221,28 +193,28 @@ export const RepayInput = (props: {
|
|||
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Collateral Amount (estimated)"
|
||||
title='Collateral Amount (estimated)'
|
||||
reserve={collateralReserve?.info}
|
||||
amount={parseFloat(collateralValue) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setCollateralValue(val?.toString() || "");
|
||||
setLastTyped("collateral");
|
||||
setCollateralValue(val?.toString() || '');
|
||||
setLastTyped('collateral');
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
/>
|
||||
</div>
|
||||
<ConnectButton
|
||||
type="primary"
|
||||
size="large"
|
||||
type='primary'
|
||||
size='large'
|
||||
onClick={onRepay}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
|
|
|
@ -1,51 +1,38 @@
|
|||
import React from "react";
|
||||
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
|
||||
import { Card, Col, Row, Statistic } from "antd";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import "./style.less";
|
||||
import { GUTTER, LABELS } from "../../constants";
|
||||
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
|
||||
import { useMemo } from "react";
|
||||
import { formatNumber, fromLamports, wadToLamports } from "../../utils/utils";
|
||||
import { useMint } from "../../contexts/accounts";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import React from 'react';
|
||||
import { calculateDepositAPY, LendingReserve } from '../../models/lending';
|
||||
import { Card, Col, Row, Statistic } from 'antd';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import './style.less';
|
||||
import { GUTTER, LABELS } from '../../constants';
|
||||
import { ReserveUtilizationChart } from './../../components/ReserveUtilizationChart';
|
||||
import { useMemo } from 'react';
|
||||
import { formatNumber, fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
|
||||
export const ReserveStatus = (props: {
|
||||
className?: string;
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const ReserveStatus = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
||||
const liquidityMint = useMint(mintAddress);
|
||||
const { price } = useMidPriceInUSD(mintAddress);
|
||||
const availableLiquidity = fromLamports(
|
||||
props.reserve.state.availableLiquidity,
|
||||
liquidityMint
|
||||
);
|
||||
const availableLiquidity = fromLamports(props.reserve.state.availableLiquidity, liquidityMint);
|
||||
|
||||
const availableLiquidityInUSD = price * availableLiquidity;
|
||||
|
||||
const totalBorrows = useMemo(
|
||||
() =>
|
||||
fromLamports(
|
||||
wadToLamports(props.reserve.state.borrowedLiquidityWad),
|
||||
liquidityMint
|
||||
),
|
||||
() => fromLamports(wadToLamports(props.reserve.state.borrowedLiquidityWad), liquidityMint),
|
||||
[props.reserve, liquidityMint]
|
||||
);
|
||||
|
||||
const totalBorrowsInUSD = price * totalBorrows;
|
||||
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
|
||||
props.reserve,
|
||||
]);
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [props.reserve]);
|
||||
|
||||
const liquidationThreshold = props.reserve.config.liquidationThreshold;
|
||||
const liquidationPenalty = props.reserve.config.liquidationBonus;
|
||||
|
@ -60,7 +47,7 @@ export const ReserveStatus = (props: {
|
|||
style={{
|
||||
marginRight: 0,
|
||||
marginTop: 0,
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
left: 15,
|
||||
}}
|
||||
mintAddress={mintAddress}
|
||||
|
@ -71,18 +58,16 @@ export const ReserveStatus = (props: {
|
|||
}
|
||||
bodyStyle={bodyStyle}
|
||||
>
|
||||
<div className="flexColumn">
|
||||
<div className='flexColumn'>
|
||||
<Row gutter={GUTTER}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="Available Liquidity"
|
||||
title='Available Liquidity'
|
||||
value={availableLiquidity}
|
||||
valueRender={(node) => (
|
||||
<div>
|
||||
{node}
|
||||
<div className="dashboard-amount-quote-stat">
|
||||
${formatNumber.format(availableLiquidityInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote-stat'>${formatNumber.format(availableLiquidityInUSD)}</div>
|
||||
</div>
|
||||
)}
|
||||
precision={2}
|
||||
|
@ -90,14 +75,12 @@ export const ReserveStatus = (props: {
|
|||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="Total Borrowed"
|
||||
title='Total Borrowed'
|
||||
value={totalBorrows}
|
||||
valueRender={(node) => (
|
||||
<div>
|
||||
{node}
|
||||
<div className="dashboard-amount-quote-stat">
|
||||
${formatNumber.format(totalBorrowsInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote-stat'>${formatNumber.format(totalBorrowsInUSD)}</div>
|
||||
</div>
|
||||
)}
|
||||
precision={2}
|
||||
|
@ -108,9 +91,9 @@ export const ReserveStatus = (props: {
|
|||
<Col
|
||||
span={24}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<ReserveUtilizationChart reserve={props.reserve} />
|
||||
|
@ -118,39 +101,33 @@ export const ReserveStatus = (props: {
|
|||
</Row>
|
||||
<Row gutter={GUTTER}>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title={LABELS.MAX_LTV}
|
||||
className="small-statisitc"
|
||||
value={maxLTV}
|
||||
precision={2}
|
||||
suffix="%"
|
||||
/>
|
||||
<Statistic title={LABELS.MAX_LTV} className='small-statisitc' value={maxLTV} precision={2} suffix='%' />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title={LABELS.LIQUIDATION_THRESHOLD}
|
||||
className="small-statisitc"
|
||||
className='small-statisitc'
|
||||
value={liquidationThreshold}
|
||||
precision={2}
|
||||
suffix="%"
|
||||
suffix='%'
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title={LABELS.LIQUIDATION_PENALTY}
|
||||
className="small-statisitc"
|
||||
className='small-statisitc'
|
||||
value={liquidationPenalty}
|
||||
precision={2}
|
||||
suffix="%"
|
||||
suffix='%'
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title={LABELS.TABLE_TITLE_DEPOSIT_APY}
|
||||
className="small-statisitc"
|
||||
className='small-statisitc'
|
||||
value={depositAPY * 100}
|
||||
precision={2}
|
||||
suffix="%"
|
||||
suffix='%'
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
||||
import { useMint } from "../../contexts/accounts";
|
||||
import { WaterWave } from "./../WaterWave";
|
||||
import { Statistic } from "antd";
|
||||
import React, { useMemo } from 'react';
|
||||
import { LendingReserve } from '../../models/lending';
|
||||
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { WaterWave } from './../WaterWave';
|
||||
import { Statistic } from 'antd';
|
||||
|
||||
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
||||
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
||||
const liquidityMint = useMint(mintAddress);
|
||||
const availableLiquidity = fromLamports(
|
||||
props.reserve.state.availableLiquidity,
|
||||
liquidityMint
|
||||
);
|
||||
const availableLiquidity = fromLamports(props.reserve.state.availableLiquidity, liquidityMint);
|
||||
|
||||
const totalBorrows = useMemo(
|
||||
() =>
|
||||
fromLamports(
|
||||
wadToLamports(props.reserve.state.borrowedLiquidityWad),
|
||||
liquidityMint
|
||||
),
|
||||
() => fromLamports(wadToLamports(props.reserve.state.borrowedLiquidityWad), liquidityMint),
|
||||
[props.reserve, liquidityMint]
|
||||
);
|
||||
|
||||
|
@ -29,14 +22,7 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
|||
<WaterWave
|
||||
style={{ height: 300 }}
|
||||
showPercent={false}
|
||||
title={
|
||||
<Statistic
|
||||
title="Utilization"
|
||||
value={percent}
|
||||
suffix="%"
|
||||
precision={2}
|
||||
/>
|
||||
}
|
||||
title={<Statistic title='Utilization' value={percent} suffix='%' precision={2} />}
|
||||
percent={percent}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { Select } from "antd";
|
||||
import { ENDPOINTS, useConnectionConfig } from "../../contexts/connection";
|
||||
import { useWallet, WALLET_PROVIDERS } from "../../contexts/wallet";
|
||||
import React from 'react';
|
||||
import { Select } from 'antd';
|
||||
import { ENDPOINTS, useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { useWallet, WALLET_PROVIDERS } from 'common/src/contexts/wallet';
|
||||
|
||||
export const Settings = () => {
|
||||
const { providerUrl, setProvider } = useWallet();
|
||||
|
@ -9,13 +9,9 @@ export const Settings = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: "grid" }}>
|
||||
Network:{" "}
|
||||
<Select
|
||||
onSelect={setEndpoint}
|
||||
value={endpoint}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
<div style={{ display: 'grid' }}>
|
||||
Network:{' '}
|
||||
<Select onSelect={setEndpoint} value={endpoint} style={{ marginRight: 8 }}>
|
||||
{ENDPOINTS.map(({ name, endpoint }) => (
|
||||
<Select.Option value={endpoint} key={endpoint}>
|
||||
{name}
|
||||
|
@ -23,8 +19,8 @@ export const Settings = () => {
|
|||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div style={{ display: "grid" }}>
|
||||
Wallet:{" "}
|
||||
<div style={{ display: 'grid' }}>
|
||||
Wallet:{' '}
|
||||
<Select onSelect={setProvider} value={providerUrl}>
|
||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||
<Select.Option value={url} key={url}>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import React from "react";
|
||||
import { useTokenName } from "./../../hooks";
|
||||
import React from 'react';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import {
|
||||
calculateBorrowAPY,
|
||||
calculateDepositAPY,
|
||||
calculateUtilizationRatio,
|
||||
LendingReserve,
|
||||
} from "../../models/lending";
|
||||
import { TokenIcon } from "../../components/TokenIcon";
|
||||
import { formatNumber, formatPct, fromLamports } from "../../utils/utils";
|
||||
import { Card, Typography } from "antd";
|
||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
||||
import { Link } from "react-router-dom";
|
||||
import { LABELS } from "../../constants";
|
||||
} from '../../models/lending';
|
||||
import { TokenIcon } from '../../components/TokenIcon';
|
||||
import { formatNumber, formatPct, fromLamports } from 'common/src/utils/utils';
|
||||
import { Card, Typography } from 'antd';
|
||||
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LABELS } from '../../constants';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export enum SideReserveOverviewMode {
|
||||
Deposit = "deposit",
|
||||
Borrow = "borrow",
|
||||
Deposit = 'deposit',
|
||||
Borrow = 'borrow',
|
||||
}
|
||||
|
||||
export const SideReserveOverview = (props: {
|
||||
|
@ -30,10 +30,7 @@ export const SideReserveOverview = (props: {
|
|||
const name = useTokenName(reserve?.liquidityMint);
|
||||
const liquidityMint = useMint(reserve.liquidityMint);
|
||||
|
||||
const availableLiquidity = fromLamports(
|
||||
reserve.state.availableLiquidity,
|
||||
liquidityMint
|
||||
);
|
||||
const availableLiquidity = fromLamports(reserve.state.availableLiquidity, liquidityMint);
|
||||
|
||||
const depositApy = calculateDepositAPY(reserve);
|
||||
const borrowApr = calculateBorrowAPY(reserve);
|
||||
|
@ -47,47 +44,43 @@ export const SideReserveOverview = (props: {
|
|||
if (mode === SideReserveOverviewMode.Deposit) {
|
||||
extraInfo = (
|
||||
<>
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
{LABELS.TABLE_TITLE_DEPOSIT_APY}:
|
||||
</Text>
|
||||
<div className="card-cell ">{formatPct.format(depositApy)}</div>
|
||||
<div className='card-cell '>{formatPct.format(depositApy)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Maximum LTV:
|
||||
</Text>
|
||||
<div className="card-cell ">{formatPct.format(maxLTV)}</div>
|
||||
<div className='card-cell '>{formatPct.format(maxLTV)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Liquidation threshold:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
{formatPct.format(liquidationThreshold)}
|
||||
</div>
|
||||
<div className='card-cell '>{formatPct.format(liquidationThreshold)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Liquidation penalty:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
{formatPct.format(liquidationPenalty)}
|
||||
</div>
|
||||
<div className='card-cell '>{formatPct.format(liquidationPenalty)}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else if (mode === SideReserveOverviewMode.Borrow) {
|
||||
extraInfo = (
|
||||
<>
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
{LABELS.TABLE_TITLE_BORROW_APY}:
|
||||
</Text>
|
||||
<div className="card-cell ">{formatPct.format(borrowApr)}</div>
|
||||
<div className='card-cell '>{formatPct.format(borrowApr)}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -99,34 +92,30 @@ export const SideReserveOverview = (props: {
|
|||
title={
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: "1.2rem",
|
||||
justifyContent: "center",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: '1.2rem',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Link to={`/reserve/${props.reserve.pubkey}`}>
|
||||
<TokenIcon
|
||||
mintAddress={reserve?.liquidityMint}
|
||||
style={{ width: 30, height: 30 }}
|
||||
/>{" "}
|
||||
{name} Reserve Overview
|
||||
<TokenIcon mintAddress={reserve?.liquidityMint} style={{ width: 30, height: 30 }} /> {name} Reserve Overview
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Utilization rate:
|
||||
</Text>
|
||||
<div className="card-cell ">{formatPct.format(utilizationRate)}</div>
|
||||
<div className='card-cell '>{formatPct.format(utilizationRate)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Available liquidity:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div className='card-cell '>
|
||||
{formatNumber.format(availableLiquidity)} {name}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { PoolInfo } from "../../models";
|
||||
import echarts from "echarts";
|
||||
import { formatNumber, formatUSD } from "../../utils/utils";
|
||||
import { useEnrichedPools } from "../../contexts/market";
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { PoolInfo } from '../../models';
|
||||
import echarts from 'echarts';
|
||||
import { formatNumber, formatUSD } from 'common/src/utils/utils';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
|
||||
export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||
const { pool } = props;
|
||||
|
@ -44,7 +44,7 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
|
||||
instance.setOption({
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
trigger: 'item',
|
||||
formatter: function (params: any) {
|
||||
var val = formatUSD.format(params.value);
|
||||
var tokenAmount = formatNumber.format(params.data.tokens);
|
||||
|
@ -53,8 +53,8 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
},
|
||||
series: [
|
||||
{
|
||||
name: "Liquidity",
|
||||
type: "pie",
|
||||
name: 'Liquidity',
|
||||
type: 'pie',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
|
@ -70,20 +70,20 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
},
|
||||
rich: {
|
||||
c: {
|
||||
color: "black",
|
||||
color: 'black',
|
||||
lineHeight: 22,
|
||||
align: "center",
|
||||
align: 'center',
|
||||
},
|
||||
r: {
|
||||
color: "black",
|
||||
align: "right",
|
||||
color: 'black',
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: "#000",
|
||||
borderColor: '#000',
|
||||
},
|
||||
},
|
||||
data,
|
||||
|
@ -96,5 +96,5 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
return <div ref={chartDiv} style={{ height: 150, width: "100%" }} />;
|
||||
return <div ref={chartDiv} style={{ height: 150, width: '100%' }} />;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { useMint, useAccountByMint } from "../../contexts/accounts";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import React from 'react';
|
||||
import { useMint, useAccountByMint } from 'common/src/contexts/accounts';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
|
||||
export const TokenDisplay = (props: {
|
||||
name: string;
|
||||
|
@ -16,8 +16,7 @@ export const TokenDisplay = (props: {
|
|||
let hasBalance: boolean = false;
|
||||
if (showBalance) {
|
||||
if (tokenAccount && tokenMint) {
|
||||
balance =
|
||||
tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||
balance = tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||
hasBalance = balance > 0;
|
||||
}
|
||||
}
|
||||
|
@ -28,27 +27,18 @@ export const TokenDisplay = (props: {
|
|||
title={mintAddress}
|
||||
key={mintAddress}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{icon || <TokenIcon mintAddress={mintAddress} />}
|
||||
{name}
|
||||
</div>
|
||||
{showBalance ? (
|
||||
<span
|
||||
title={balance.toString()}
|
||||
key={mintAddress}
|
||||
className="token-balance"
|
||||
>
|
||||
{" "}
|
||||
{hasBalance
|
||||
? balance < 0.001
|
||||
? "<0.001"
|
||||
: balance.toFixed(3)
|
||||
: "-"}
|
||||
<span title={balance.toString()} key={mintAddress} className='token-balance'>
|
||||
{hasBalance ? (balance < 0.001 ? '<0.001' : balance.toFixed(3)) : '-'}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Identicon } from "../Identicon";
|
||||
import React from "react";
|
||||
import { getTokenIcon } from "../../utils/utils";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Identicon } from '../Identicon';
|
||||
import React from 'react';
|
||||
import { getTokenIcon } from 'common/src/utils/utils';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const TokenIcon = (props: {
|
||||
mintAddress?: string | PublicKey;
|
||||
|
@ -18,18 +18,18 @@ export const TokenIcon = (props: {
|
|||
if (icon) {
|
||||
return (
|
||||
<img
|
||||
alt="Token icon"
|
||||
alt='Token icon'
|
||||
className={props.className}
|
||||
key={icon}
|
||||
width={props.style?.width || size.toString()}
|
||||
height={props.style?.height || size.toString()}
|
||||
src={icon}
|
||||
style={{
|
||||
marginRight: "0.5rem",
|
||||
marginTop: "0.11rem",
|
||||
borderRadius: "10rem",
|
||||
backgroundColor: "white",
|
||||
backgroundClip: "padding-box",
|
||||
marginRight: '0.5rem',
|
||||
marginTop: '0.11rem',
|
||||
borderRadius: '10rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundClip: 'padding-box',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
|
@ -40,7 +40,7 @@ export const TokenIcon = (props: {
|
|||
<Identicon
|
||||
address={props.mintAddress}
|
||||
style={{
|
||||
marginRight: "0.5rem",
|
||||
marginRight: '0.5rem',
|
||||
width: size,
|
||||
height: size,
|
||||
marginTop: 2,
|
||||
|
@ -50,18 +50,10 @@ export const TokenIcon = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
export const PoolIcon = (props: {
|
||||
mintA: string;
|
||||
mintB: string;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}) => {
|
||||
export const PoolIcon = (props: { mintA: string; mintB: string; style?: React.CSSProperties; className?: string }) => {
|
||||
return (
|
||||
<div className={props.className} style={{ display: "flex" }}>
|
||||
<TokenIcon
|
||||
mintAddress={props.mintA}
|
||||
style={{ marginRight: "-0.5rem", ...props.style }}
|
||||
/>
|
||||
<div className={props.className} style={{ display: 'flex' }}>
|
||||
<TokenIcon mintAddress={props.mintA} style={{ marginRight: '-0.5rem', ...props.style }} />
|
||||
<TokenIcon mintAddress={props.mintB} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,50 +1,32 @@
|
|||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
useUserCollateralBalance,
|
||||
useTokenName,
|
||||
useUserBalance,
|
||||
useBorrowedAmount,
|
||||
useBorrowingPower,
|
||||
useUserObligationByReserve,
|
||||
} from "./../../hooks";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { formatNumber } from "../../utils/utils";
|
||||
import { Button, Card, Typography } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { LABELS } from "../../constants";
|
||||
} from './../../hooks';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import { LendingReserve } from '../../models/lending';
|
||||
import { formatNumber } from 'common/src/utils/utils';
|
||||
import { Button, Card, Typography } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const UserLendingCard = (props: {
|
||||
className?: string;
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const UserLendingCard = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||
const reserve = props.reserve;
|
||||
const address = props.address;
|
||||
|
||||
const name = useTokenName(reserve?.liquidityMint);
|
||||
|
||||
const {
|
||||
balance: tokenBalance,
|
||||
balanceInUSD: tokenBalanceInUSD,
|
||||
} = useUserBalance(props.reserve.liquidityMint);
|
||||
const {
|
||||
balance: collateralBalance,
|
||||
balanceInUSD: collateralBalanceInUSD,
|
||||
} = useUserCollateralBalance(props.reserve);
|
||||
const { balance: tokenBalance, balanceInUSD: tokenBalanceInUSD } = useUserBalance(props.reserve.liquidityMint);
|
||||
const { balance: collateralBalance, balanceInUSD: collateralBalanceInUSD } = useUserCollateralBalance(props.reserve);
|
||||
|
||||
const {
|
||||
borrowed: totalBorrowed,
|
||||
borrowedInUSD,
|
||||
ltv,
|
||||
health,
|
||||
} = useBorrowedAmount(address);
|
||||
const {
|
||||
totalInQuote: borrowingPowerInUSD,
|
||||
borrowingPower,
|
||||
} = useBorrowingPower(address);
|
||||
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
|
||||
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
|
||||
const { userObligationsByReserve } = useUserObligationByReserve(address);
|
||||
|
||||
return (
|
||||
|
@ -53,10 +35,10 @@ export const UserLendingCard = (props: {
|
|||
title={
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: "1.2rem",
|
||||
justifyContent: "center",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: '1.2rem',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
Your Information
|
||||
|
@ -65,90 +47,79 @@ export const UserLendingCard = (props: {
|
|||
>
|
||||
<h3>{LABELS.BORROWS}</h3>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Borrowed
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div className='card-cell '>
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(totalBorrowed)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(borrowedInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(borrowedInUSD)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
{LABELS.TABLE_TITLE_HEALTH}:
|
||||
</Text>
|
||||
<div className="card-cell ">{health.toFixed(2)}</div>
|
||||
<div className='card-cell '>{health.toFixed(2)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
{LABELS.LOAN_TO_VALUE}:
|
||||
</Text>
|
||||
<div className="card-cell ">{formatNumber.format(ltv)}</div>
|
||||
<div className='card-cell '>{formatNumber.format(ltv)}</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
Available to you:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div className='card-cell '>
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(borrowingPowerInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(borrowingPowerInUSD)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{LABELS.DEPOSITS}</h3>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
{LABELS.WALLET_BALANCE}:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div className='card-cell '>
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(tokenBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(tokenBalanceInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(tokenBalanceInUSD)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-row">
|
||||
<Text type="secondary" className="card-cell ">
|
||||
<div className='card-row'>
|
||||
<Text type='secondary' className='card-cell '>
|
||||
You already deposited:
|
||||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div className='card-cell '>
|
||||
<div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateralBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(collateralBalanceInUSD)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(collateralBalanceInUSD)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="card-row"
|
||||
style={{ marginTop: 20, justifyContent: "space-evenly" }}
|
||||
>
|
||||
<div className='card-row' style={{ marginTop: 20, justifyContent: 'space-evenly' }}>
|
||||
<Link to={`/deposit/${address}`}>
|
||||
<Button>{LABELS.DEPOSIT_ACTION}</Button>
|
||||
</Link>
|
||||
|
@ -159,9 +130,7 @@ export const UserLendingCard = (props: {
|
|||
<Button>{LABELS.WITHDRAW_ACTION}</Button>
|
||||
</Link>
|
||||
{!!userObligationsByReserve.length && (
|
||||
<Link
|
||||
to={`/repay/loan/${userObligationsByReserve[0].obligation.account.pubkey.toBase58()}`}
|
||||
>
|
||||
<Link to={`/repay/loan/${userObligationsByReserve[0].obligation.account.pubkey.toBase58()}`}>
|
||||
<Button>{LABELS.REPAY_ACTION}</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
InputType,
|
||||
useUserCollateralBalance,
|
||||
useSliderInput,
|
||||
useUserBalance,
|
||||
} from "../../hooks";
|
||||
import { LendingReserve } from "../../models/lending";
|
||||
import { Card, Slider } from "antd";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { withdraw } from "../../actions";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import "./style.less";
|
||||
import { LABELS, marks } from "../../constants";
|
||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||
import { ConnectButton } from "../ConnectButton";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { InputType, useUserCollateralBalance, useSliderInput, useUserBalance } from '../../hooks';
|
||||
import { LendingReserve } from '../../models/lending';
|
||||
import { Card, Slider } from 'antd';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { useWallet } from 'common/src/contexts/wallet';
|
||||
import { withdraw } from '../../actions';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import './style.less';
|
||||
import { LABELS, marks } from '../../constants';
|
||||
import { ActionConfirmation } from './../ActionConfirmation';
|
||||
import { ConnectButton } from '../ConnectButton';
|
||||
import CollateralInput from '../CollateralInput';
|
||||
|
||||
export const WithdrawInput = (props: {
|
||||
className?: string;
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const WithdrawInput = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
|
@ -30,17 +21,14 @@ export const WithdrawInput = (props: {
|
|||
const reserve = props.reserve;
|
||||
const address = props.address;
|
||||
|
||||
const {
|
||||
balanceLamports: collateralBalanceLamports,
|
||||
accounts: fromAccounts,
|
||||
} = useUserBalance(reserve?.collateralMint);
|
||||
const { balance: collateralBalanceInLiquidity } = useUserCollateralBalance(
|
||||
reserve
|
||||
const { balanceLamports: collateralBalanceLamports, accounts: fromAccounts } = useUserBalance(
|
||||
reserve?.collateralMint
|
||||
);
|
||||
const { balance: collateralBalanceInLiquidity } = useUserCollateralBalance(reserve);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
if (typeof val === "string") {
|
||||
if (typeof val === 'string') {
|
||||
return (parseFloat(val) / collateralBalanceInLiquidity) * 100;
|
||||
} else {
|
||||
return (val * collateralBalanceInLiquidity) / 100;
|
||||
|
@ -59,22 +47,12 @@ export const WithdrawInput = (props: {
|
|||
const withdrawAmount = Math.min(
|
||||
type === InputType.Percent
|
||||
? (pct * collateralBalanceLamports) / 100
|
||||
: Math.ceil(
|
||||
collateralBalanceLamports *
|
||||
(parseFloat(value) / collateralBalanceInLiquidity)
|
||||
),
|
||||
: Math.ceil(collateralBalanceLamports * (parseFloat(value) / collateralBalanceInLiquidity)),
|
||||
collateralBalanceLamports
|
||||
);
|
||||
await withdraw(
|
||||
fromAccounts[0],
|
||||
withdrawAmount,
|
||||
reserve,
|
||||
address,
|
||||
connection,
|
||||
wallet
|
||||
);
|
||||
await withdraw(fromAccounts[0], withdrawAmount, reserve, address, connection, wallet);
|
||||
|
||||
setValue("");
|
||||
setValue('');
|
||||
setShowConfirmation(true);
|
||||
} catch {
|
||||
// TODO:
|
||||
|
@ -97,11 +75,11 @@ export const WithdrawInput = (props: {
|
|||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -111,26 +89,26 @@ export const WithdrawInput = (props: {
|
|||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<div className="withdraw-input-title">{LABELS.WITHDRAW_QUESTION}</div>
|
||||
<div className='withdraw-input-title'>{LABELS.WITHDRAW_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Amount"
|
||||
title='Amount'
|
||||
reserve={reserve}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setValue(val?.toString() || '');
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
|
@ -140,15 +118,13 @@ export const WithdrawInput = (props: {
|
|||
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||
|
||||
<ConnectButton
|
||||
size="large"
|
||||
type="primary"
|
||||
size='large'
|
||||
type='primary'
|
||||
onClick={onWithdraw}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
>
|
||||
{fromAccounts.length === 0
|
||||
? LABELS.NO_COLLATERAL
|
||||
: LABELS.WITHDRAW_ACTION}
|
||||
{fromAccounts.length === 0 ? LABELS.NO_COLLATERAL : LABELS.WITHDRAW_ACTION}
|
||||
</ConnectButton>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from "./labels";
|
||||
export * from "./math";
|
||||
export * from "./marks";
|
||||
export * from "./style";
|
||||
export * from './labels';
|
||||
export * from './marks';
|
||||
export * from './style';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useConnection } from "./connection";
|
||||
import { LENDING_PROGRAM_ID } from "./../utils/ids";
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useConnection } from '@common/contexts/connection';
|
||||
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import {
|
||||
LendingMarketParser,
|
||||
isLendingReserve,
|
||||
|
@ -9,17 +9,12 @@ import {
|
|||
LendingReserve,
|
||||
isLendingObligation,
|
||||
LendingObligationParser,
|
||||
} from "./../models/lending";
|
||||
import {
|
||||
cache,
|
||||
getMultipleAccounts,
|
||||
MintParser,
|
||||
ParsedAccount,
|
||||
} from "./accounts";
|
||||
import { PublicKey, AccountInfo } from "@solana/web3.js";
|
||||
import { DexMarketParser } from "../models/dex";
|
||||
import { usePrecacheMarket } from "./market";
|
||||
import { useLendingReserves } from "../hooks";
|
||||
} from './../models/lending';
|
||||
import { cache, getMultipleAccounts, MintParser, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { PublicKey, AccountInfo } from '@solana/web3.js';
|
||||
import { DexMarketParser } from '../models/dex';
|
||||
import { usePrecacheMarket } from './market';
|
||||
import { useLendingReserves } from '../hooks';
|
||||
|
||||
export interface LendingContextState {}
|
||||
|
||||
|
@ -46,38 +41,21 @@ export const useLending = () => {
|
|||
|
||||
// TODO: query for all the dex from reserves
|
||||
|
||||
const processAccount = useCallback(
|
||||
(item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
|
||||
const processAccount = useCallback((item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
|
||||
if (isLendingReserve(item.account)) {
|
||||
const reserve = cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingReserveParser
|
||||
);
|
||||
const reserve = cache.add(item.pubkey.toBase58(), item.account, LendingReserveParser);
|
||||
|
||||
return reserve;
|
||||
} else if (isLendingMarket(item.account)) {
|
||||
return cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingMarketParser
|
||||
);
|
||||
return cache.add(item.pubkey.toBase58(), item.account, LendingMarketParser);
|
||||
} else if (isLendingObligation(item.account)) {
|
||||
return cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingObligationParser
|
||||
);
|
||||
return cache.add(item.pubkey.toBase58(), item.account, LendingObligationParser);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (reserveAccounts.length > 0) {
|
||||
precacheMarkets(
|
||||
reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58())
|
||||
);
|
||||
precacheMarkets(reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58()));
|
||||
}
|
||||
}, [reserveAccounts, precacheMarkets]);
|
||||
|
||||
|
@ -86,36 +64,21 @@ export const useLending = () => {
|
|||
setLendingAccounts([]);
|
||||
|
||||
const queryLendingAccounts = async () => {
|
||||
const programAccounts = await connection.getProgramAccounts(
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
const programAccounts = await connection.getProgramAccounts(LENDING_PROGRAM_ID);
|
||||
|
||||
const accounts = programAccounts
|
||||
.map(processAccount)
|
||||
.filter((item) => item !== undefined);
|
||||
const accounts = programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||
|
||||
const lendingReserves = accounts
|
||||
.filter(
|
||||
(acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
|
||||
)
|
||||
.filter((acc) => (acc?.info as LendingReserve).lendingMarket !== undefined)
|
||||
.map((acc) => acc as ParsedAccount<LendingReserve>);
|
||||
|
||||
const toQuery = [
|
||||
...lendingReserves.map((acc) => {
|
||||
const result = [
|
||||
cache.registerParser(
|
||||
acc?.info.collateralMint.toBase58(),
|
||||
MintParser
|
||||
),
|
||||
cache.registerParser(
|
||||
acc?.info.liquidityMint.toBase58(),
|
||||
MintParser
|
||||
),
|
||||
cache.registerParser(acc?.info.collateralMint.toBase58(), MintParser),
|
||||
cache.registerParser(acc?.info.liquidityMint.toBase58(), MintParser),
|
||||
// ignore dex if its not set
|
||||
cache.registerParser(
|
||||
acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : "",
|
||||
DexMarketParser
|
||||
),
|
||||
cache.registerParser(acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : '', DexMarketParser),
|
||||
].filter((_) => _);
|
||||
return result;
|
||||
}),
|
||||
|
@ -123,15 +86,13 @@ export const useLending = () => {
|
|||
|
||||
// This will pre-cache all accounts used by pools
|
||||
// All those accounts are updated whenever there is a change
|
||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||
({ keys, array }) => {
|
||||
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||
return array.map((obj, index) => {
|
||||
const address = keys[index];
|
||||
cache.add(address, obj);
|
||||
return obj;
|
||||
}) as any[];
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// HACK: fix, force account refresh
|
||||
programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||
|
@ -155,7 +116,7 @@ export const useLending = () => {
|
|||
};
|
||||
processAccount(item);
|
||||
},
|
||||
"singleGossip"
|
||||
'singleGossip'
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
|
|
@ -1,26 +1,20 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { MINT_TO_MARKET } from "./../models/marketOverrides";
|
||||
import { POOLS_WITH_AIRDROP } from "./../models/airdrops";
|
||||
import {
|
||||
convert,
|
||||
fromLamports,
|
||||
getPoolName,
|
||||
getTokenName,
|
||||
KnownTokenMap,
|
||||
STABLE_COINS,
|
||||
} from "./../utils/utils";
|
||||
import { useConnectionConfig } from "./connection";
|
||||
import { cache, getMultipleAccounts, ParsedAccount } from "./accounts";
|
||||
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum";
|
||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||
import { useMemo } from "react";
|
||||
import { EventEmitter } from "./../utils/eventEmitter";
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { MINT_TO_MARKET } from './../models/marketOverrides';
|
||||
import { POOLS_WITH_AIRDROP } from './../models/airdrops';
|
||||
import { convert, fromLamports, getTokenName, KnownTokenMap, STABLE_COINS } from 'common/src/utils/utils';
|
||||
import { getPoolName } from '../utils/utils';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { cache, getMultipleAccounts, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { useMemo } from 'react';
|
||||
import { EventEmitter } from 'common/src/utils/eventEmitter';
|
||||
|
||||
import { DexMarketParser } from "./../models/dex";
|
||||
import { LendingMarket, LendingReserve, PoolInfo } from "../models";
|
||||
import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from "../utils/pools";
|
||||
import { DexMarketParser } from './../models/dex';
|
||||
import { LendingMarket, LendingReserve, PoolInfo } from '../models';
|
||||
import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from '../utils/pools';
|
||||
|
||||
const INITAL_LIQUIDITY_DATE = new Date("2020-10-27");
|
||||
const INITAL_LIQUIDITY_DATE = new Date('2020-10-27');
|
||||
export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min
|
||||
|
||||
interface RecentPoolData {
|
||||
|
@ -50,27 +44,19 @@ export function MarketProvider({ children = null as any }) {
|
|||
const { endpoint } = useConnectionConfig();
|
||||
const accountsToObserve = useMemo(() => new Map<string, number>(), []);
|
||||
const [marketMints, setMarketMints] = useState<string[]>([]);
|
||||
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(
|
||||
new Map()
|
||||
);
|
||||
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(new Map());
|
||||
|
||||
const connection = useMemo(() => new Connection(endpoint, "recent"), [
|
||||
endpoint,
|
||||
]);
|
||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||
|
||||
const marketByMint = useMemo(() => {
|
||||
return [...new Set(marketMints).values()].reduce((acc, key) => {
|
||||
const mintAddress = key;
|
||||
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
||||
(a) => a.address.toBase58() === mintAddress
|
||||
);
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||
|
||||
const marketAddress = MINT_TO_MARKET[mintAddress];
|
||||
const marketName = `${SERUM_TOKEN?.name}/USDC`;
|
||||
const marketInfo = MARKETS.find(
|
||||
(m) => m.name === marketName || m.address.toBase58() === marketAddress
|
||||
);
|
||||
const marketInfo = MARKETS.find((m) => m.name === marketName || m.address.toBase58() === marketAddress);
|
||||
|
||||
if (marketInfo) {
|
||||
acc.set(mintAddress, {
|
||||
|
@ -109,7 +95,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
connection,
|
||||
// only query for markets that are not in cahce
|
||||
allMarkets.filter((a) => cache.get(a) === undefined),
|
||||
"single"
|
||||
'single'
|
||||
).then(({ keys, array }) => {
|
||||
allMarkets.forEach(() => {});
|
||||
|
||||
|
@ -167,10 +153,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
|
||||
const midPriceInUSD = useCallback(
|
||||
(mintAddress: string) => {
|
||||
return getMidPrice(
|
||||
marketByMint.get(mintAddress)?.marketInfo.address.toBase58(),
|
||||
mintAddress
|
||||
);
|
||||
return getMidPrice(marketByMint.get(mintAddress)?.marketInfo.address.toBase58(), mintAddress);
|
||||
},
|
||||
[marketByMint]
|
||||
);
|
||||
|
@ -178,7 +161,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
const subscribeToMarket = useCallback(
|
||||
(mintAddress: string) => {
|
||||
const info = marketByMint.get(mintAddress);
|
||||
const market = cache.get(info?.marketInfo.address.toBase58() || "");
|
||||
const market = cache.get(info?.marketInfo.address.toBase58() || '');
|
||||
if (!market) {
|
||||
return () => {};
|
||||
}
|
||||
|
@ -248,7 +231,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
const marketEmitter = context?.marketEmitter;
|
||||
const marketsByMint = context?.marketByMint;
|
||||
const dailyVolume = context?.dailyVolume;
|
||||
const poolKeys = pools.map((p) => p.pubkeys.account.toBase58()).join(",");
|
||||
const poolKeys = pools.map((p) => p.pubkeys.account.toBase58()).join(',');
|
||||
|
||||
useEffect(() => {
|
||||
if (!marketEmitter || !subscribeToMarket || pools.length === 0) {
|
||||
|
@ -260,9 +243,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
const subscriptions = mints.map((m) => subscribeToMarket(m));
|
||||
|
||||
const update = () => {
|
||||
setEnriched(
|
||||
createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap)
|
||||
);
|
||||
setEnriched(createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap));
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket(update);
|
||||
|
@ -274,15 +255,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
subscriptions.forEach((dispose) => dispose && dispose());
|
||||
};
|
||||
// Do not add pools here, causes a really bad infinite rendering loop. Use poolKeys instead.
|
||||
}, [
|
||||
pools,
|
||||
tokenMap,
|
||||
dailyVolume,
|
||||
poolKeys,
|
||||
subscribeToMarket,
|
||||
marketEmitter,
|
||||
marketsByMint,
|
||||
]);
|
||||
}, [pools, tokenMap, dailyVolume, poolKeys, subscribeToMarket, marketEmitter, marketsByMint]);
|
||||
|
||||
return enriched;
|
||||
};
|
||||
|
@ -307,30 +280,20 @@ function createEnrichedPools(
|
|||
const result = pools
|
||||
.filter((p) => p.pubkeys.holdingMints && p.pubkeys.holdingMints.length > 1)
|
||||
.map((p, index) => {
|
||||
const mints = (p.pubkeys.holdingMints || [])
|
||||
.map((a) => a.toBase58())
|
||||
.sort();
|
||||
const mints = (p.pubkeys.holdingMints || []).map((a) => a.toBase58()).sort();
|
||||
const mintA = cache.getMint(mints[0]);
|
||||
const mintB = cache.getMint(mints[1]);
|
||||
|
||||
const account0 = cache.get(p.pubkeys.holdingAccounts[0]);
|
||||
const account1 = cache.get(p.pubkeys.holdingAccounts[1]);
|
||||
|
||||
const accountA =
|
||||
account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
||||
const accountB =
|
||||
account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
||||
const accountA = account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
||||
const accountB = account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
||||
|
||||
const baseMid = getMidPrice(
|
||||
marketByMint.get(mints[0])?.marketInfo.address.toBase58() || "",
|
||||
mints[0]
|
||||
);
|
||||
const baseMid = getMidPrice(marketByMint.get(mints[0])?.marketInfo.address.toBase58() || '', mints[0]);
|
||||
const baseReserveUSD = baseMid * convert(accountA, mintA);
|
||||
|
||||
const quote = getMidPrice(
|
||||
marketByMint.get(mints[1])?.marketInfo.address.toBase58() || "",
|
||||
mints[1]
|
||||
);
|
||||
const quote = getMidPrice(marketByMint.get(mints[1])?.marketInfo.address.toBase58() || '', mints[1]);
|
||||
const quoteReserveUSD = quote * convert(accountB, mintB);
|
||||
|
||||
const poolMint = cache.getMint(p.pubkeys.mint);
|
||||
|
@ -338,16 +301,10 @@ function createEnrichedPools(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let airdropYield = calculateAirdropYield(
|
||||
p,
|
||||
marketByMint,
|
||||
baseReserveUSD,
|
||||
quoteReserveUSD
|
||||
);
|
||||
let airdropYield = calculateAirdropYield(p, marketByMint, baseReserveUSD, quoteReserveUSD);
|
||||
|
||||
let volume = 0;
|
||||
let volume24h =
|
||||
baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
||||
let volume24h = baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
||||
let fees24h = volume24h * (LIQUIDITY_PROVIDER_FEE - SERUM_FEE);
|
||||
let fees = 0;
|
||||
let apy = airdropYield;
|
||||
|
@ -355,18 +312,13 @@ function createEnrichedPools(
|
|||
if (p.pubkeys.feeAccount) {
|
||||
const feeAccount = cache.get(p.pubkeys.feeAccount);
|
||||
|
||||
if (
|
||||
poolMint &&
|
||||
feeAccount &&
|
||||
feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()
|
||||
) {
|
||||
if (poolMint && feeAccount && feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()) {
|
||||
const feeBalance = feeAccount?.info.amount.toNumber();
|
||||
const supply = poolMint?.supply.toNumber();
|
||||
|
||||
const ownedPct = feeBalance / supply;
|
||||
|
||||
const poolOwnerFees =
|
||||
ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
||||
const poolOwnerFees = ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
||||
volume = poolOwnerFees / 0.0004;
|
||||
fees = volume * LIQUIDITY_PROVIDER_FEE;
|
||||
|
||||
|
@ -376,27 +328,16 @@ 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()) /
|
||||
(24 * 3600 * 1000)
|
||||
(TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) / (24 * 3600 * 1000)
|
||||
);
|
||||
const apy0 =
|
||||
parseFloat(
|
||||
((baseVolume / daysSinceInception) *
|
||||
LIQUIDITY_PROVIDER_FEE *
|
||||
356) as any
|
||||
) / baseReserveUSD;
|
||||
parseFloat(((baseVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||
const apy1 =
|
||||
parseFloat(
|
||||
((quoteVolume / daysSinceInception) *
|
||||
LIQUIDITY_PROVIDER_FEE *
|
||||
356) as any
|
||||
) / quoteReserveUSD;
|
||||
parseFloat(((quoteVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / quoteReserveUSD;
|
||||
|
||||
apy = apy + Math.max(apy0, apy1);
|
||||
|
||||
const apy24h0 =
|
||||
parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) /
|
||||
baseReserveUSD;
|
||||
const apy24h0 = parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||
apy24h = apy24h + apy24h0;
|
||||
}
|
||||
}
|
||||
|
@ -405,10 +346,7 @@ function createEnrichedPools(
|
|||
const lpMint = cache.getMint(p.pubkeys.mint);
|
||||
|
||||
const name = getPoolName(tokenMap, p);
|
||||
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace(
|
||||
"/",
|
||||
"-"
|
||||
)}`;
|
||||
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace('/', '-')}`;
|
||||
|
||||
return {
|
||||
key: p.pubkeys.account.toBase58(),
|
||||
|
@ -423,11 +361,7 @@ function createEnrichedPools(
|
|||
liquidityAinUsd: baseReserveUSD,
|
||||
liquidityB: convert(accountB, mintB),
|
||||
liquidityBinUsd: quoteReserveUSD,
|
||||
supply:
|
||||
lpMint &&
|
||||
(
|
||||
lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)
|
||||
).toFixed(9),
|
||||
supply: lpMint && (lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)).toFixed(9),
|
||||
fees,
|
||||
fees24h,
|
||||
liquidity: baseReserveUSD + quoteReserveUSD,
|
||||
|
@ -451,9 +385,7 @@ function calculateAirdropYield(
|
|||
quoteReserveUSD: number
|
||||
) {
|
||||
let airdropYield = 0;
|
||||
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) =>
|
||||
drop.pool.equals(p.pubkeys.mint)
|
||||
);
|
||||
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) => drop.pool.equals(p.pubkeys.mint));
|
||||
if (poolWithAirdrop) {
|
||||
airdropYield = poolWithAirdrop.airdrops.reduce((acc, item) => {
|
||||
const market = marketByMint.get(item.mint.toBase58())?.marketInfo.address;
|
||||
|
@ -463,8 +395,7 @@ function calculateAirdropYield(
|
|||
acc =
|
||||
acc +
|
||||
// airdrop yield
|
||||
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) *
|
||||
(365 / 30);
|
||||
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) * (365 / 30);
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
@ -474,9 +405,7 @@ function calculateAirdropYield(
|
|||
}
|
||||
|
||||
export const useMidPriceInUSD = (mint: string) => {
|
||||
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(
|
||||
MarketsContext
|
||||
) as MarketsContextState;
|
||||
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(MarketsContext) as MarketsContextState;
|
||||
const [price, setPrice] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -504,12 +433,7 @@ export const usePrecacheMarket = () => {
|
|||
return context.precacheMarkets;
|
||||
};
|
||||
|
||||
export const simulateMarketOrderFill = (
|
||||
amount: number,
|
||||
reserve: LendingReserve,
|
||||
dex: PublicKey,
|
||||
useBBO = false
|
||||
) => {
|
||||
export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve, dex: PublicKey, useBBO = false) => {
|
||||
const liquidityMint = cache.get(reserve.liquidityMint);
|
||||
const collateralMint = cache.get(reserve.collateralMint);
|
||||
if (!liquidityMint || !collateralMint) {
|
||||
|
@ -522,22 +446,12 @@ export const simulateMarketOrderFill = (
|
|||
}
|
||||
const decodedMarket = marketInfo.info;
|
||||
|
||||
const baseMintDecimals =
|
||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals =
|
||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
|
||||
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
|
||||
const dexMarket = new Market(
|
||||
decodedMarket,
|
||||
baseMintDecimals,
|
||||
quoteMintDecimals,
|
||||
undefined,
|
||||
decodedMarket.programId
|
||||
);
|
||||
const dexMarket = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||
|
||||
const bidInfo = cache.get(decodedMarket?.bids)?.info;
|
||||
const askInfo = cache.get(decodedMarket?.asks)?.info;
|
||||
|
@ -548,9 +462,7 @@ export const simulateMarketOrderFill = (
|
|||
const bids = new Orderbook(dexMarket, bidInfo.accountFlags, bidInfo.slab);
|
||||
const asks = new Orderbook(dexMarket, askInfo.accountFlags, askInfo.slab);
|
||||
|
||||
const book = lendingMarket.info.quoteMint.equals(reserve.liquidityMint)
|
||||
? bids
|
||||
: asks;
|
||||
const book = lendingMarket.info.quoteMint.equals(reserve.liquidityMint) ? bids : asks;
|
||||
|
||||
let cost = 0;
|
||||
let remaining = fromLamports(amount, liquidityMint.info);
|
||||
|
@ -593,11 +505,9 @@ const bbo = (bidsBook: Orderbook, asksBook: Orderbook) => {
|
|||
};
|
||||
|
||||
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
||||
(a) => a.address.toBase58() === mintAddress
|
||||
);
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||
|
||||
if (STABLE_COINS.has(SERUM_TOKEN?.name || "")) {
|
||||
if (STABLE_COINS.has(SERUM_TOKEN?.name || '')) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
|
@ -612,18 +522,10 @@ const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
|||
|
||||
const decodedMarket = marketInfo.info;
|
||||
|
||||
const baseMintDecimals =
|
||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals =
|
||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
|
||||
const market = new Market(
|
||||
decodedMarket,
|
||||
baseMintDecimals,
|
||||
quoteMintDecimals,
|
||||
undefined,
|
||||
decodedMarket.programId
|
||||
);
|
||||
const market = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||
|
||||
const bids = cache.get(decodedMarket.bids)?.info;
|
||||
const asks = cache.get(decodedMarket.asks)?.info;
|
||||
|
@ -643,14 +545,12 @@ const refreshAccounts = async (connection: Connection, keys: string[]) => {
|
|||
return [];
|
||||
}
|
||||
|
||||
return getMultipleAccounts(connection, keys, "single").then(
|
||||
({ keys, array }) => {
|
||||
return getMultipleAccounts(connection, keys, 'single').then(({ keys, array }) => {
|
||||
return array.map((item, index) => {
|
||||
const address = keys[index];
|
||||
return cache.add(new PublicKey(address), item);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
interface SerumMarket {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React, { useContext, useMemo } from 'react';
|
||||
|
||||
import { PoolInfo } from '../models';
|
||||
|
||||
const AccountsContext = React.createContext<any>(null);
|
||||
|
||||
export function useCachedPool(legacy = false) {
|
||||
const context = useContext(AccountsContext);
|
||||
|
||||
const allPools = context.pools as PoolInfo[];
|
||||
const pools = useMemo(() => {
|
||||
return allPools.filter((p) => p.legacy === legacy);
|
||||
}, [allPools, legacy]);
|
||||
|
||||
return {
|
||||
pools,
|
||||
};
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
export * from "./useUserAccounts";
|
||||
export * from "./useAccountByMint";
|
||||
export * from "./useLendingReserves";
|
||||
export * from "./useTokenName";
|
||||
export * from "./useUserBalance";
|
||||
export * from "./useCollateralBalance";
|
||||
export * from "./useLendingObligations";
|
||||
export * from "./useUserObligations";
|
||||
export * from "./useUserObligationByReserve";
|
||||
export * from "./useBorrowedAmount";
|
||||
export * from "./useUserDeposits";
|
||||
export * from "./useSliderInput";
|
||||
export * from "./useEnrichedLendingObligations";
|
||||
export * from "./useBorrowingPower";
|
||||
export * from "./useLendingMarket";
|
||||
export * from './useLendingReserves';
|
||||
export * from './useCollateralBalance';
|
||||
export * from './useLendingObligations';
|
||||
export * from './useUserObligations';
|
||||
export * from './useUserObligationByReserve';
|
||||
export * from './useBorrowedAmount';
|
||||
export * from './useUserDeposits';
|
||||
export * from './useSliderInput';
|
||||
export * from './useUserBalance';
|
||||
export * from './useEnrichedLendingObligations';
|
||||
export * from './useBorrowingPower';
|
||||
export * from './useLendingMarket';
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useUserAccounts } from "./useUserAccounts";
|
||||
|
||||
export const useAccountByMint = (mint?: string | PublicKey) => {
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const mintAddress = typeof mint === "string" ? mint : mint?.toBase58();
|
||||
|
||||
const index = userAccounts.findIndex(
|
||||
(acc) => acc.info.mint.toBase58() === mintAddress
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
return userAccounts[index];
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
|
@ -1,17 +1,11 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useUserObligationByReserve } from "./useUserObligationByReserve";
|
||||
import { fromLamports, wadToLamports } from "../utils/utils";
|
||||
import {
|
||||
cache,
|
||||
getMultipleAccounts,
|
||||
MintParser,
|
||||
ParsedAccount,
|
||||
useMint,
|
||||
} from "../contexts/accounts";
|
||||
import { useConnection } from "../contexts/connection";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
import { useLendingReserve } from "./useLendingReserves";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useUserObligationByReserve } from './useUserObligationByReserve';
|
||||
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||
import { cache, getMultipleAccounts, MintParser, ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { useLendingReserve } from './useLendingReserves';
|
||||
|
||||
export function useBorrowedAmount(address?: string | PublicKey) {
|
||||
const connection = useConnection();
|
||||
|
@ -39,10 +33,8 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
|||
// precache obligation mints
|
||||
const { keys, array } = await getMultipleAccounts(
|
||||
connection,
|
||||
userObligationsByReserve.map((item) =>
|
||||
item.obligation.info.tokenMint.toBase58()
|
||||
),
|
||||
"single"
|
||||
userObligationsByReserve.map((item) => item.obligation.info.tokenMint.toBase58()),
|
||||
'single'
|
||||
);
|
||||
|
||||
array.forEach((item, index) => {
|
||||
|
@ -61,20 +53,12 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
|||
let liquidationThreshold = 0;
|
||||
|
||||
userObligationsByReserve.forEach((item) => {
|
||||
const borrowed = wadToLamports(
|
||||
item.obligation.info.borrowAmountWad
|
||||
).toNumber();
|
||||
const borrowed = wadToLamports(item.obligation.info.borrowAmountWad).toNumber();
|
||||
|
||||
const owned = item.userAccounts.reduce(
|
||||
(amount, acc) => (amount += acc.info.amount.toNumber()),
|
||||
0
|
||||
);
|
||||
const obligationMint = cache.get(
|
||||
item.obligation.info.tokenMint
|
||||
) as ParsedAccount<MintInfo>;
|
||||
const owned = item.userAccounts.reduce((amount, acc) => (amount += acc.info.amount.toNumber()), 0);
|
||||
const obligationMint = cache.get(item.obligation.info.tokenMint) as ParsedAccount<MintInfo>;
|
||||
|
||||
result.borrowedLamports +=
|
||||
borrowed * (owned / obligationMint?.info.supply.toNumber());
|
||||
result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber());
|
||||
result.borrowedInUSD += item.obligation.info.borrowedInQuote;
|
||||
result.colateralInUSD += item.obligation.info.collateralInQuote;
|
||||
liquidationThreshold = item.obligation.info.liquidationThreshold;
|
||||
|
@ -85,10 +69,7 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
|||
result.health = userObligationsByReserve[0].obligation.info.health;
|
||||
} else {
|
||||
result.ltv = (100 * result.borrowedInUSD) / result.colateralInUSD;
|
||||
result.health =
|
||||
(result.colateralInUSD * liquidationThreshold) /
|
||||
100 /
|
||||
result.borrowedInUSD;
|
||||
result.health = (result.colateralInUSD * liquidationThreshold) / 100 / result.borrowedInUSD;
|
||||
result.health = Number.isFinite(result.health) ? result.health : 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +1,32 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMint } from "../contexts/accounts";
|
||||
import { useMarkets } from "../contexts/market";
|
||||
import { LendingReserve, reserveMarketCap } from "../models/lending";
|
||||
import { fromLamports } from "../utils/utils";
|
||||
import { useUserBalance } from "./useUserBalance";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { useMarkets } from '../contexts/market';
|
||||
import { LendingReserve, reserveMarketCap } from '../models/lending';
|
||||
import { fromLamports } from 'common/src/utils/utils';
|
||||
import { useUserBalance } from './useUserBalance';
|
||||
|
||||
export function useUserCollateralBalance(
|
||||
reserve?: LendingReserve,
|
||||
account?: PublicKey
|
||||
) {
|
||||
export function useUserCollateralBalance(reserve?: LendingReserve, account?: PublicKey) {
|
||||
const mint = useMint(reserve?.collateralMint);
|
||||
const { balanceLamports: userBalance, accounts } = useUserBalance(
|
||||
reserve?.collateralMint,
|
||||
account
|
||||
);
|
||||
const { balanceLamports: userBalance, accounts } = useUserBalance(reserve?.collateralMint, account);
|
||||
|
||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||
|
||||
const balanceLamports = useMemo(
|
||||
() => reserve && calculateCollateralBalance(reserve, userBalance),
|
||||
[userBalance, reserve]
|
||||
);
|
||||
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mint), [
|
||||
balanceLamports,
|
||||
mint,
|
||||
const balanceLamports = useMemo(() => reserve && calculateCollateralBalance(reserve, userBalance), [
|
||||
userBalance,
|
||||
reserve,
|
||||
]);
|
||||
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mint), [balanceLamports, mint]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateBalance = () => {
|
||||
setBalanceInUSD(
|
||||
balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || "")
|
||||
);
|
||||
setBalanceInUSD(balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || ''));
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket((args) => {
|
||||
if (args.ids.has(reserve?.dexMarket.toBase58() || "")) {
|
||||
if (args.ids.has(reserve?.dexMarket.toBase58() || '')) {
|
||||
updateBalance();
|
||||
}
|
||||
});
|
||||
|
@ -58,12 +47,6 @@ export function useUserCollateralBalance(
|
|||
hasBalance: accounts.length > 0 && balance > 0,
|
||||
};
|
||||
}
|
||||
export function calculateCollateralBalance(
|
||||
reserve: LendingReserve,
|
||||
balanceLamports: number
|
||||
) {
|
||||
return (
|
||||
reserveMarketCap(reserve) *
|
||||
(balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1))
|
||||
);
|
||||
export function calculateCollateralBalance(reserve: LendingReserve, balanceLamports: number) {
|
||||
return reserveMarketCap(reserve) * (balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1));
|
||||
}
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
||||
import { useLendingObligations } from "./useLendingObligations";
|
||||
import {
|
||||
collateralToLiquidity,
|
||||
LendingObligation,
|
||||
LendingReserve,
|
||||
} from "../models/lending";
|
||||
import { useLendingReserves } from "./useLendingReserves";
|
||||
import { fromLamports, getTokenName, wadToLamports } from "../utils/utils";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
import { simulateMarketOrderFill, useMarkets } from "../contexts/market";
|
||||
import { useConnectionConfig } from "../contexts/connection";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { useLendingObligations } from './useLendingObligations';
|
||||
import { collateralToLiquidity, LendingObligation, LendingReserve } from '../models/lending';
|
||||
import { useLendingReserves } from './useLendingReserves';
|
||||
import { fromLamports, getTokenName, wadToLamports } from 'common/src/utils/utils';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { simulateMarketOrderFill, useMarkets } from '../contexts/market';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
|
||||
interface EnrichedLendingObligationInfo extends LendingObligation {
|
||||
ltv: number;
|
||||
|
@ -50,9 +46,7 @@ export function useEnrichedLendingObligations() {
|
|||
obligations
|
||||
.map((obligation) => ({
|
||||
obligation,
|
||||
reserve: availableReserves.get(
|
||||
obligation.info.borrowReserve.toBase58()
|
||||
) as ParsedAccount<LendingReserve>,
|
||||
reserve: availableReserves.get(obligation.info.borrowReserve.toBase58()) as ParsedAccount<LendingReserve>,
|
||||
collateralReserve: availableReserves.get(
|
||||
obligation.info.collateralReserve.toBase58()
|
||||
) as ParsedAccount<LendingReserve>,
|
||||
|
@ -60,63 +54,42 @@ export function useEnrichedLendingObligations() {
|
|||
// 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
|
||||
)
|
||||
.filter((item) => wadToLamports(item.obligation.info.borrowAmountWad).toNumber() > 0)
|
||||
.map((item) => {
|
||||
const obligation = item.obligation;
|
||||
const reserve = item.reserve.info;
|
||||
const collateralReserve = item.reserve.info;
|
||||
const liquidityMint = cache.get(
|
||||
reserve.liquidityMint
|
||||
) as ParsedAccount<MintInfo>;
|
||||
const liquidityMint = cache.get(reserve.liquidityMint) as ParsedAccount<MintInfo>;
|
||||
let ltv = 0;
|
||||
let health = 0;
|
||||
let borrowedInQuote = 0;
|
||||
let collateralInQuote = 0;
|
||||
|
||||
if (liquidityMint) {
|
||||
const collateralMint = cache.get(
|
||||
item.collateralReserve.info.liquidityMint
|
||||
);
|
||||
const collateralMint = cache.get(item.collateralReserve.info.liquidityMint);
|
||||
|
||||
const collateral = fromLamports(
|
||||
collateralToLiquidity(
|
||||
obligation.info.depositedCollateral,
|
||||
item.reserve.info
|
||||
),
|
||||
collateralToLiquidity(obligation.info.depositedCollateral, item.reserve.info),
|
||||
collateralMint?.info
|
||||
);
|
||||
|
||||
const borrowed = wadToLamports(
|
||||
obligation.info.borrowAmountWad
|
||||
).toNumber();
|
||||
const borrowed = wadToLamports(obligation.info.borrowAmountWad).toNumber();
|
||||
|
||||
const borrowedAmount = simulateMarketOrderFill(
|
||||
borrowed,
|
||||
item.reserve.info,
|
||||
item.reserve.info.dexMarketOption
|
||||
? item.reserve.info.dexMarket
|
||||
: item.collateralReserve.info.dexMarket,
|
||||
item.reserve.info.dexMarketOption ? item.reserve.info.dexMarket : item.collateralReserve.info.dexMarket,
|
||||
true
|
||||
);
|
||||
|
||||
const liquidityMintAddress = item.reserve.info.liquidityMint.toBase58();
|
||||
const liquidityMint = cache.get(
|
||||
liquidityMintAddress
|
||||
) as ParsedAccount<MintInfo>;
|
||||
borrowedInQuote =
|
||||
fromLamports(borrowed, liquidityMint.info) *
|
||||
midPriceInUSD(liquidityMintAddress);
|
||||
collateralInQuote =
|
||||
collateral *
|
||||
midPriceInUSD(collateralMint?.pubkey.toBase58() || "");
|
||||
const liquidityMint = cache.get(liquidityMintAddress) as ParsedAccount<MintInfo>;
|
||||
borrowedInQuote = fromLamports(borrowed, liquidityMint.info) * midPriceInUSD(liquidityMintAddress);
|
||||
collateralInQuote = collateral * midPriceInUSD(collateralMint?.pubkey.toBase58() || '');
|
||||
|
||||
ltv = (100 * borrowedAmount) / collateral;
|
||||
|
||||
const liquidationThreshold =
|
||||
item.reserve.info.config.liquidationThreshold;
|
||||
const liquidationThreshold = item.reserve.info.config.liquidationThreshold;
|
||||
health = (collateral * liquidationThreshold) / 100 / borrowedAmount;
|
||||
}
|
||||
|
||||
|
@ -128,13 +101,9 @@ export function useEnrichedLendingObligations() {
|
|||
health,
|
||||
borrowedInQuote,
|
||||
collateralInQuote,
|
||||
liquidationThreshold:
|
||||
item.reserve.info.config.liquidationThreshold,
|
||||
liquidationThreshold: item.reserve.info.config.liquidationThreshold,
|
||||
repayName: getTokenName(tokenMap, reserve.liquidityMint),
|
||||
collateralName: getTokenName(
|
||||
tokenMap,
|
||||
collateralReserve.liquidityMint
|
||||
),
|
||||
collateralName: getTokenName(tokenMap, collateralReserve.liquidityMint),
|
||||
},
|
||||
} as EnrichedLendingObligation;
|
||||
})
|
||||
|
@ -142,9 +111,7 @@ export function useEnrichedLendingObligations() {
|
|||
);
|
||||
}, [obligations, availableReserves, midPriceInUSD, tokenMap]);
|
||||
|
||||
const [enriched, setEnriched] = useState<EnrichedLendingObligation[]>(
|
||||
enrichedFactory()
|
||||
);
|
||||
const [enriched, setEnriched] = useState<EnrichedLendingObligation[]>(enrichedFactory());
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = marketEmitter.onMarket(() => {
|
||||
|
@ -162,7 +129,7 @@ export function useEnrichedLendingObligations() {
|
|||
}
|
||||
|
||||
export function useEnrichedLendingObligation(address?: string | PublicKey) {
|
||||
const id = typeof address === "string" ? address : address?.toBase58();
|
||||
const id = typeof address === 'string' ? address : address?.toBase58();
|
||||
const { obligations } = useEnrichedLendingObligations();
|
||||
|
||||
const obligation = useMemo(() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LendingMarketParser, LendingMarket } from "../models/lending";
|
||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { LendingMarketParser, LendingMarket } from '../models/lending';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
|
||||
const getLendingMarkets = () => {
|
||||
return cache
|
||||
|
@ -11,9 +11,7 @@ const getLendingMarkets = () => {
|
|||
};
|
||||
|
||||
export function useLendingMarkets() {
|
||||
const [lendingMarkets, setLendingMarket] = useState<
|
||||
ParsedAccount<LendingMarket>[]
|
||||
>(getLendingMarkets());
|
||||
const [lendingMarkets, setLendingMarket] = useState<ParsedAccount<LendingMarket>[]>(getLendingMarkets());
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = cache.emitter.onCache((args) => {
|
||||
|
@ -33,10 +31,10 @@ export function useLendingMarkets() {
|
|||
}
|
||||
|
||||
export function useLendingMarket(address?: string | PublicKey) {
|
||||
const id = typeof address === "string" ? address : address?.toBase58();
|
||||
const [lendingMarket, setLendingMarket] = useState<
|
||||
ParsedAccount<LendingMarket>
|
||||
>(cache.get(id || "") as ParsedAccount<LendingMarket>);
|
||||
const id = typeof address === 'string' ? address : address?.toBase58();
|
||||
const [lendingMarket, setLendingMarket] = useState<ParsedAccount<LendingMarket>>(
|
||||
cache.get(id || '') as ParsedAccount<LendingMarket>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = cache.emitter.onCache((args) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LendingObligation, LendingObligationParser } from "../models/lending";
|
||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { LendingObligation, LendingObligationParser } from '../models/lending';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
|
||||
const getLendingObligations = () => {
|
||||
return cache
|
||||
|
@ -31,10 +31,8 @@ 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<LendingObligation>
|
||||
);
|
||||
const id = typeof address === 'string' ? address : address?.toBase58();
|
||||
const [obligationAccount, setObligationAccount] = useState(cache.get(id || '') as ParsedAccount<LendingObligation>);
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = cache.emitter.onCache((args) => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { LendingReserve, LendingReserveParser } from "../models/lending";
|
||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
||||
import { useConnectionConfig } from "./../contexts/connection";
|
||||
import { getTokenByName, KnownToken } from "../utils/utils";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { LendingReserve, LendingReserveParser } from '../models/lending';
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { getTokenByName, KnownToken } from 'common/src/utils/utils';
|
||||
|
||||
export const getLendingReserves = () => {
|
||||
return cache
|
||||
|
@ -13,9 +13,7 @@ export const getLendingReserves = () => {
|
|||
};
|
||||
|
||||
export function useLendingReserves() {
|
||||
const [reserveAccounts, setReserveAccounts] = useState<
|
||||
ParsedAccount<LendingReserve>[]
|
||||
>(getLendingReserves());
|
||||
const [reserveAccounts, setReserveAccounts] = useState<ParsedAccount<LendingReserve>[]>(getLendingReserves());
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = cache.emitter.onCache((args) => {
|
||||
|
@ -38,26 +36,20 @@ export function useLendingReserve(address?: string | PublicKey) {
|
|||
const { tokenMap } = useConnectionConfig();
|
||||
const { reserveAccounts } = useLendingReserves();
|
||||
let addressName = address;
|
||||
if (typeof address === "string") {
|
||||
if (typeof address === 'string') {
|
||||
const token: KnownToken | null = getTokenByName(tokenMap, address);
|
||||
if (token) {
|
||||
const account = reserveAccounts.filter(
|
||||
(acc) => acc.info.liquidityMint.toBase58() === token.mintAddress
|
||||
)[0];
|
||||
const account = reserveAccounts.filter((acc) => acc.info.liquidityMint.toBase58() === token.mintAddress)[0];
|
||||
if (account) {
|
||||
addressName = account.pubkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
const id = useMemo(
|
||||
() =>
|
||||
typeof addressName === "string" ? addressName : addressName?.toBase58(),
|
||||
[addressName]
|
||||
);
|
||||
const id = useMemo(() => (typeof addressName === 'string' ? addressName : addressName?.toBase58()), [addressName]);
|
||||
|
||||
const [reserveAccount, setReserveAccount] = useState<
|
||||
ParsedAccount<LendingReserve>
|
||||
>(cache.get(id || "") as ParsedAccount<LendingReserve>);
|
||||
const [reserveAccount, setReserveAccount] = useState<ParsedAccount<LendingReserve>>(
|
||||
cache.get(id || '') as ParsedAccount<LendingReserve>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = cache.emitter.onCache((args) => {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useConnectionConfig } from "../contexts/connection";
|
||||
import { getTokenName } from "../utils/utils";
|
||||
|
||||
export function useTokenName(mintAddress?: string | PublicKey) {
|
||||
const { tokenMap } = useConnectionConfig();
|
||||
const address =
|
||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58();
|
||||
return getTokenName(tokenMap, address);
|
||||
}
|
|
@ -1,19 +1,12 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMint } from "../contexts/accounts";
|
||||
import { useMarkets } from "../contexts/market";
|
||||
import { fromLamports } from "../utils/utils";
|
||||
import { useUserAccounts } from "./useUserAccounts";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMint } from 'common/src/contexts/accounts';
|
||||
import { useMarkets } from '../contexts/market';
|
||||
import { fromLamports } from 'common/src/utils/utils';
|
||||
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||
|
||||
export function useUserBalance(
|
||||
mintAddress?: PublicKey | string,
|
||||
account?: PublicKey
|
||||
) {
|
||||
const mint = useMemo(
|
||||
() =>
|
||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58(),
|
||||
[mintAddress]
|
||||
);
|
||||
export function useUserBalance(mintAddress?: PublicKey | string, account?: PublicKey) {
|
||||
const mint = useMemo(() => (typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58()), [mintAddress]);
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||
|
@ -21,29 +14,19 @@ export function useUserBalance(
|
|||
const mintInfo = useMint(mint);
|
||||
const accounts = useMemo(() => {
|
||||
return userAccounts
|
||||
.filter(
|
||||
(acc) =>
|
||||
mint === acc.info.mint.toBase58() &&
|
||||
(!account || account.equals(acc.pubkey))
|
||||
)
|
||||
.filter((acc) => mint === acc.info.mint.toBase58() && (!account || account.equals(acc.pubkey)))
|
||||
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
||||
}, [userAccounts, mint, account]);
|
||||
|
||||
const balanceLamports = useMemo(() => {
|
||||
return accounts.reduce(
|
||||
(res, item) => (res += item.info.amount.toNumber()),
|
||||
0
|
||||
);
|
||||
return accounts.reduce((res, item) => (res += item.info.amount.toNumber()), 0);
|
||||
}, [accounts]);
|
||||
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [
|
||||
mintInfo,
|
||||
balanceLamports,
|
||||
]);
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [mintInfo, balanceLamports]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateBalance = () => {
|
||||
setBalanceInUSD(balance * midPriceInUSD(mint || ""));
|
||||
setBalanceInUSD(balance * midPriceInUSD(mint || ''));
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket((args) => {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { cache, ParsedAccount } from "../contexts/accounts";
|
||||
import { calculateDepositAPY, LendingReserve } from "../models/lending";
|
||||
import { useUserAccounts } from "./useUserAccounts";
|
||||
import { useLendingReserves } from "./useLendingReserves";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { TokenAccount } from "../models";
|
||||
import { useMarkets } from "../contexts/market";
|
||||
import { fromLamports, getTokenName } from "../utils/utils";
|
||||
import { useConnectionConfig } from "../contexts/connection";
|
||||
import { calculateCollateralBalance } from "./useCollateralBalance";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||
import { calculateDepositAPY, LendingReserve } from '../models/lending';
|
||||
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||
import { useLendingReserves } from './useLendingReserves';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { TokenAccount } from 'common/src/models';
|
||||
import { useMarkets } from '../contexts/market';
|
||||
import { fromLamports, getTokenName } from 'common/src/utils/utils';
|
||||
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||
import { calculateCollateralBalance } from './useCollateralBalance';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
|
||||
export interface UserDeposit {
|
||||
account: TokenAccount;
|
||||
|
@ -45,28 +45,17 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
|||
}, [reserveAccounts, exclude, include]);
|
||||
|
||||
useEffect(() => {
|
||||
const activeMarkets = new Set(
|
||||
reserveAccounts.map((r) => r.info.dexMarket.toBase58())
|
||||
);
|
||||
const activeMarkets = new Set(reserveAccounts.map((r) => r.info.dexMarket.toBase58()));
|
||||
|
||||
const userDepositsFactory = () => {
|
||||
return userAccounts
|
||||
.filter((acc) =>
|
||||
reservesByCollateralMint.has(acc?.info.mint.toBase58())
|
||||
)
|
||||
.filter((acc) => reservesByCollateralMint.has(acc?.info.mint.toBase58()))
|
||||
.map((item) => {
|
||||
const reserve = reservesByCollateralMint.get(
|
||||
item?.info.mint.toBase58()
|
||||
) as ParsedAccount<LendingReserve>;
|
||||
const reserve = reservesByCollateralMint.get(item?.info.mint.toBase58()) as ParsedAccount<LendingReserve>;
|
||||
|
||||
let collateralMint = cache.get(
|
||||
reserve.info.collateralMint
|
||||
) as ParsedAccount<MintInfo>;
|
||||
let collateralMint = cache.get(reserve.info.collateralMint) as ParsedAccount<MintInfo>;
|
||||
|
||||
const amountLamports = calculateCollateralBalance(
|
||||
reserve.info,
|
||||
item?.info.amount.toNumber()
|
||||
);
|
||||
const amountLamports = calculateCollateralBalance(reserve.info, item?.info.amount.toNumber());
|
||||
const amount = fromLamports(amountLamports, collateralMint?.info);
|
||||
const price = midPriceInUSD(reserve.info.liquidityMint.toBase58());
|
||||
const amountInQuote = price * amount;
|
||||
|
@ -99,20 +88,10 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
|||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [
|
||||
userAccounts,
|
||||
reserveAccounts,
|
||||
reservesByCollateralMint,
|
||||
tokenMap,
|
||||
midPriceInUSD,
|
||||
marketEmitter,
|
||||
]);
|
||||
}, [userAccounts, reserveAccounts, reservesByCollateralMint, tokenMap, midPriceInUSD, marketEmitter]);
|
||||
|
||||
return {
|
||||
userDeposits,
|
||||
totalInQuote: userDeposits.reduce(
|
||||
(res, item) => res + item.info.amountInQuote,
|
||||
0
|
||||
),
|
||||
totalInQuote: userDeposits.reduce((res, item) => res + item.info.amountInQuote, 0),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo } from "react";
|
||||
import { useUserAccounts } from "./useUserAccounts";
|
||||
import { useEnrichedLendingObligations } from "./useEnrichedLendingObligations";
|
||||
import { TokenAccount } from "../models";
|
||||
import { useMemo } from 'react';
|
||||
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||
import { useEnrichedLendingObligations } from './useEnrichedLendingObligations';
|
||||
import { TokenAccount } from 'common/src/models';
|
||||
|
||||
export function useUserObligations() {
|
||||
const { userAccounts } = useUserAccounts();
|
||||
|
@ -21,26 +21,18 @@ export function useUserObligations() {
|
|||
}
|
||||
|
||||
return obligations
|
||||
.filter(
|
||||
(acc) => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined
|
||||
)
|
||||
.filter((acc) => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined)
|
||||
.map((ob) => {
|
||||
return {
|
||||
obligation: ob,
|
||||
userAccounts: [...accountsByMint.get(ob.info.tokenMint.toBase58())],
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote
|
||||
);
|
||||
.sort((a, b) => b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote);
|
||||
}, [accountsByMint, obligations]);
|
||||
|
||||
return {
|
||||
userObligations,
|
||||
totalInQuote: userObligations.reduce(
|
||||
(result, item) => result + item.obligation.info.borrowedInQuote,
|
||||
0
|
||||
),
|
||||
totalInQuote: userObligations.reduce((result, item) => result + item.obligation.info.borrowedInQuote, 0),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { Market, MARKETS, Orderbook } from "@project-serum/serum";
|
||||
import { AccountInfo, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
MintParser,
|
||||
ParsedAccountBase,
|
||||
cache,
|
||||
} from "./../../contexts/accounts";
|
||||
import { Market, MARKETS, Orderbook } from '@project-serum/serum';
|
||||
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||
import { MintParser, ParsedAccountBase, cache } from 'common/src/contexts/accounts';
|
||||
|
||||
export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||
const decoded = Orderbook.LAYOUT.decode(acc.data);
|
||||
|
@ -20,18 +16,11 @@ export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
|||
return details;
|
||||
};
|
||||
|
||||
const DEFAULT_DEX_ID = new PublicKey(
|
||||
"EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o"
|
||||
);
|
||||
const DEFAULT_DEX_ID = new PublicKey('EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o');
|
||||
|
||||
export const DexMarketParser = (
|
||||
pubkey: PublicKey,
|
||||
acc: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||
const market = MARKETS.find((m) => m.address.equals(pubkey));
|
||||
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(
|
||||
acc.data
|
||||
);
|
||||
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(acc.data);
|
||||
|
||||
const details = {
|
||||
pubkey,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
export * from "./account";
|
||||
export * from "./lending";
|
||||
export * from "./tokenSwap";
|
||||
export * from "./pool";
|
||||
export * from "./totals";
|
||||
export * from './lending';
|
||||
export * from './pool';
|
||||
export * from './totals';
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import { calculateUtilizationRatio, LendingReserve } from "./reserve";
|
||||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||
|
||||
export enum BorrowAmountType {
|
||||
LiquidityBorrowAmount = 0,
|
||||
|
@ -64,9 +60,9 @@ export const borrowInstruction = (
|
|||
hostFeeReceiver?: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("amount"),
|
||||
BufferLayout.u8("amountType"),
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('amount'),
|
||||
BufferLayout.u8('amountType'),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
@ -133,16 +129,12 @@ export const calculateBorrowAPY = (reserve: LendingReserve) => {
|
|||
const normalizedFactor = currentUtilization / optimalUtilization;
|
||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||
const minBorrowRate = reserve.config.minBorrowRate / 100;
|
||||
borrowAPY =
|
||||
normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
||||
borrowAPY = normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
||||
} else {
|
||||
const normalizedFactor =
|
||||
(currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
||||
const normalizedFactor = (currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
|
||||
borrowAPY =
|
||||
normalizedFactor * (maxBorrowRate - optimalBorrowRate) +
|
||||
optimalBorrowRate;
|
||||
borrowAPY = normalizedFactor * (maxBorrowRate - optimalBorrowRate) + optimalBorrowRate;
|
||||
}
|
||||
|
||||
return borrowAPY;
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { calculateBorrowAPY } from "./borrow";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import { calculateUtilizationRatio, LendingReserve } from "./reserve";
|
||||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
import { calculateBorrowAPY } from './borrow';
|
||||
import { LendingInstruction } from './lending';
|
||||
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||
|
||||
/// Deposit liquidity into a reserve. The output is a collateral token representing ownership
|
||||
/// of the reserve liquidity pool.
|
||||
|
@ -35,10 +31,7 @@ export const depositInstruction = (
|
|||
reserveSupply: PublicKey,
|
||||
collateralMint: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
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 * as Layout from "./../../utils/layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
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 * as Layout from 'common/src/utils/layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
|
||||
/// Purchase collateral tokens at a discount rate if the chosen obligation is unhealthy.
|
||||
///
|
||||
|
@ -43,10 +39,7 @@ export const liquidateInstruction = (
|
|||
dexOrderBookSide: PublicKey,
|
||||
memory: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { AccountInfo, PublicKey } from "@solana/web3.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
|
||||
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("version"),
|
||||
BufferLayout.u8("bumpSeed"),
|
||||
Layout.publicKey("owner"),
|
||||
Layout.publicKey("quoteMint"),
|
||||
Layout.publicKey("tokenProgramId"),
|
||||
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||
BufferLayout.u8('version'),
|
||||
BufferLayout.u8('bumpSeed'),
|
||||
Layout.publicKey('owner'),
|
||||
Layout.publicKey('quoteMint'),
|
||||
Layout.publicKey('tokenProgramId'),
|
||||
|
||||
// extra space for future contract changes
|
||||
BufferLayout.blob(62, "padding"),
|
||||
]
|
||||
);
|
||||
BufferLayout.blob(62, 'padding'),
|
||||
]);
|
||||
|
||||
export interface LendingMarket {
|
||||
version: number;
|
||||
|
@ -27,10 +25,7 @@ export const isLendingMarket = (info: AccountInfo<Buffer>) => {
|
|||
return info.data.length === LendingMarketLayout.span;
|
||||
};
|
||||
|
||||
export const LendingMarketParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const LendingMarketParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = LendingMarketLayout.decode(buffer);
|
||||
|
||||
|
|
|
@ -4,33 +4,31 @@ import {
|
|||
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 { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
} from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { LendingInstruction } from '.';
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
|
||||
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("version"),
|
||||
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||
BufferLayout.u8('version'),
|
||||
/// Amount of collateral tokens deposited for this obligation
|
||||
Layout.uint64("depositedCollateral"),
|
||||
Layout.uint64('depositedCollateral'),
|
||||
/// Reserve which collateral tokens were deposited into
|
||||
Layout.publicKey("collateralReserve"),
|
||||
Layout.publicKey('collateralReserve'),
|
||||
/// Borrow rate used for calculating interest.
|
||||
Layout.uint128("cumulativeBorrowRateWad"),
|
||||
Layout.uint128('cumulativeBorrowRateWad'),
|
||||
/// Amount of tokens borrowed for this obligation plus interest
|
||||
Layout.uint128("borrowAmountWad"),
|
||||
Layout.uint128('borrowAmountWad'),
|
||||
/// Reserve which tokens were borrowed from
|
||||
Layout.publicKey("borrowReserve"),
|
||||
Layout.publicKey('borrowReserve'),
|
||||
/// Mint address of the tokens for this obligation
|
||||
Layout.publicKey("tokenMint"),
|
||||
Layout.publicKey('tokenMint'),
|
||||
|
||||
// extra space for future contract changes
|
||||
BufferLayout.blob(128, "padding"),
|
||||
]
|
||||
);
|
||||
BufferLayout.blob(128, 'padding'),
|
||||
]);
|
||||
|
||||
export const isLendingObligation = (info: AccountInfo<Buffer>) => {
|
||||
return info.data.length === LendingObligationLayout.span;
|
||||
|
@ -47,10 +45,7 @@ export interface LendingObligation {
|
|||
tokenMint: PublicKey;
|
||||
}
|
||||
|
||||
export const LendingObligationParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const LendingObligationParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = LendingObligationLayout.decode(buffer);
|
||||
|
||||
|
@ -66,7 +61,7 @@ export const LendingObligationParser = (
|
|||
};
|
||||
|
||||
export const healthFactorToRiskColor = (health: number) => {
|
||||
return "";
|
||||
return '';
|
||||
};
|
||||
|
||||
export const initObligationInstruction = (
|
||||
|
@ -79,7 +74,7 @@ export const initObligationInstruction = (
|
|||
lendingMarket: PublicKey,
|
||||
lendingMarketAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
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 * as Layout from "./../../utils/layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
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 * as Layout from 'common/src/utils/layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
|
||||
/// Repay loaned tokens to a reserve and receive collateral tokens. The obligation balance
|
||||
/// will be recalculated for interest.
|
||||
|
@ -42,10 +38,7 @@ export const repayInstruction = (
|
|||
authority: PublicKey,
|
||||
transferAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -4,48 +4,47 @@ import {
|
|||
SYSVAR_CLOCK_PUBKEY,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import { wadToLamports } from "../../utils/utils";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
} from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import { wadToLamports } from 'common/src/utils/utils';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
|
||||
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("version"),
|
||||
Layout.uint64("lastUpdateSlot"),
|
||||
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('lendingMarket'),
|
||||
Layout.publicKey('liquidityMint'),
|
||||
BufferLayout.u8('liquidityMintDecimals'),
|
||||
Layout.publicKey('liquiditySupply'),
|
||||
Layout.publicKey('collateralMint'),
|
||||
Layout.publicKey('collateralSupply'),
|
||||
|
||||
Layout.publicKey("collateralFeesReceiver"),
|
||||
Layout.publicKey('collateralFeesReceiver'),
|
||||
|
||||
// TODO: replace u32 option with generic quivalent
|
||||
BufferLayout.u32("dexMarketOption"),
|
||||
Layout.publicKey("dexMarket"),
|
||||
BufferLayout.u32('dexMarketOption'),
|
||||
Layout.publicKey('dexMarket'),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
/// Optimal utilization rate as a percent
|
||||
BufferLayout.u8("optimalUtilizationRate"),
|
||||
BufferLayout.u8('optimalUtilizationRate'),
|
||||
/// The ratio of the loan to the value of the collateral as a percent
|
||||
BufferLayout.u8("loanToValueRatio"),
|
||||
BufferLayout.u8('loanToValueRatio'),
|
||||
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
|
||||
BufferLayout.u8("liquidationBonus"),
|
||||
BufferLayout.u8('liquidationBonus'),
|
||||
/// The percent at which an obligation is considered unhealthy
|
||||
BufferLayout.u8("liquidationThreshold"),
|
||||
BufferLayout.u8('liquidationThreshold'),
|
||||
/// Min borrow APY
|
||||
BufferLayout.u8("minBorrowRate"),
|
||||
BufferLayout.u8('minBorrowRate'),
|
||||
/// Optimal (utilization) borrow APY
|
||||
BufferLayout.u8("optimalBorrowRate"),
|
||||
BufferLayout.u8('optimalBorrowRate'),
|
||||
/// Max borrow APY
|
||||
BufferLayout.u8("maxBorrowRate"),
|
||||
BufferLayout.u8('maxBorrowRate'),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
|
@ -55,31 +54,30 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
|
|||
/// 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"),
|
||||
Layout.uint64('borrowFeeWad'),
|
||||
|
||||
/// Amount of fee going to host account, if provided in liquidate and repay
|
||||
BufferLayout.u8("hostFeePercentage"),
|
||||
BufferLayout.u8('hostFeePercentage'),
|
||||
],
|
||||
"fees"
|
||||
'fees'
|
||||
),
|
||||
],
|
||||
"config"
|
||||
'config'
|
||||
),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
Layout.uint128("cumulativeBorrowRateWad"),
|
||||
Layout.uint128("borrowedLiquidityWad"),
|
||||
Layout.uint64("availableLiquidity"),
|
||||
Layout.uint64("collateralMintSupply"),
|
||||
Layout.uint128('cumulativeBorrowRateWad'),
|
||||
Layout.uint128('borrowedLiquidityWad'),
|
||||
Layout.uint64('availableLiquidity'),
|
||||
Layout.uint64('collateralMintSupply'),
|
||||
],
|
||||
"state"
|
||||
'state'
|
||||
),
|
||||
|
||||
// extra space for future contract changes
|
||||
BufferLayout.blob(300, "padding"),
|
||||
]
|
||||
);
|
||||
BufferLayout.blob(300, 'padding'),
|
||||
]);
|
||||
|
||||
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
||||
return info.data.length === LendingReserveLayout.span;
|
||||
|
@ -124,10 +122,7 @@ export interface LendingReserve {
|
|||
};
|
||||
}
|
||||
|
||||
export const LendingReserveParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
export const LendingReserveParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = LendingReserveLayout.decode(buffer) as LendingReserve;
|
||||
|
||||
|
@ -165,9 +160,9 @@ export const initReserveInstruction = (
|
|||
dexMarket: PublicKey // TODO: optional
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
BufferLayout.u8("maxUtilizationRate"),
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('liquidityAmount'),
|
||||
BufferLayout.u8('maxUtilizationRate'),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
@ -207,10 +202,8 @@ export const initReserveInstruction = (
|
|||
});
|
||||
};
|
||||
|
||||
export const accrueInterestInstruction = (
|
||||
...reserveAccount: PublicKey[]
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
||||
export const accrueInterestInstruction = (...reserveAccount: PublicKey[]): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
@ -236,50 +229,30 @@ export const accrueInterestInstruction = (
|
|||
};
|
||||
|
||||
export const calculateUtilizationRatio = (reserve: LendingReserve) => {
|
||||
const totalBorrows = wadToLamports(
|
||||
reserve.state.borrowedLiquidityWad
|
||||
).toNumber();
|
||||
const currentUtilization =
|
||||
totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows);
|
||||
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 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)
|
||||
);
|
||||
return (reserve?.state.collateralMintSupply.toNumber() || 1) / reserveMarketCap(reserve);
|
||||
};
|
||||
|
||||
export const collateralToLiquidity = (
|
||||
collateralAmount: BN | number,
|
||||
reserve?: LendingReserve
|
||||
) => {
|
||||
const amount =
|
||||
typeof collateralAmount === "number"
|
||||
? collateralAmount
|
||||
: collateralAmount.toNumber();
|
||||
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();
|
||||
export const liquidityToCollateral = (liquidityAmount: BN | number, reserve?: LendingReserve) => {
|
||||
const amount = typeof liquidityAmount === 'number' ? liquidityAmount : liquidityAmount.toNumber();
|
||||
return Math.floor(amount * collateralExchangeRate(reserve));
|
||||
};
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||
import * as Layout from 'common/src/utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
|
||||
export const withdrawInstruction = (
|
||||
collateralAmount: number | BN,
|
||||
|
@ -20,10 +16,7 @@ export const withdrawInstruction = (
|
|||
authority: PublicKey,
|
||||
transferAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("collateralAmount"),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('collateralAmount')]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { TokenAccount } from "./account";
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { TokenAccount } from 'common/src/models/account';
|
||||
|
||||
export const DEFAULT_DENOMINATOR = 10_000;
|
||||
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
import * as BufferLayout from "buffer-layout";
|
||||
import { publicKey, uint64 } from "../utils/layout";
|
||||
|
||||
export { TokenSwap } from "@solana/spl-token-swap";
|
||||
|
||||
const FEE_LAYOUT = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.nu64("tradeFeeNumerator"),
|
||||
BufferLayout.nu64("tradeFeeDenominator"),
|
||||
BufferLayout.nu64("ownerTradeFeeNumerator"),
|
||||
BufferLayout.nu64("ownerTradeFeeDenominator"),
|
||||
BufferLayout.nu64("ownerWithdrawFeeNumerator"),
|
||||
BufferLayout.nu64("ownerWithdrawFeeDenominator"),
|
||||
BufferLayout.nu64("hostFeeNumerator"),
|
||||
BufferLayout.nu64("hostFeeDenominator"),
|
||||
],
|
||||
"fees"
|
||||
);
|
||||
|
||||
export const TokenSwapLayoutLegacyV0 = BufferLayout.struct([
|
||||
BufferLayout.u8("isInitialized"),
|
||||
BufferLayout.u8("nonce"),
|
||||
publicKey("tokenAccountA"),
|
||||
publicKey("tokenAccountB"),
|
||||
publicKey("tokenPool"),
|
||||
uint64("feesNumerator"),
|
||||
uint64("feesDenominator"),
|
||||
]);
|
||||
|
||||
export const TokenSwapLayoutV1: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("isInitialized"),
|
||||
BufferLayout.u8("nonce"),
|
||||
publicKey("tokenProgramId"),
|
||||
publicKey("tokenAccountA"),
|
||||
publicKey("tokenAccountB"),
|
||||
publicKey("tokenPool"),
|
||||
publicKey("mintA"),
|
||||
publicKey("mintB"),
|
||||
publicKey("feeAccount"),
|
||||
BufferLayout.u8("curveType"),
|
||||
uint64("tradeFeeNumerator"),
|
||||
uint64("tradeFeeDenominator"),
|
||||
uint64("ownerTradeFeeNumerator"),
|
||||
uint64("ownerTradeFeeDenominator"),
|
||||
uint64("ownerWithdrawFeeNumerator"),
|
||||
uint64("ownerWithdrawFeeDenominator"),
|
||||
BufferLayout.blob(16, "padding"),
|
||||
]
|
||||
);
|
||||
|
||||
const CURVE_NODE = BufferLayout.union(
|
||||
BufferLayout.u8(),
|
||||
BufferLayout.blob(32),
|
||||
"curve"
|
||||
);
|
||||
CURVE_NODE.addVariant(0, BufferLayout.struct([]), "constantProduct");
|
||||
CURVE_NODE.addVariant(
|
||||
1,
|
||||
BufferLayout.struct([BufferLayout.nu64("token_b_price")]),
|
||||
"constantPrice"
|
||||
);
|
||||
CURVE_NODE.addVariant(2, BufferLayout.struct([]), "stable");
|
||||
CURVE_NODE.addVariant(
|
||||
3,
|
||||
BufferLayout.struct([BufferLayout.nu64("token_b_offset")]),
|
||||
"offset"
|
||||
);
|
||||
|
||||
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("isInitialized"),
|
||||
BufferLayout.u8("nonce"),
|
||||
publicKey("tokenProgramId"),
|
||||
publicKey("tokenAccountA"),
|
||||
publicKey("tokenAccountB"),
|
||||
publicKey("tokenPool"),
|
||||
publicKey("mintA"),
|
||||
publicKey("mintB"),
|
||||
publicKey("feeAccount"),
|
||||
FEE_LAYOUT,
|
||||
CURVE_NODE,
|
||||
]
|
||||
);
|
|
@ -1,11 +1,11 @@
|
|||
import { HashRouter, Route, Switch } from "react-router-dom";
|
||||
import React from "react";
|
||||
import { WalletProvider } from "./contexts/wallet";
|
||||
import { ConnectionProvider } from "./contexts/connection";
|
||||
import { AccountsProvider } from "./contexts/accounts";
|
||||
import { MarketProvider } from "./contexts/market";
|
||||
import { LendingProvider } from "./contexts/lending";
|
||||
import { AppLayout } from "./components/Layout";
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { WalletProvider } from 'common/src/contexts/wallet';
|
||||
import { ConnectionProvider } from 'common/src/contexts/connection';
|
||||
import { AccountsProvider } from 'common/src/contexts/accounts';
|
||||
import { MarketProvider } from './contexts/market';
|
||||
import { LendingProvider } from './contexts/lending';
|
||||
import { AppLayout } from './components/Layout';
|
||||
|
||||
import {
|
||||
BorrowReserveView,
|
||||
|
@ -21,13 +21,13 @@ import {
|
|||
LiquidateView,
|
||||
LiquidateReserveView,
|
||||
MarginTrading,
|
||||
} from "./views";
|
||||
import { NewPosition } from "./views/margin/newPosition";
|
||||
} from './views';
|
||||
import { NewPosition } from './views/margin/newPosition';
|
||||
|
||||
export function Routes() {
|
||||
return (
|
||||
<>
|
||||
<HashRouter basename={"/"}>
|
||||
<HashRouter basename={'/'}>
|
||||
<ConnectionProvider>
|
||||
<WalletProvider>
|
||||
<AccountsProvider>
|
||||
|
@ -35,53 +35,22 @@ export function Routes() {
|
|||
<LendingProvider>
|
||||
<AppLayout>
|
||||
<Switch>
|
||||
<Route exact path="/" component={() => <HomeView />} />
|
||||
<Route
|
||||
exact
|
||||
path="/dashboard"
|
||||
children={<DashboardView />}
|
||||
/>
|
||||
<Route path="/reserve/:id" children={<ReserveView />} />
|
||||
<Route
|
||||
exact
|
||||
path="/deposit"
|
||||
component={() => <DepositView />}
|
||||
/>
|
||||
<Route
|
||||
path="/deposit/:id"
|
||||
children={<DepositReserveView />}
|
||||
/>
|
||||
<Route path="/withdraw/:id" children={<WithdrawView />} />
|
||||
<Route exact path="/borrow" children={<BorrowView />} />
|
||||
<Route
|
||||
path="/borrow/:id"
|
||||
children={<BorrowReserveView />}
|
||||
/>
|
||||
<Route
|
||||
path="/repay/loan/:obligation"
|
||||
children={<RepayReserveView />}
|
||||
/>
|
||||
<Route
|
||||
path="/repay/:reserve"
|
||||
children={<RepayReserveView />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/liquidate"
|
||||
children={<LiquidateView />}
|
||||
/>
|
||||
<Route
|
||||
path="/liquidate/:id"
|
||||
children={<LiquidateReserveView />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/margin"
|
||||
children={<MarginTrading />}
|
||||
/>
|
||||
<Route exact path='/' component={() => <HomeView />} />
|
||||
<Route exact path='/dashboard' children={<DashboardView />} />
|
||||
<Route path='/reserve/:id' children={<ReserveView />} />
|
||||
<Route exact path='/deposit' component={() => <DepositView />} />
|
||||
<Route path='/deposit/:id' children={<DepositReserveView />} />
|
||||
<Route path='/withdraw/:id' children={<WithdrawView />} />
|
||||
<Route exact path='/borrow' children={<BorrowView />} />
|
||||
<Route path='/borrow/:id' children={<BorrowReserveView />} />
|
||||
<Route path='/repay/loan/:obligation' children={<RepayReserveView />} />
|
||||
<Route path='/repay/:reserve' children={<RepayReserveView />} />
|
||||
<Route exact path='/liquidate' children={<LiquidateView />} />
|
||||
<Route path='/liquidate/:id' children={<LiquidateReserveView />} />
|
||||
<Route exact path='/margin' children={<MarginTrading />} />
|
||||
|
||||
<Route path="/margin/:id" children={<NewPosition />} />
|
||||
<Route exact path="/faucet" children={<FaucetView />} />
|
||||
<Route path='/margin/:id' children={<NewPosition />} />
|
||||
<Route exact path='/faucet' children={<FaucetView />} />
|
||||
</Switch>
|
||||
</AppLayout>
|
||||
</LendingProvider>
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { MintLayout, AccountLayout } from "@solana/spl-token";
|
||||
import { programIds } from "./ids";
|
||||
import {
|
||||
PoolInfo,
|
||||
TokenSwapLayout,
|
||||
TokenSwapLayoutLegacyV0 as TokenSwapLayoutV0,
|
||||
TokenSwapLayoutV1,
|
||||
} from "./../models";
|
||||
import { useConnection } from "../contexts/connection";
|
||||
import {
|
||||
cache,
|
||||
getMultipleAccounts,
|
||||
TokenAccountParser,
|
||||
} from "../contexts/accounts";
|
||||
import { Connection, PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { MintLayout, AccountLayout } from '@solana/spl-token';
|
||||
import { programIds } from 'common/src/utils/ids';
|
||||
import { TokenSwapLayout, TokenSwapLayoutLegacyV0 as TokenSwapLayoutV0, TokenSwapLayoutV1 } from 'common/src/models';
|
||||
import { PoolInfo } from '../models';
|
||||
import { useConnection } from 'common/src/contexts/connection';
|
||||
import { cache, getMultipleAccounts, TokenAccountParser } from 'common/src/contexts/accounts';
|
||||
|
||||
export const LIQUIDITY_PROVIDER_FEE = 0.003;
|
||||
export const SERUM_FEE = 0.0005;
|
||||
|
@ -80,16 +72,10 @@ export const usePools = () => {
|
|||
// TODO: this is not great
|
||||
// Ideally SwapLayout stores hash of all the mints to make finding of pool for a pair easier
|
||||
const holdings = await Promise.all(
|
||||
getHoldings(connection, [
|
||||
result.data.tokenAccountA,
|
||||
result.data.tokenAccountB,
|
||||
])
|
||||
getHoldings(connection, [result.data.tokenAccountA, result.data.tokenAccountB])
|
||||
);
|
||||
|
||||
pool.pubkeys.holdingMints = [
|
||||
holdings[0].info.mint,
|
||||
holdings[1].info.mint,
|
||||
] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [holdings[0].info.mint, holdings[1].info.mint] as PublicKey[];
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
@ -100,10 +86,7 @@ export const usePools = () => {
|
|||
let pool = toPoolInfo(result, swapId);
|
||||
pool.legacy = isLegacy;
|
||||
pool.pubkeys.feeAccount = result.data.feeAccount;
|
||||
pool.pubkeys.holdingMints = [
|
||||
result.data.mintA,
|
||||
result.data.mintB,
|
||||
] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [result.data.mintA, result.data.mintB] as PublicKey[];
|
||||
|
||||
poolsArray.push(pool as PoolInfo);
|
||||
}
|
||||
|
@ -125,8 +108,7 @@ export const usePools = () => {
|
|||
|
||||
// This will pre-cache all accounts used by pools
|
||||
// All those accounts are updated whenever there is a change
|
||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||
({ keys, array }) => {
|
||||
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||
return array.map((obj, index) => {
|
||||
const pubKey = keys[index];
|
||||
if (obj.data.length === AccountLayout.span) {
|
||||
|
@ -139,17 +121,15 @@ export const usePools = () => {
|
|||
|
||||
return obj;
|
||||
}) as any[];
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return poolsArray;
|
||||
};
|
||||
Promise.all([
|
||||
queryPools(programIds().swap),
|
||||
...programIds().swap_legacy.map((leg) => queryPools(leg, true)),
|
||||
]).then((all) => {
|
||||
Promise.all([queryPools(programIds().swap), ...programIds().swap_legacy.map((leg) => queryPools(leg, true))]).then(
|
||||
(all) => {
|
||||
setPools(all.flat());
|
||||
});
|
||||
}
|
||||
);
|
||||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -165,9 +145,7 @@ export const usePools = () => {
|
|||
pubkey: new PublicKey(id),
|
||||
};
|
||||
|
||||
const index =
|
||||
pools &&
|
||||
pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
||||
const index = pools && pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
||||
if (index && index >= 0 && pools) {
|
||||
// TODO: check if account is empty?
|
||||
|
||||
|
@ -177,16 +155,13 @@ export const usePools = () => {
|
|||
let pool = toPoolInfo(updated, programIds().swap);
|
||||
|
||||
pool.pubkeys.feeAccount = updated.data.feeAccount;
|
||||
pool.pubkeys.holdingMints = [
|
||||
updated.data.mintA,
|
||||
updated.data.mintB,
|
||||
] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [updated.data.mintA, updated.data.mintB] as PublicKey[];
|
||||
|
||||
setPools([...pools, pool]);
|
||||
}
|
||||
}
|
||||
},
|
||||
"singleGossip"
|
||||
'singleGossip'
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
@ -218,10 +193,7 @@ export const usePoolForBasket = (mints: (string | undefined)[]) => {
|
|||
for (let i = 0; i < matchingPool.length; i++) {
|
||||
const p = matchingPool[i];
|
||||
|
||||
const account = await cache.query(
|
||||
connection,
|
||||
p.pubkeys.holdingAccounts[0]
|
||||
);
|
||||
const account = await cache.query(connection, p.pubkeys.holdingAccounts[0]);
|
||||
|
||||
if (!account.info.amount.eqn(0)) {
|
||||
setPool(p);
|
||||
|
@ -239,9 +211,7 @@ function estimateProceedsFromInput(
|
|||
proceedsQuantityInPool: number,
|
||||
inputAmount: number
|
||||
): number {
|
||||
return (
|
||||
(proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount)
|
||||
);
|
||||
return (proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount);
|
||||
}
|
||||
|
||||
function estimateInputFromProceeds(
|
||||
|
@ -250,13 +220,10 @@ function estimateInputFromProceeds(
|
|||
proceedsAmount: number
|
||||
): number | string {
|
||||
if (proceedsAmount >= proceedsQuantityInPool) {
|
||||
return "Not possible";
|
||||
return 'Not possible';
|
||||
}
|
||||
|
||||
return (
|
||||
(inputQuantityInPool * proceedsAmount) /
|
||||
(proceedsQuantityInPool - proceedsAmount)
|
||||
);
|
||||
return (inputQuantityInPool * proceedsAmount) / (proceedsQuantityInPool - proceedsAmount);
|
||||
}
|
||||
|
||||
export enum PoolOperation {
|
||||
|
@ -273,20 +240,14 @@ export async function calculateDependentAmount(
|
|||
op: PoolOperation
|
||||
): Promise<number | string | undefined> {
|
||||
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
|
||||
const accountA = await cache.query(
|
||||
connection,
|
||||
pool.pubkeys.holdingAccounts[0]
|
||||
);
|
||||
const accountA = await cache.query(connection, pool.pubkeys.holdingAccounts[0]);
|
||||
const amountA = accountA.info.amount.toNumber();
|
||||
|
||||
const accountB = await cache.query(
|
||||
connection,
|
||||
pool.pubkeys.holdingAccounts[1]
|
||||
);
|
||||
const accountB = await cache.query(connection, pool.pubkeys.holdingAccounts[1]);
|
||||
let amountB = accountB.info.amount.toNumber();
|
||||
|
||||
if (!poolMint.mintAuthority) {
|
||||
throw new Error("Mint doesnt have authority");
|
||||
throw new Error('Mint doesnt have authority');
|
||||
}
|
||||
|
||||
if (poolMint.supply.eqn(0)) {
|
||||
|
@ -308,14 +269,8 @@ export async function calculateDependentAmount(
|
|||
}
|
||||
|
||||
const isFirstIndependent = accountA.info.mint.toBase58() === independent;
|
||||
const depPrecision = Math.pow(
|
||||
10,
|
||||
isFirstIndependent ? mintB.decimals : mintA.decimals
|
||||
);
|
||||
const indPrecision = Math.pow(
|
||||
10,
|
||||
isFirstIndependent ? mintA.decimals : mintB.decimals
|
||||
);
|
||||
const depPrecision = Math.pow(10, isFirstIndependent ? mintB.decimals : mintA.decimals);
|
||||
const indPrecision = Math.pow(10, isFirstIndependent ? mintA.decimals : mintB.decimals);
|
||||
const indAdjustedAmount = amount * indPrecision;
|
||||
|
||||
let indBasketQuantity = isFirstIndependent ? amountA : amountB;
|
||||
|
@ -330,27 +285,18 @@ export async function calculateDependentAmount(
|
|||
} else {
|
||||
switch (+op) {
|
||||
case PoolOperation.Add:
|
||||
depAdjustedAmount =
|
||||
(depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
||||
depAdjustedAmount = (depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
||||
break;
|
||||
case PoolOperation.SwapGivenProceeds:
|
||||
depAdjustedAmount = estimateInputFromProceeds(
|
||||
depBasketQuantity,
|
||||
indBasketQuantity,
|
||||
indAdjustedAmount
|
||||
);
|
||||
depAdjustedAmount = estimateInputFromProceeds(depBasketQuantity, indBasketQuantity, indAdjustedAmount);
|
||||
break;
|
||||
case PoolOperation.SwapGivenInput:
|
||||
depAdjustedAmount = estimateProceedsFromInput(
|
||||
indBasketQuantity,
|
||||
depBasketQuantity,
|
||||
indAdjustedAmount
|
||||
);
|
||||
depAdjustedAmount = estimateProceedsFromInput(indBasketQuantity, depBasketQuantity, indAdjustedAmount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof depAdjustedAmount === "string") {
|
||||
if (typeof depAdjustedAmount === 'string') {
|
||||
return depAdjustedAmount;
|
||||
}
|
||||
if (depAdjustedAmount === undefined) {
|
||||
|
|
|
@ -1,246 +1,7 @@
|
|||
import { useCallback, useState } from "react";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
import { getTokenName, KnownTokenMap } from 'common/src/utils/utils';
|
||||
import { PoolInfo } from '../models';
|
||||
|
||||
import { PoolInfo, TokenAccount } from "./../models";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { WAD, ZERO } from "../constants";
|
||||
|
||||
export interface KnownToken {
|
||||
tokenSymbol: string;
|
||||
tokenName: string;
|
||||
icon: string;
|
||||
mintAddress: string;
|
||||
}
|
||||
|
||||
export type KnownTokenMap = Map<string, KnownToken>;
|
||||
|
||||
export const formatPriceNumber = new Intl.NumberFormat("en-US", {
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 8,
|
||||
});
|
||||
|
||||
export function useLocalStorageState(key: string, defaultState?: string) {
|
||||
const [state, setState] = useState(() => {
|
||||
// NOTE: Not sure if this is ok
|
||||
const storedState = localStorage.getItem(key);
|
||||
if (storedState) {
|
||||
return JSON.parse(storedState);
|
||||
}
|
||||
return defaultState;
|
||||
});
|
||||
|
||||
const setLocalStorageState = useCallback(
|
||||
(newState) => {
|
||||
const changed = state !== newState;
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
setState(newState);
|
||||
if (newState === null) {
|
||||
localStorage.removeItem(key);
|
||||
} else {
|
||||
localStorage.setItem(key, JSON.stringify(newState));
|
||||
}
|
||||
},
|
||||
[state, key]
|
||||
);
|
||||
|
||||
return [state, setLocalStorageState];
|
||||
}
|
||||
|
||||
// shorten the checksummed version of the input address to have 4 characters at start and end
|
||||
export function shortenAddress(address: string, chars = 4): string {
|
||||
return `${address.slice(0, chars)}...${address.slice(-chars)}`;
|
||||
}
|
||||
|
||||
export function getTokenName(
|
||||
map: KnownTokenMap,
|
||||
mint?: string | PublicKey,
|
||||
shorten = true
|
||||
): string {
|
||||
const mintAddress = typeof mint === "string" ? mint : mint?.toBase58();
|
||||
|
||||
if (!mintAddress) {
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
const knownSymbol = map.get(mintAddress)?.tokenSymbol;
|
||||
if (knownSymbol) {
|
||||
return knownSymbol;
|
||||
}
|
||||
|
||||
return shorten ? `${mintAddress.substring(0, 5)}...` : mintAddress;
|
||||
}
|
||||
|
||||
export function getTokenByName(tokenMap: KnownTokenMap, name: string) {
|
||||
let token: KnownToken | null = null;
|
||||
for (const val of tokenMap.values()) {
|
||||
if (val.tokenSymbol === name) {
|
||||
token = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getTokenIcon(
|
||||
map: KnownTokenMap,
|
||||
mintAddress?: string | PublicKey
|
||||
): string | undefined {
|
||||
const address =
|
||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58();
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
return map.get(address)?.icon;
|
||||
}
|
||||
|
||||
export function isKnownMint(map: KnownTokenMap, mintAddress: string) {
|
||||
return !!map.get(mintAddress);
|
||||
}
|
||||
|
||||
export const STABLE_COINS = new Set(["USDC", "wUSDC", "USDT"]);
|
||||
|
||||
export function chunks<T>(array: T[], size: number): T[][] {
|
||||
return Array.apply<number, T[], T[][]>(
|
||||
0,
|
||||
new Array(Math.ceil(array.length / size))
|
||||
).map((_, index) => array.slice(index * size, (index + 1) * size));
|
||||
}
|
||||
|
||||
export function toLamports(
|
||||
account?: TokenAccount | number,
|
||||
mint?: MintInfo
|
||||
): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount =
|
||||
typeof account === "number" ? account : account.info.amount?.toNumber();
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
return Math.floor(amount * precision);
|
||||
}
|
||||
|
||||
export function wadToLamports(amount?: BN): BN {
|
||||
return amount?.div(WAD) || ZERO;
|
||||
}
|
||||
|
||||
export function fromLamports(
|
||||
account?: TokenAccount | number | BN,
|
||||
mint?: MintInfo,
|
||||
rate: number = 1.0
|
||||
): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount = Math.floor(
|
||||
typeof account === "number"
|
||||
? account
|
||||
: BN.isBN(account)
|
||||
? account.toNumber()
|
||||
: account.info.amount.toNumber()
|
||||
);
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
return (amount / precision) * rate;
|
||||
}
|
||||
|
||||
var SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"];
|
||||
|
||||
const abbreviateNumber = (number: number, precision: number) => {
|
||||
let tier = (Math.log10(number) / 3) | 0;
|
||||
let scaled = number;
|
||||
let suffix = SI_SYMBOL[tier];
|
||||
if (tier !== 0) {
|
||||
let scale = Math.pow(10, tier * 3);
|
||||
scaled = number / scale;
|
||||
}
|
||||
|
||||
return scaled.toFixed(precision) + suffix;
|
||||
};
|
||||
|
||||
export const formatAmount = (
|
||||
val: number,
|
||||
precision: number = 6,
|
||||
abbr: boolean = true
|
||||
) => (abbr ? abbreviateNumber(val, precision) : val.toFixed(precision));
|
||||
|
||||
export function formatTokenAmount(
|
||||
account?: TokenAccount,
|
||||
mint?: MintInfo,
|
||||
rate: number = 1.0,
|
||||
prefix = "",
|
||||
suffix = "",
|
||||
precision = 6,
|
||||
abbr = false
|
||||
): string {
|
||||
if (!account) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `${[prefix]}${formatAmount(
|
||||
fromLamports(account, mint, rate),
|
||||
precision,
|
||||
abbr
|
||||
)}${suffix}`;
|
||||
}
|
||||
|
||||
export const formatUSD = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
});
|
||||
|
||||
const numberFormater = new Intl.NumberFormat("en-US", {
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export const formatNumber = {
|
||||
format: (val?: number) => {
|
||||
if (!val) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return numberFormater.format(val);
|
||||
},
|
||||
};
|
||||
|
||||
export const formatPct = new Intl.NumberFormat("en-US", {
|
||||
style: "percent",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export function convert(
|
||||
account?: TokenAccount | number,
|
||||
mint?: MintInfo,
|
||||
rate: number = 1.0
|
||||
): number {
|
||||
if (!account) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const amount =
|
||||
typeof account === "number" ? account : account.info.amount?.toNumber();
|
||||
|
||||
const precision = Math.pow(10, mint?.decimals || 0);
|
||||
let result = (amount / precision) * rate;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getPoolName(
|
||||
map: KnownTokenMap,
|
||||
pool: PoolInfo,
|
||||
shorten = true
|
||||
) {
|
||||
export function getPoolName(map: KnownTokenMap, pool: PoolInfo, shorten = true) {
|
||||
const sorted = pool.pubkeys.holdingMints.map((a) => a.toBase58()).sort();
|
||||
return sorted.map((item) => getTokenName(map, item, shorten)).join("/");
|
||||
return sorted.map((item) => getTokenName(map, item, shorten)).join('/');
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import React from "react";
|
||||
import { useTokenName, useBorrowingPower } from "../../hooks";
|
||||
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../../components/TokenIcon";
|
||||
import { formatNumber, formatPct } from "../../utils/utils";
|
||||
import { Button } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { LABELS } from "../../constants";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
import React from 'react';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import { useBorrowingPower } from '../../hooks';
|
||||
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
|
||||
import { TokenIcon } from '../../components/TokenIcon';
|
||||
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
|
||||
export const BorrowItem = (props: {
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
export const BorrowItem = (props: { reserve: LendingReserve; address: PublicKey }) => {
|
||||
const name = useTokenName(props.reserve.liquidityMint);
|
||||
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
|
||||
|
||||
|
@ -22,8 +20,8 @@ export const BorrowItem = (props: {
|
|||
|
||||
return (
|
||||
<Link to={`/borrow/${props.address.toBase58()}`}>
|
||||
<div className="borrow-item">
|
||||
<span style={{ display: "flex" }}>
|
||||
<div className='borrow-item'>
|
||||
<span style={{ display: 'flex' }}>
|
||||
<TokenIcon mintAddress={props.reserve.liquidityMint} />
|
||||
{name}
|
||||
</span>
|
||||
|
@ -33,14 +31,12 @@ export const BorrowItem = (props: {
|
|||
<div>
|
||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(totalInQuote)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(apr)}</div>
|
||||
<div>
|
||||
<Button type="primary">
|
||||
<Button type='primary'>
|
||||
<span>{LABELS.BORROW_ACTION}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import { BarChartStatistic } from "../../../components/BarChartStatistic";
|
||||
import { LABELS } from "../../../constants";
|
||||
import { formatNumber } from "../../../utils/utils";
|
||||
import { useUserDeposits } from "./../../../hooks";
|
||||
import { DepositItem } from "./item";
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import { BarChartStatistic } from '../../../components/BarChartStatistic';
|
||||
import { LABELS } from '../../../constants';
|
||||
import { formatNumber } from 'common/src/utils/utils';
|
||||
import { useUserDeposits } from './../../../hooks';
|
||||
import { DepositItem } from './item';
|
||||
|
||||
export const DashboardDeposits = () => {
|
||||
const { userDeposits, totalInQuote } = useUserDeposits();
|
||||
|
@ -12,11 +12,10 @@ export const DashboardDeposits = () => {
|
|||
return (
|
||||
<Card
|
||||
title={
|
||||
<div className="dashboard-title">
|
||||
<div className='dashboard-title'>
|
||||
<div>{LABELS.DASHBOARD_TITLE_DEPOSITS}</div>
|
||||
<div>
|
||||
<span>{LABELS.TOTAL_TITLE}: </span>$
|
||||
{formatNumber.format(totalInQuote)}
|
||||
<span>{LABELS.TOTAL_TITLE}: </span>${formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -26,17 +25,14 @@ export const DashboardDeposits = () => {
|
|||
getPct={(item) => item.info.amountInQuote / totalInQuote}
|
||||
name={(item) => item.info.name}
|
||||
/>
|
||||
<div className="dashboard-item dashboard-header">
|
||||
<div className='dashboard-item dashboard-header'>
|
||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||
<div>{LABELS.TABLE_TITLE_DEPOSIT_BALANCE}</div>
|
||||
<div>{LABELS.TABLE_TITLE_APY}</div>
|
||||
<div></div>
|
||||
</div>
|
||||
{userDeposits.map((deposit) => (
|
||||
<DepositItem
|
||||
key={deposit.account.pubkey.toBase58()}
|
||||
userDeposit={deposit}
|
||||
/>
|
||||
<DepositItem key={deposit.account.pubkey.toBase58()} userDeposit={deposit} />
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { useTokenName, UserDeposit } from "../../../hooks";
|
||||
import { calculateDepositAPY } from "../../../models/lending";
|
||||
import { TokenIcon } from "../../../components/TokenIcon";
|
||||
import { formatNumber, formatPct } from "../../../utils/utils";
|
||||
import { Button } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { LABELS } from "../../../constants";
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTokenName } from 'common/src/hooks';
|
||||
import { UserDeposit } from '../../../hooks';
|
||||
import { calculateDepositAPY } from '../../../models/lending';
|
||||
import { TokenIcon } from '../../../components/TokenIcon';
|
||||
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LABELS } from '../../../constants';
|
||||
|
||||
export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
||||
const { reserve, info } = props.userDeposit;
|
||||
const mintAddress = reserve.info.liquidityMint;
|
||||
const name = useTokenName(mintAddress);
|
||||
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(reserve.info), [
|
||||
reserve,
|
||||
]);
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(reserve.info), [reserve]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-item">
|
||||
<span style={{ display: "flex" }}>
|
||||
<div className='dashboard-item'>
|
||||
<span style={{ display: 'flex' }}>
|
||||
<TokenIcon mintAddress={mintAddress} />
|
||||
{name}
|
||||
</span>
|
||||
|
@ -27,20 +26,18 @@ export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
|||
<div>
|
||||
<em>{formatNumber.format(info.amount)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(info.amountInQuote)}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(info.amountInQuote)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(depositAPY)}</div>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Link to={`/deposit/${reserve.pubkey.toBase58()}`}>
|
||||
<Button type="primary">
|
||||
<Button type='primary'>
|
||||
<span>{LABELS.DEPOSIT_ACTION}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to={`/withdraw/${reserve.pubkey.toBase58()}`}>
|
||||
<Button type="text">
|
||||
<Button type='text'>
|
||||
<span>{LABELS.WITHDRAW_ACTION}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue