Intermediary step where ive gotten everything at least green and moved to common and linked

This commit is contained in:
Dummy Tester 123 2021-02-06 16:08:34 -06:00
parent 27c62ce9d3
commit 7e2749b9bc
118 changed files with 2387 additions and 3400 deletions

7
.gitignore vendored
View File

@ -2,7 +2,12 @@
# dependencies
*/node_modules
.yarn/
.yarn/*
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
/.pnp
.pnp.js

View File

@ -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

55
.yarn/releases/yarn-berry.cjs vendored Executable file

File diff suppressed because one or more lines are too long

5
.yarn/sdks/integrations.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# This file is automatically generated by PnPify.
# Manual changes will be lost!
integrations:
- vscode

20
.yarn/sdks/typescript/bin/tsc vendored Executable file
View File

@ -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`);

20
.yarn/sdks/typescript/bin/tsserver vendored Executable file
View File

@ -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`);

20
.yarn/sdks/typescript/lib/tsc.js vendored Normal file
View File

@ -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`);

111
.yarn/sdks/typescript/lib/tsserver.js vendored Normal file
View File

@ -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`));

20
.yarn/sdks/typescript/lib/typescript.js vendored Normal file
View File

@ -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`);

6
.yarn/sdks/typescript/package.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "typescript",
"version": "4.1.3-pnpify",
"main": "./lib/typescript.js",
"type": "commonjs"
}

View File

@ -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"

View File

@ -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,

View File

@ -0,0 +1 @@
export * from './account';

View File

@ -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}
>

View File

@ -0,0 +1 @@
export * from './math';

View File

@ -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);

View File

@ -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)})`);
}
}

View File

@ -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}

View File

@ -0,0 +1,3 @@
export * from './useUserAccounts';
export * from './useAccountByMint';
export * from './useTokenName';

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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();

View File

@ -0,0 +1,2 @@
export * from './account';
export * from './tokenSwap';

View File

@ -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,
]);

View File

@ -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');
}
};

View File

@ -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);
};

204
common/src/utils/utils.ts Normal file
View File

@ -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;
}

5
common/tsconfig.json Normal file
View File

@ -0,0 +1,5 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {},
"include": ["src"]
}

Binary file not shown.

View File

@ -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",

View File

@ -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) {

View File

@ -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 {

View File

@ -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';

View File

@ -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}`,
});
};

View File

@ -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;
}

View File

@ -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}`,
});
};

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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}
/>

View File

@ -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">
&nbsp; {userDeposit ? formatAmount(userDeposit.info.amount) : "--"}
</span>
<span className='token-balance'>&nbsp; {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}
/>

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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>

View File

@ -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}

View File

@ -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"
mode='inline'
className='bottom-links'
>
<Menu.Item key="16" icon={<ForkOutlined />}>
<a
title="Fork"
href={`${config.repository.url}/fork`}
target="_blank"
rel="noopener noreferrer"
>
<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>

View File

@ -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}

View File

@ -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>

View File

@ -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 ? '<' : ''}
&nbsp;{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>

View File

@ -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}

View File

@ -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>

View File

@ -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}
/>
);

View File

@ -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}>

View File

@ -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>

View File

@ -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%' }} />;
};

View File

@ -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"
>
&nbsp;{" "}
{hasBalance
? balance < 0.001
? "<0.001"
: balance.toFixed(3)
: "-"}
<span title={balance.toString()} key={mintAddress} className='token-balance'>
&nbsp; {hasBalance ? (balance < 0.001 ? '<0.001' : balance.toFixed(3)) : '-'}
</span>
) : null}
</div>

View File

@ -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>
);

View File

@ -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>
)}

View File

@ -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>
)}

View File

@ -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';

View File

@ -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> }) => {
if (isLendingReserve(item.account)) {
const reserve = cache.add(
item.pubkey.toBase58(),
item.account,
LendingReserveParser
);
const processAccount = useCallback((item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
if (isLendingReserve(item.account)) {
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
);
} else if (isLendingObligation(item.account)) {
return cache.add(
item.pubkey.toBase58(),
item.account,
LendingObligationParser
);
}
},
[]
);
return reserve;
} else if (isLendingMarket(item.account)) {
return cache.add(item.pubkey.toBase58(), item.account, LendingMarketParser);
} else if (isLendingObligation(item.account)) {
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 }) => {
return array.map((obj, index) => {
const address = keys[index];
cache.add(address, obj);
return obj;
}) as any[];
}
);
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 () => {

View File

@ -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 array.map((item, index) => {
const address = keys[index];
return cache.add(new PublicKey(address), item);
});
}
);
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 {

View File

@ -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,
};
}

View File

@ -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';

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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(() => {

View File

@ -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) => {

View File

@ -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) => {

View File

@ -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) => {

View File

@ -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);
}

View File

@ -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) => {

View File

@ -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),
};
}

View File

@ -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),
};
}

View File

@ -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,

View File

@ -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';

View File

@ -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;

View File

@ -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(

View File

@ -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(

View File

@ -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"),
]
);
// extra space for future contract changes
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);

View File

@ -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"),
/// Amount of collateral tokens deposited for this obligation
Layout.uint64("depositedCollateral"),
/// Reserve which collateral tokens were deposited into
Layout.publicKey("collateralReserve"),
/// Borrow rate used for calculating interest.
Layout.uint128("cumulativeBorrowRateWad"),
/// Amount of tokens borrowed for this obligation plus interest
Layout.uint128("borrowAmountWad"),
/// Reserve which tokens were borrowed from
Layout.publicKey("borrowReserve"),
/// Mint address of the tokens for this obligation
Layout.publicKey("tokenMint"),
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct([
BufferLayout.u8('version'),
/// Amount of collateral tokens deposited for this obligation
Layout.uint64('depositedCollateral'),
/// Reserve which collateral tokens were deposited into
Layout.publicKey('collateralReserve'),
/// Borrow rate used for calculating interest.
Layout.uint128('cumulativeBorrowRateWad'),
/// Amount of tokens borrowed for this obligation plus interest
Layout.uint128('borrowAmountWad'),
/// Reserve which tokens were borrowed from
Layout.publicKey('borrowReserve'),
/// Mint address of the tokens for this obligation
Layout.publicKey('tokenMint'),
// extra space for future contract changes
BufferLayout.blob(128, "padding"),
]
);
// extra space for future contract changes
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(

View File

@ -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(

View File

@ -4,82 +4,80 @@ 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"),
// TODO: replace u32 option with generic quivalent
BufferLayout.u32('dexMarketOption'),
Layout.publicKey('dexMarket'),
BufferLayout.struct(
[
/// Optimal utilization rate as a percent
BufferLayout.u8("optimalUtilizationRate"),
/// The ratio of the loan to the value of the collateral as a percent
BufferLayout.u8("loanToValueRatio"),
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
BufferLayout.u8("liquidationBonus"),
/// The percent at which an obligation is considered unhealthy
BufferLayout.u8("liquidationThreshold"),
/// Min borrow APY
BufferLayout.u8("minBorrowRate"),
/// Optimal (utilization) borrow APY
BufferLayout.u8("optimalBorrowRate"),
/// Max borrow APY
BufferLayout.u8("maxBorrowRate"),
BufferLayout.struct(
[
/// Optimal utilization rate as a percent
BufferLayout.u8('optimalUtilizationRate'),
/// The ratio of the loan to the value of the collateral as a percent
BufferLayout.u8('loanToValueRatio'),
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
BufferLayout.u8('liquidationBonus'),
/// The percent at which an obligation is considered unhealthy
BufferLayout.u8('liquidationThreshold'),
/// Min borrow APY
BufferLayout.u8('minBorrowRate'),
/// Optimal (utilization) borrow APY
BufferLayout.u8('optimalBorrowRate'),
/// Max borrow APY
BufferLayout.u8('maxBorrowRate'),
BufferLayout.struct(
[
/// Fee assessed on `BorrowReserveLiquidity`, expressed as a Wad.
/// Must be between 0 and 10^18, such that 10^18 = 1. A few examples for
/// clarity:
/// 1% = 10_000_000_000_000_000
/// 0.01% (1 basis point) = 100_000_000_000_000
/// 0.00001% (Aave borrow fee) = 100_000_000_000
Layout.uint64("borrowFeeWad"),
BufferLayout.struct(
[
/// Fee assessed on `BorrowReserveLiquidity`, expressed as a Wad.
/// Must be between 0 and 10^18, such that 10^18 = 1. A few examples for
/// clarity:
/// 1% = 10_000_000_000_000_000
/// 0.01% (1 basis point) = 100_000_000_000_000
/// 0.00001% (Aave borrow fee) = 100_000_000_000
Layout.uint64('borrowFeeWad'),
/// Amount of fee going to host account, if provided in liquidate and repay
BufferLayout.u8("hostFeePercentage"),
],
"fees"
),
],
"config"
),
/// Amount of fee going to host account, if provided in liquidate and repay
BufferLayout.u8('hostFeePercentage'),
],
'fees'
),
],
'config'
),
BufferLayout.struct(
[
Layout.uint128("cumulativeBorrowRateWad"),
Layout.uint128("borrowedLiquidityWad"),
Layout.uint64("availableLiquidity"),
Layout.uint64("collateralMintSupply"),
],
"state"
),
BufferLayout.struct(
[
Layout.uint128('cumulativeBorrowRateWad'),
Layout.uint128('borrowedLiquidityWad'),
Layout.uint64('availableLiquidity'),
Layout.uint64('collateralMintSupply'),
],
'state'
),
// extra space for future contract changes
BufferLayout.blob(300, "padding"),
]
);
// extra space for future contract changes
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));
};

View File

@ -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(

View File

@ -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;

View File

@ -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,
]
);

View File

@ -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>

View File

@ -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,31 +108,28 @@ 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 }) => {
return array.map((obj, index) => {
const pubKey = keys[index];
if (obj.data.length === AccountLayout.span) {
return cache.add(pubKey, obj, TokenAccountParser);
} else if (obj.data.length === MintLayout.span) {
if (!cache.getMint(pubKey)) {
return cache.addMint(new PublicKey(pubKey), obj);
}
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
return array.map((obj, index) => {
const pubKey = keys[index];
if (obj.data.length === AccountLayout.span) {
return cache.add(pubKey, obj, TokenAccountParser);
} else if (obj.data.length === MintLayout.span) {
if (!cache.getMint(pubKey)) {
return cache.addMint(new PublicKey(pubKey), obj);
}
}
return obj;
}) as any[];
}
);
return obj;
}) as any[];
});
return poolsArray;
};
Promise.all([
queryPools(programIds().swap),
...programIds().swap_legacy.map((leg) => queryPools(leg, true)),
]).then((all) => {
setPools(all.flat());
});
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) {

View File

@ -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('/');
}

View File

@ -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>

View File

@ -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>
);

View File

@ -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