Intermediary step where ive gotten everything at least green and moved to common and linked
This commit is contained in:
parent
27c62ce9d3
commit
7e2749b9bc
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
*/node_modules
|
*/node_modules
|
||||||
.yarn/
|
.yarn/*
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
.pnp.*
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@
|
||||||
"**/.pnp.*": true
|
"**/.pnp.*": true
|
||||||
},
|
},
|
||||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"prettier.prettierPath": ".vscode/pnpify/prettier/index.js"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
||||||
|
# This file is automatically generated by PnPify.
|
||||||
|
# Manual changes will be lost!
|
||||||
|
|
||||||
|
integrations:
|
||||||
|
- vscode
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire, createRequireFromPath} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.js";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsc
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsc your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsc`);
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire, createRequireFromPath} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.js";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsserver
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsserver your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsserver`);
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire, createRequireFromPath} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.js";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsc.js your application uses
|
||||||
|
module.exports = absRequire(`typescript/lib/tsc.js`);
|
|
@ -0,0 +1,111 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire, createRequireFromPath} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.js";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||||
|
|
||||||
|
const moduleWrapper = tsserver => {
|
||||||
|
const {isAbsolute} = require(`path`);
|
||||||
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
|
return `${locator.name}@${locator.reference}`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
|
// before forwarding it to TS, and to add it back on all returned paths.
|
||||||
|
|
||||||
|
function toEditorPath(str) {
|
||||||
|
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||||
|
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
|
||||||
|
// We also take the opportunity to turn virtual paths into physical ones;
|
||||||
|
// this makes is much easier to work with workspaces that list peer
|
||||||
|
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||||
|
// file instances instead of the real ones.
|
||||||
|
//
|
||||||
|
// We only do this to modules owned by the the dependency tree roots.
|
||||||
|
// This avoids breaking the resolution when jumping inside a vendor
|
||||||
|
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||||
|
// errors on react).
|
||||||
|
//
|
||||||
|
const resolved = pnpApi.resolveVirtual(str);
|
||||||
|
if (resolved) {
|
||||||
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
|
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||||
|
str = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(/\\/g, `/`)
|
||||||
|
str = str.replace(/^\/?/, `/`);
|
||||||
|
|
||||||
|
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||||
|
// VSCode only adds it automatically for supported schemes,
|
||||||
|
// so we have to do it manually for the `zip` scheme.
|
||||||
|
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||||
|
//
|
||||||
|
if (str.match(/\.zip\//)) {
|
||||||
|
str = `${isVSCode ? `^` : ``}zip:${str}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEditorPath(str) {
|
||||||
|
return process.platform === `win32`
|
||||||
|
? str.replace(/^\^?zip:\//, ``)
|
||||||
|
: str.replace(/^\^?zip:/, ``);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And here is the point where we hijack the VSCode <-> TS communications
|
||||||
|
// by adding ourselves in the middle. We locate everything that looks
|
||||||
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
|
const Session = tsserver.server.Session;
|
||||||
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
|
let isVSCode = false;
|
||||||
|
|
||||||
|
return Object.assign(Session.prototype, {
|
||||||
|
onMessage(/** @type {string} */ message) {
|
||||||
|
const parsedMessage = JSON.parse(message)
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedMessage != null &&
|
||||||
|
typeof parsedMessage === `object` &&
|
||||||
|
parsedMessage.arguments &&
|
||||||
|
parsedMessage.arguments.hostInfo === `vscode`
|
||||||
|
) {
|
||||||
|
isVSCode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||||
|
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
send(/** @type {any} */ msg) {
|
||||||
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||||
|
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire, createRequireFromPath} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.js";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/typescript.js your application uses
|
||||||
|
module.exports = absRequire(`typescript/lib/typescript.js`);
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "typescript",
|
||||||
|
"version": "4.1.3-pnpify",
|
||||||
|
"main": "./lib/typescript.js",
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
|
@ -32,8 +32,7 @@
|
||||||
"react-github-btn": "^1.2.0",
|
"react-github-btn": "^1.2.0",
|
||||||
"react-intl": "^5.10.2",
|
"react-intl": "^5.10.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "3.4.3",
|
"react-scripts": "3.4.3"
|
||||||
"typescript": "^4.0.0"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
|
import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
|
||||||
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from '../utils/ids';
|
import { TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from '../utils/ids';
|
||||||
import { LendingObligationLayout, TokenAccount } from '../models';
|
import { TokenAccount } from '../models/account';
|
||||||
import { cache, TokenAccountParser } from './../contexts/accounts';
|
import { cache, TokenAccountParser } from '../contexts/accounts';
|
||||||
|
|
||||||
export function ensureSplAccount(
|
export function ensureSplAccount(
|
||||||
instructions: TransactionInstruction[],
|
instructions: TransactionInstruction[],
|
||||||
|
@ -51,28 +51,6 @@ export function createTempMemoryAccount(
|
||||||
return account.publicKey;
|
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(
|
export function createUninitializedMint(
|
||||||
instructions: TransactionInstruction[],
|
instructions: TransactionInstruction[],
|
||||||
payer: PublicKey,
|
payer: PublicKey,
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './account';
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Typography } from "antd";
|
import { Typography } from 'antd';
|
||||||
import { shortenAddress } from "../../utils/utils";
|
import { shortenAddress } from '../../utils/utils';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
|
||||||
export const ExplorerLink = (props: {
|
export const ExplorerLink = (props: {
|
||||||
address: string | PublicKey;
|
address: string | PublicKey;
|
||||||
|
@ -12,10 +12,7 @@ export const ExplorerLink = (props: {
|
||||||
}) => {
|
}) => {
|
||||||
const { type, code } = props;
|
const { type, code } = props;
|
||||||
|
|
||||||
const address =
|
const address = typeof props.address === 'string' ? props.address : props.address?.toBase58();
|
||||||
typeof props.address === "string"
|
|
||||||
? props.address
|
|
||||||
: props.address?.toBase58();
|
|
||||||
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -27,7 +24,7 @@ export const ExplorerLink = (props: {
|
||||||
<a
|
<a
|
||||||
href={`https://explorer.solana.com/${type}/${address}`}
|
href={`https://explorer.solana.com/${type}/${address}`}
|
||||||
// eslint-disable-next-line react/jsx-no-target-blank
|
// eslint-disable-next-line react/jsx-no-target-blank
|
||||||
target="_blank"
|
target='_blank'
|
||||||
title={address}
|
title={address}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
>
|
>
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './math';
|
|
@ -1,23 +1,13 @@
|
||||||
import React, {
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
useCallback,
|
import { useConnection } from '../contexts/connection';
|
||||||
useContext,
|
import { useWallet } from '../contexts/wallet';
|
||||||
useEffect,
|
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||||
useMemo,
|
import { AccountLayout, u64, MintInfo, MintLayout } from '@solana/spl-token';
|
||||||
useState,
|
import { TokenAccount } from '../models';
|
||||||
} from "react";
|
import { chunks } from '../utils/utils';
|
||||||
import { useConnection } from "./connection";
|
import { EventEmitter } from '../utils/eventEmitter';
|
||||||
import { useWallet } from "./wallet";
|
import { useUserAccounts } from '../hooks/useUserAccounts';
|
||||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
import { WRAPPED_SOL_MINT, programIds, LEND_HOST_FEE_ADDRESS } from 'common/src/utils/ids';
|
||||||
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";
|
|
||||||
|
|
||||||
const AccountsContext = React.createContext<any>(null);
|
const AccountsContext = React.createContext<any>(null);
|
||||||
|
|
||||||
|
@ -32,10 +22,7 @@ export interface ParsedAccountBase {
|
||||||
info: any; // TODO: change to unkown
|
info: any; // TODO: change to unkown
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountParser = (
|
export type AccountParser = (pubkey: PublicKey, data: AccountInfo<Buffer>) => ParsedAccountBase | undefined;
|
||||||
pubkey: PublicKey,
|
|
||||||
data: AccountInfo<Buffer>
|
|
||||||
) => ParsedAccountBase | undefined;
|
|
||||||
|
|
||||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||||
info: T;
|
info: T;
|
||||||
|
@ -44,7 +31,7 @@ export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||||
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
||||||
const info = await connection.getAccountInfo(pubKey);
|
const info = await connection.getAccountInfo(pubKey);
|
||||||
if (info === null) {
|
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);
|
const data = Buffer.from(info.data);
|
||||||
|
@ -68,10 +55,7 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
return details;
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TokenAccountParser = (
|
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
pubKey: PublicKey,
|
|
||||||
info: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
const data = deserializeAccount(buffer);
|
const data = deserializeAccount(buffer);
|
||||||
|
|
||||||
|
@ -86,10 +70,7 @@ export const TokenAccountParser = (
|
||||||
return details;
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GenericAccountParser = (
|
export const GenericAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
pubKey: PublicKey,
|
|
||||||
info: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
|
|
||||||
const details = {
|
const details = {
|
||||||
|
@ -107,13 +88,9 @@ export const keyToAccountParser = new Map<string, AccountParser>();
|
||||||
|
|
||||||
export const cache = {
|
export const cache = {
|
||||||
emitter: new EventEmitter(),
|
emitter: new EventEmitter(),
|
||||||
query: async (
|
query: async (connection: Connection, pubKey: string | PublicKey, parser?: AccountParser) => {
|
||||||
connection: Connection,
|
|
||||||
pubKey: string | PublicKey,
|
|
||||||
parser?: AccountParser
|
|
||||||
) => {
|
|
||||||
let id: PublicKey;
|
let id: PublicKey;
|
||||||
if (typeof pubKey === "string") {
|
if (typeof pubKey === 'string') {
|
||||||
id = new PublicKey(pubKey);
|
id = new PublicKey(pubKey);
|
||||||
} else {
|
} else {
|
||||||
id = pubKey;
|
id = pubKey;
|
||||||
|
@ -134,7 +111,7 @@ export const cache = {
|
||||||
// TODO: refactor to use multiple accounts query with flush like behavior
|
// TODO: refactor to use multiple accounts query with flush like behavior
|
||||||
query = connection.getAccountInfo(id).then((data) => {
|
query = connection.getAccountInfo(id).then((data) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error("Account not found");
|
throw new Error('Account not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.add(id, data, parser);
|
return cache.add(id, data, parser);
|
||||||
|
@ -143,21 +120,15 @@ export const cache = {
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
},
|
},
|
||||||
add: (
|
add: (id: PublicKey | string, obj: AccountInfo<Buffer>, parser?: AccountParser) => {
|
||||||
id: PublicKey | string,
|
|
||||||
obj: AccountInfo<Buffer>,
|
|
||||||
parser?: AccountParser
|
|
||||||
) => {
|
|
||||||
if (obj.data.length === 0) {
|
if (obj.data.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = typeof id === "string" ? id : id?.toBase58();
|
const address = typeof id === 'string' ? id : id?.toBase58();
|
||||||
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
||||||
if (!deserialize) {
|
if (!deserialize) {
|
||||||
throw new Error(
|
throw new Error('Deserializer needs to be registered or passed as a parameter');
|
||||||
"Deserializer needs to be registered or passed as a parameter"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.registerParser(id, deserialize);
|
cache.registerParser(id, deserialize);
|
||||||
|
@ -175,7 +146,7 @@ export const cache = {
|
||||||
},
|
},
|
||||||
get: (pubKey: string | PublicKey) => {
|
get: (pubKey: string | PublicKey) => {
|
||||||
let key: string;
|
let key: string;
|
||||||
if (typeof pubKey !== "string") {
|
if (typeof pubKey !== 'string') {
|
||||||
key = pubKey.toBase58();
|
key = pubKey.toBase58();
|
||||||
} else {
|
} else {
|
||||||
key = pubKey;
|
key = pubKey;
|
||||||
|
@ -185,7 +156,7 @@ export const cache = {
|
||||||
},
|
},
|
||||||
delete: (pubKey: string | PublicKey) => {
|
delete: (pubKey: string | PublicKey) => {
|
||||||
let key: string;
|
let key: string;
|
||||||
if (typeof pubKey !== "string") {
|
if (typeof pubKey !== 'string') {
|
||||||
key = pubKey.toBase58();
|
key = pubKey.toBase58();
|
||||||
} else {
|
} else {
|
||||||
key = pubKey;
|
key = pubKey;
|
||||||
|
@ -211,7 +182,7 @@ export const cache = {
|
||||||
},
|
},
|
||||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const address = typeof pubkey === "string" ? pubkey : pubkey?.toBase58();
|
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||||
keyToAccountParser.set(address, parser);
|
keyToAccountParser.set(address, parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +190,7 @@ export const cache = {
|
||||||
},
|
},
|
||||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
||||||
let id: PublicKey;
|
let id: PublicKey;
|
||||||
if (typeof pubKey === "string") {
|
if (typeof pubKey === 'string') {
|
||||||
id = new PublicKey(pubKey);
|
id = new PublicKey(pubKey);
|
||||||
} else {
|
} else {
|
||||||
id = pubKey;
|
id = pubKey;
|
||||||
|
@ -248,7 +219,7 @@ export const cache = {
|
||||||
},
|
},
|
||||||
getMint: (pubKey: string | PublicKey) => {
|
getMint: (pubKey: string | PublicKey) => {
|
||||||
let key: string;
|
let key: string;
|
||||||
if (typeof pubKey !== "string") {
|
if (typeof pubKey !== 'string') {
|
||||||
key = pubKey.toBase58();
|
key = pubKey.toBase58();
|
||||||
} else {
|
} else {
|
||||||
key = pubKey;
|
key = pubKey;
|
||||||
|
@ -270,10 +241,7 @@ export const useAccountsContext = () => {
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
function wrapNativeAccount(
|
function wrapNativeAccount(pubkey: PublicKey, account?: AccountInfo<Buffer>): TokenAccount | undefined {
|
||||||
pubkey: PublicKey,
|
|
||||||
account?: AccountInfo<Buffer>
|
|
||||||
): TokenAccount | undefined {
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -296,22 +264,7 @@ function wrapNativeAccount(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCachedPool(legacy = false) {
|
export const getCachedAccount = (predicate: (account: TokenAccount) => boolean) => {
|
||||||
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
|
|
||||||
) => {
|
|
||||||
for (const account of genericCache.values()) {
|
for (const account of genericCache.values()) {
|
||||||
if (predicate(account)) {
|
if (predicate(account)) {
|
||||||
return account as TokenAccount;
|
return account as TokenAccount;
|
||||||
|
@ -361,10 +314,7 @@ const UseNativeAccount = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const PRECACHED_OWNERS = new Set<string>();
|
const PRECACHED_OWNERS = new Set<string>();
|
||||||
const precacheUserTokenAccounts = async (
|
const precacheUserTokenAccounts = async (connection: Connection, owner?: PublicKey) => {
|
||||||
connection: Connection,
|
|
||||||
owner?: PublicKey
|
|
||||||
) => {
|
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -392,16 +342,12 @@ export function AccountsProvider({ children = null as any }) {
|
||||||
return cache
|
return cache
|
||||||
.byParser(TokenAccountParser)
|
.byParser(TokenAccountParser)
|
||||||
.map((id) => cache.get(id))
|
.map((id) => cache.get(id))
|
||||||
.filter(
|
.filter((a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58())
|
||||||
(a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58()
|
|
||||||
)
|
|
||||||
.map((a) => a as TokenAccount);
|
.map((a) => a as TokenAccount);
|
||||||
}, [wallet]);
|
}, [wallet]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const accounts = selectUserAccounts().filter(
|
const accounts = selectUserAccounts().filter((a) => a !== undefined) as TokenAccount[];
|
||||||
(a) => a !== undefined
|
|
||||||
) as TokenAccount[];
|
|
||||||
setUserAccounts(accounts);
|
setUserAccounts(accounts);
|
||||||
}, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
|
}, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
|
||||||
|
|
||||||
|
@ -451,7 +397,7 @@ export function AccountsProvider({ children = null as any }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"singleGossip"
|
'singleGossip'
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -479,15 +425,9 @@ export function useNativeAccount() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMultipleAccounts = async (
|
export const getMultipleAccounts = async (connection: any, keys: string[], commitment: string) => {
|
||||||
connection: any,
|
|
||||||
keys: string[],
|
|
||||||
commitment: string
|
|
||||||
) => {
|
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
chunks(keys, 99).map((chunk) =>
|
chunks(keys, 99).map((chunk) => getMultipleAccountsCore(connection, chunk, commitment))
|
||||||
getMultipleAccountsCore(connection, chunk, commitment)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const array = result
|
const array = result
|
||||||
|
@ -502,7 +442,7 @@ export const getMultipleAccounts = async (
|
||||||
const { data, ...rest } = acc;
|
const { data, ...rest } = acc;
|
||||||
const obj = {
|
const obj = {
|
||||||
...rest,
|
...rest,
|
||||||
data: Buffer.from(data[0], "base64"),
|
data: Buffer.from(data[0], 'base64'),
|
||||||
} as AccountInfo<Buffer>;
|
} as AccountInfo<Buffer>;
|
||||||
return obj;
|
return obj;
|
||||||
})
|
})
|
||||||
|
@ -512,18 +452,12 @@ export const getMultipleAccounts = async (
|
||||||
return { keys, array };
|
return { keys, array };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMultipleAccountsCore = async (
|
const getMultipleAccountsCore = async (connection: any, keys: string[], commitment: string) => {
|
||||||
connection: any,
|
const args = connection._buildArgs([keys], commitment, 'base64');
|
||||||
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) {
|
if (unsafeRes.error) {
|
||||||
throw new Error(
|
throw new Error('failed to get info about account ' + unsafeRes.error.message);
|
||||||
"failed to get info about account " + unsafeRes.error.message
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unsafeRes.result.value) {
|
if (unsafeRes.result.value) {
|
||||||
|
@ -539,7 +473,7 @@ export function useMint(key?: string | PublicKey) {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const [mint, setMint] = useState<MintInfo>();
|
const [mint, setMint] = useState<MintInfo>();
|
||||||
|
|
||||||
const id = typeof key === "string" ? key : key?.toBase58();
|
const id = typeof key === 'string' ? key : key?.toBase58();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
@ -554,9 +488,7 @@ export function useMint(key?: string | PublicKey) {
|
||||||
const dispose = cache.emitter.onCache((e) => {
|
const dispose = cache.emitter.onCache((e) => {
|
||||||
const event = e;
|
const event = e;
|
||||||
if (event.id === id) {
|
if (event.id === id) {
|
||||||
cache
|
cache.query(connection, id, MintParser).then((mint) => setMint(mint.info as any));
|
||||||
.query(connection, id, MintParser)
|
|
||||||
.then((mint) => setMint(mint.info as any));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -569,9 +501,7 @@ export function useMint(key?: string | PublicKey) {
|
||||||
|
|
||||||
export const useAccountByMint = (mint: string) => {
|
export const useAccountByMint = (mint: string) => {
|
||||||
const { userAccounts } = useUserAccounts();
|
const { userAccounts } = useUserAccounts();
|
||||||
const index = userAccounts.findIndex(
|
const index = userAccounts.findIndex((acc) => acc.info.mint.toBase58() === mint);
|
||||||
(acc) => acc.info.mint.toBase58() === mint
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
return userAccounts[index];
|
return userAccounts[index];
|
||||||
|
@ -592,9 +522,7 @@ export function useAccount(pubKey?: PublicKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const acc = await cache
|
const acc = await cache.query(connection, key, TokenAccountParser).catch((err) => console.log(err));
|
||||||
.query(connection, key, TokenAccountParser)
|
|
||||||
.catch((err) => console.log(err));
|
|
||||||
if (acc) {
|
if (acc) {
|
||||||
setAccount(acc);
|
setAccount(acc);
|
||||||
}
|
}
|
||||||
|
@ -657,7 +585,7 @@ const deserializeAccount = (data: Buffer) => {
|
||||||
// TODO: expose in spl package
|
// TODO: expose in spl package
|
||||||
const deserializeMint = (data: Buffer) => {
|
const deserializeMint = (data: Buffer) => {
|
||||||
if (data.length !== MintLayout.span) {
|
if (data.length !== MintLayout.span) {
|
||||||
throw new Error("Not a valid Mint");
|
throw new Error('Not a valid Mint');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mintInfo = MintLayout.decode(data);
|
const mintInfo = MintLayout.decode(data);
|
|
@ -1,40 +1,29 @@
|
||||||
import { KnownToken, useLocalStorageState } from "./../utils/utils";
|
import { KnownToken, useLocalStorageState } from '../utils/utils';
|
||||||
import {
|
import { Account, clusterApiUrl, Connection, Transaction, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
clusterApiUrl,
|
import { notify } from '../utils/notifications';
|
||||||
Connection,
|
import { ExplorerLink } from '../components/ExplorerLink';
|
||||||
Transaction,
|
import LocalTokens from '../config/tokens.json';
|
||||||
TransactionInstruction,
|
import { setProgramIds } from 'common/src/utils/ids';
|
||||||
} 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";
|
|
||||||
|
|
||||||
export type ENV =
|
export type ENV = 'mainnet-beta' | 'testnet' | 'devnet' | 'localnet' | 'lending';
|
||||||
| "mainnet-beta"
|
|
||||||
| "testnet"
|
|
||||||
| "devnet"
|
|
||||||
| "localnet"
|
|
||||||
| "lending";
|
|
||||||
|
|
||||||
export const ENDPOINTS = [
|
export const ENDPOINTS = [
|
||||||
{
|
{
|
||||||
name: "mainnet-beta" as ENV,
|
name: 'mainnet-beta' as ENV,
|
||||||
endpoint: "https://solana-api.projectserum.com/",
|
endpoint: 'https://solana-api.projectserum.com/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Oyster Dev" as ENV,
|
name: 'Oyster Dev' as ENV,
|
||||||
endpoint: "http://oyster-dev.solana.com/",
|
endpoint: 'http://oyster-dev.solana.com/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Lending" as ENV,
|
name: 'Lending' as ENV,
|
||||||
endpoint: "https://tln.solana.com/",
|
endpoint: 'https://tln.solana.com/',
|
||||||
},
|
},
|
||||||
{ name: "testnet" as ENV, endpoint: clusterApiUrl("testnet") },
|
{ name: 'testnet' as ENV, endpoint: clusterApiUrl('testnet') },
|
||||||
{ name: "devnet" as ENV, endpoint: clusterApiUrl("devnet") },
|
{ name: 'devnet' as ENV, endpoint: clusterApiUrl('devnet') },
|
||||||
{ name: "localnet" as ENV, endpoint: "http://127.0.0.1:8899" },
|
{ name: 'localnet' as ENV, endpoint: 'http://127.0.0.1:8899' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT = ENDPOINTS[0].endpoint;
|
const DEFAULT = ENDPOINTS[0].endpoint;
|
||||||
|
@ -57,43 +46,29 @@ const ConnectionContext = React.createContext<ConnectionConfig>({
|
||||||
setEndpoint: () => {},
|
setEndpoint: () => {},
|
||||||
slippage: DEFAULT_SLIPPAGE,
|
slippage: DEFAULT_SLIPPAGE,
|
||||||
setSlippage: (val: number) => {},
|
setSlippage: (val: number) => {},
|
||||||
connection: new Connection(DEFAULT, "recent"),
|
connection: new Connection(DEFAULT, 'recent'),
|
||||||
sendConnection: new Connection(DEFAULT, "recent"),
|
sendConnection: new Connection(DEFAULT, 'recent'),
|
||||||
env: ENDPOINTS[0].name,
|
env: ENDPOINTS[0].name,
|
||||||
tokens: [],
|
tokens: [],
|
||||||
tokenMap: new Map<string, KnownToken>(),
|
tokenMap: new Map<string, KnownToken>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConnectionProvider({ children = undefined as any }) {
|
export function ConnectionProvider({ children = undefined as any }) {
|
||||||
const [endpoint, setEndpoint] = useLocalStorageState(
|
const [endpoint, setEndpoint] = useLocalStorageState('connectionEndpts', ENDPOINTS[0].endpoint);
|
||||||
"connectionEndpts",
|
|
||||||
ENDPOINTS[0].endpoint
|
|
||||||
);
|
|
||||||
|
|
||||||
const [slippage, setSlippage] = useLocalStorageState(
|
const [slippage, setSlippage] = useLocalStorageState('slippage', DEFAULT_SLIPPAGE.toString());
|
||||||
"slippage",
|
|
||||||
DEFAULT_SLIPPAGE.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
const connection = useMemo(() => new Connection(endpoint, "recent"), [
|
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||||
endpoint,
|
const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||||
]);
|
|
||||||
const sendConnection = useMemo(() => new Connection(endpoint, "recent"), [
|
|
||||||
endpoint,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const env =
|
const env = ENDPOINTS.find((end) => end.endpoint === endpoint)?.name || ENDPOINTS[0].name;
|
||||||
ENDPOINTS.find((end) => end.endpoint === endpoint)?.name ||
|
|
||||||
ENDPOINTS[0].name;
|
|
||||||
|
|
||||||
const [tokens, setTokens] = useState<KnownToken[]>([]);
|
const [tokens, setTokens] = useState<KnownToken[]>([]);
|
||||||
const [tokenMap, setTokenMap] = useState<Map<string, KnownToken>>(new Map());
|
const [tokenMap, setTokenMap] = useState<Map<string, KnownToken>>(new Map());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// fetch token files
|
// fetch token files
|
||||||
window
|
window
|
||||||
.fetch(
|
.fetch(`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`)
|
||||||
`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
|
@ -129,10 +104,7 @@ export function ConnectionProvider({ children = undefined as any }) {
|
||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = sendConnection.onAccountChange(
|
const id = sendConnection.onAccountChange(new Account().publicKey, () => {});
|
||||||
new Account().publicKey,
|
|
||||||
() => {}
|
|
||||||
);
|
|
||||||
return () => {
|
return () => {
|
||||||
sendConnection.removeAccountChangeListener(id);
|
sendConnection.removeAccountChangeListener(id);
|
||||||
};
|
};
|
||||||
|
@ -190,7 +162,7 @@ export function useSlippageConfig() {
|
||||||
|
|
||||||
const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
||||||
// wait for all confirmation before geting transaction
|
// wait for all confirmation before geting transaction
|
||||||
await connection.confirmTransaction(txid, "max");
|
await connection.confirmTransaction(txid, 'max');
|
||||||
|
|
||||||
const tx = await connection.getParsedConfirmedTransaction(txid);
|
const tx = await connection.getParsedConfirmedTransaction(txid);
|
||||||
|
|
||||||
|
@ -224,9 +196,7 @@ export const sendTransaction = async (
|
||||||
) => {
|
) => {
|
||||||
let transaction = new Transaction();
|
let transaction = new Transaction();
|
||||||
instructions.forEach((instruction) => transaction.add(instruction));
|
instructions.forEach((instruction) => transaction.add(instruction));
|
||||||
transaction.recentBlockhash = (
|
transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash;
|
||||||
await connection.getRecentBlockhash("max")
|
|
||||||
).blockhash;
|
|
||||||
transaction.setSigners(
|
transaction.setSigners(
|
||||||
// fee payied by the wallet owner
|
// fee payied by the wallet owner
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
|
@ -239,37 +209,30 @@ export const sendTransaction = async (
|
||||||
const rawTransaction = transaction.serialize();
|
const rawTransaction = transaction.serialize();
|
||||||
let options = {
|
let options = {
|
||||||
skipPreflight: true,
|
skipPreflight: true,
|
||||||
commitment: "singleGossip",
|
commitment: 'singleGossip',
|
||||||
};
|
};
|
||||||
|
|
||||||
const txid = await connection.sendRawTransaction(rawTransaction, options);
|
const txid = await connection.sendRawTransaction(rawTransaction, options);
|
||||||
|
|
||||||
if (awaitConfirmation) {
|
if (awaitConfirmation) {
|
||||||
const status = (
|
const status = (await connection.confirmTransaction(txid, options && (options.commitment as any))).value;
|
||||||
await connection.confirmTransaction(
|
|
||||||
txid,
|
|
||||||
options && (options.commitment as any)
|
|
||||||
)
|
|
||||||
).value;
|
|
||||||
|
|
||||||
if (status?.err) {
|
if (status?.err) {
|
||||||
const errors = await getErrorForTransaction(connection, txid);
|
const errors = await getErrorForTransaction(connection, txid);
|
||||||
notify({
|
notify({
|
||||||
message: "Transaction failed...",
|
message: 'Transaction failed...',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
{errors.map((err) => (
|
{errors.map((err) => (
|
||||||
<div>{err}</div>
|
<div>{err}</div>
|
||||||
))}
|
))}
|
||||||
<ExplorerLink address={txid} type="transaction" />
|
<ExplorerLink address={txid} type='transaction' />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(`Raw transaction ${txid} failed (${JSON.stringify(status)})`);
|
||||||
`Raw transaction ${txid} failed (${JSON.stringify(status)})`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
import Wallet from '@project-serum/sol-wallet-adapter';
|
||||||
import { notify } from "./../utils/notifications";
|
import { notify } from '../utils/notifications';
|
||||||
import { useConnectionConfig } from "./connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { useLocalStorageState } from "./../utils/utils";
|
import { useLocalStorageState } from '../utils/utils';
|
||||||
import { SolongAdapter } from "./../wallet-adapters/solong_adapter";
|
import { SolongAdapter } from '../wallet-adapters/solong_adapter';
|
||||||
|
|
||||||
export const WALLET_PROVIDERS = [
|
export const WALLET_PROVIDERS = [
|
||||||
{ name: "sollet.io", url: "https://www.sollet.io" },
|
{ name: 'sollet.io', url: 'https://www.sollet.io' },
|
||||||
{ name: "solongwallet.com", url: "http://solongwallet.com" },
|
{ name: 'solongwallet.com', url: 'http://solongwallet.com' },
|
||||||
{ name: "solflare.com", url: "https://solflare.com/access-wallet" },
|
{ name: 'solflare.com', url: 'https://solflare.com/access-wallet' },
|
||||||
{ name: "mathwallet.org", url: "https://www.mathwallet.org" },
|
{ name: 'mathwallet.org', url: 'https://www.mathwallet.org' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const WalletContext = React.createContext<any>(null);
|
const WalletContext = React.createContext<any>(null);
|
||||||
|
|
||||||
export function WalletProvider({ children = null as any }) {
|
export function WalletProvider({ children = null as any }) {
|
||||||
const { endpoint } = useConnectionConfig();
|
const { endpoint } = useConnectionConfig();
|
||||||
const [providerUrl, setProviderUrl] = useLocalStorageState(
|
const [providerUrl, setProviderUrl] = useLocalStorageState('walletProvider', 'https://www.sollet.io');
|
||||||
"walletProvider",
|
|
||||||
"https://www.sollet.io"
|
|
||||||
);
|
|
||||||
const wallet = useMemo(() => {
|
const wallet = useMemo(() => {
|
||||||
if (providerUrl === "http://solongwallet.com") {
|
if (providerUrl === 'http://solongwallet.com') {
|
||||||
return new SolongAdapter(providerUrl, endpoint);
|
return new SolongAdapter(providerUrl, endpoint);
|
||||||
} else {
|
} else {
|
||||||
return new Wallet(providerUrl, endpoint);
|
return new Wallet(providerUrl, endpoint);
|
||||||
|
@ -30,7 +27,7 @@ export function WalletProvider({ children = null as any }) {
|
||||||
|
|
||||||
const [connected, setConnected] = useState(false);
|
const [connected, setConnected] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
wallet.on("connect", () => {
|
wallet.on('connect', () => {
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
let walletPublicKey = wallet.publicKey.toBase58();
|
let walletPublicKey = wallet.publicKey.toBase58();
|
||||||
let keyToDisplay =
|
let keyToDisplay =
|
||||||
|
@ -42,15 +39,15 @@ export function WalletProvider({ children = null as any }) {
|
||||||
: walletPublicKey;
|
: walletPublicKey;
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Wallet update",
|
message: 'Wallet update',
|
||||||
description: "Connected to wallet " + keyToDisplay,
|
description: 'Connected to wallet ' + keyToDisplay,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
wallet.on("disconnect", () => {
|
wallet.on('disconnect', () => {
|
||||||
setConnected(false);
|
setConnected(false);
|
||||||
notify({
|
notify({
|
||||||
message: "Wallet update",
|
message: 'Wallet update',
|
||||||
description: "Disconnected from wallet",
|
description: 'Disconnected from wallet',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -65,9 +62,7 @@ export function WalletProvider({ children = null as any }) {
|
||||||
connected,
|
connected,
|
||||||
providerUrl,
|
providerUrl,
|
||||||
setProviderUrl,
|
setProviderUrl,
|
||||||
providerName:
|
providerName: WALLET_PROVIDERS.find(({ url }) => url === providerUrl)?.name ?? providerUrl,
|
||||||
WALLET_PROVIDERS.find(({ url }) => url === providerUrl)?.name ??
|
|
||||||
providerUrl,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './useUserAccounts';
|
||||||
|
export * from './useAccountByMint';
|
||||||
|
export * from './useTokenName';
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { useUserAccounts } from '../hooks/useUserAccounts';
|
||||||
|
|
||||||
|
export const useAccountByMint = (mint?: string | PublicKey) => {
|
||||||
|
const { userAccounts } = useUserAccounts();
|
||||||
|
const mintAddress = typeof mint === 'string' ? mint : mint?.toBase58();
|
||||||
|
|
||||||
|
const index = userAccounts.findIndex((acc) => acc.info.mint.toBase58() === mintAddress);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
return userAccounts[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
|
import { getTokenName } from '../utils/utils';
|
||||||
|
|
||||||
|
export function useTokenName(mintAddress?: string | PublicKey) {
|
||||||
|
const { tokenMap } = useConnectionConfig();
|
||||||
|
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
|
||||||
|
return getTokenName(tokenMap, address);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { TokenAccount } from "../models";
|
import { TokenAccount } from '../models';
|
||||||
import { useAccountsContext } from "./../contexts/accounts";
|
import { useAccountsContext } from 'common/src/contexts/accounts';
|
||||||
|
|
||||||
export function useUserAccounts() {
|
export function useUserAccounts() {
|
||||||
const context = useAccountsContext();
|
const context = useAccountsContext();
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './account';
|
||||||
|
export * from './tokenSwap';
|
|
@ -0,0 +1,68 @@
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import { publicKey, uint64 } from '../utils/layout';
|
||||||
|
|
||||||
|
export { TokenSwap } from '@solana/spl-token-swap';
|
||||||
|
|
||||||
|
const FEE_LAYOUT = BufferLayout.struct(
|
||||||
|
[
|
||||||
|
BufferLayout.nu64('tradeFeeNumerator'),
|
||||||
|
BufferLayout.nu64('tradeFeeDenominator'),
|
||||||
|
BufferLayout.nu64('ownerTradeFeeNumerator'),
|
||||||
|
BufferLayout.nu64('ownerTradeFeeDenominator'),
|
||||||
|
BufferLayout.nu64('ownerWithdrawFeeNumerator'),
|
||||||
|
BufferLayout.nu64('ownerWithdrawFeeDenominator'),
|
||||||
|
BufferLayout.nu64('hostFeeNumerator'),
|
||||||
|
BufferLayout.nu64('hostFeeDenominator'),
|
||||||
|
],
|
||||||
|
'fees'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TokenSwapLayoutLegacyV0 = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('isInitialized'),
|
||||||
|
BufferLayout.u8('nonce'),
|
||||||
|
publicKey('tokenAccountA'),
|
||||||
|
publicKey('tokenAccountB'),
|
||||||
|
publicKey('tokenPool'),
|
||||||
|
uint64('feesNumerator'),
|
||||||
|
uint64('feesDenominator'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const TokenSwapLayoutV1: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('isInitialized'),
|
||||||
|
BufferLayout.u8('nonce'),
|
||||||
|
publicKey('tokenProgramId'),
|
||||||
|
publicKey('tokenAccountA'),
|
||||||
|
publicKey('tokenAccountB'),
|
||||||
|
publicKey('tokenPool'),
|
||||||
|
publicKey('mintA'),
|
||||||
|
publicKey('mintB'),
|
||||||
|
publicKey('feeAccount'),
|
||||||
|
BufferLayout.u8('curveType'),
|
||||||
|
uint64('tradeFeeNumerator'),
|
||||||
|
uint64('tradeFeeDenominator'),
|
||||||
|
uint64('ownerTradeFeeNumerator'),
|
||||||
|
uint64('ownerTradeFeeDenominator'),
|
||||||
|
uint64('ownerWithdrawFeeNumerator'),
|
||||||
|
uint64('ownerWithdrawFeeDenominator'),
|
||||||
|
BufferLayout.blob(16, 'padding'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const CURVE_NODE = BufferLayout.union(BufferLayout.u8(), BufferLayout.blob(32), 'curve');
|
||||||
|
CURVE_NODE.addVariant(0, BufferLayout.struct([]), 'constantProduct');
|
||||||
|
CURVE_NODE.addVariant(1, BufferLayout.struct([BufferLayout.nu64('token_b_price')]), 'constantPrice');
|
||||||
|
CURVE_NODE.addVariant(2, BufferLayout.struct([]), 'stable');
|
||||||
|
CURVE_NODE.addVariant(3, BufferLayout.struct([BufferLayout.nu64('token_b_offset')]), 'offset');
|
||||||
|
|
||||||
|
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('isInitialized'),
|
||||||
|
BufferLayout.u8('nonce'),
|
||||||
|
publicKey('tokenProgramId'),
|
||||||
|
publicKey('tokenAccountA'),
|
||||||
|
publicKey('tokenAccountB'),
|
||||||
|
publicKey('tokenPool'),
|
||||||
|
publicKey('mintA'),
|
||||||
|
publicKey('mintB'),
|
||||||
|
publicKey('feeAccount'),
|
||||||
|
FEE_LAYOUT,
|
||||||
|
CURVE_NODE,
|
||||||
|
]);
|
|
@ -1,16 +1,10 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { TokenSwapLayout, TokenSwapLayoutV1 } from "../models";
|
import { TokenSwapLayout, TokenSwapLayoutV1 } from '../models/tokenSwap';
|
||||||
|
|
||||||
export const WRAPPED_SOL_MINT = new PublicKey(
|
export const WRAPPED_SOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
|
||||||
"So11111111111111111111111111111111111111112"
|
export let TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
||||||
);
|
|
||||||
export let TOKEN_PROGRAM_ID = new PublicKey(
|
|
||||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
||||||
);
|
|
||||||
|
|
||||||
export let LENDING_PROGRAM_ID = new PublicKey(
|
export let LENDING_PROGRAM_ID = new PublicKey('TokenLending1111111111111111111111111111111');
|
||||||
"TokenLending1111111111111111111111111111111"
|
|
||||||
);
|
|
||||||
|
|
||||||
let SWAP_PROGRAM_ID: PublicKey;
|
let SWAP_PROGRAM_ID: PublicKey;
|
||||||
let SWAP_PROGRAM_LEGACY_IDS: 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
|
// legacy pools are used to show users contributions in those pools to allow for withdrawals of funds
|
||||||
export const PROGRAM_IDS = [
|
export const PROGRAM_IDS = [
|
||||||
{
|
{
|
||||||
name: "mainnet-beta",
|
name: 'mainnet-beta',
|
||||||
swap: () => ({
|
swap: () => ({
|
||||||
current: {
|
current: {
|
||||||
pubkey: new PublicKey("9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL"),
|
pubkey: new PublicKey('9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL'),
|
||||||
layout: TokenSwapLayoutV1,
|
layout: TokenSwapLayoutV1,
|
||||||
},
|
},
|
||||||
legacy: [
|
legacy: [
|
||||||
|
@ -40,30 +34,30 @@ export const PROGRAM_IDS = [
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "testnet",
|
name: 'testnet',
|
||||||
swap: () => ({
|
swap: () => ({
|
||||||
current: {
|
current: {
|
||||||
pubkey: new PublicKey("2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg"),
|
pubkey: new PublicKey('2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg'),
|
||||||
layout: TokenSwapLayoutV1,
|
layout: TokenSwapLayoutV1,
|
||||||
},
|
},
|
||||||
legacy: [],
|
legacy: [],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "devnet",
|
name: 'devnet',
|
||||||
swap: () => ({
|
swap: () => ({
|
||||||
current: {
|
current: {
|
||||||
pubkey: new PublicKey("6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj"),
|
pubkey: new PublicKey('6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj'),
|
||||||
layout: TokenSwapLayout,
|
layout: TokenSwapLayout,
|
||||||
},
|
},
|
||||||
legacy: [new PublicKey("BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ")],
|
legacy: [new PublicKey('BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ')],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "localnet",
|
name: 'localnet',
|
||||||
swap: () => ({
|
swap: () => ({
|
||||||
current: {
|
current: {
|
||||||
pubkey: new PublicKey("369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7"),
|
pubkey: new PublicKey('369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7'),
|
||||||
layout: TokenSwapLayoutV1,
|
layout: TokenSwapLayoutV1,
|
||||||
},
|
},
|
||||||
legacy: [],
|
legacy: [],
|
||||||
|
@ -83,10 +77,8 @@ export const setProgramIds = (envName: string) => {
|
||||||
SWAP_PROGRAM_LAYOUT = swap.current.layout;
|
SWAP_PROGRAM_LAYOUT = swap.current.layout;
|
||||||
SWAP_PROGRAM_LEGACY_IDS = swap.legacy;
|
SWAP_PROGRAM_LEGACY_IDS = swap.legacy;
|
||||||
|
|
||||||
if (envName === "mainnet-beta") {
|
if (envName === 'mainnet-beta') {
|
||||||
LENDING_PROGRAM_ID = new PublicKey(
|
LENDING_PROGRAM_ID = new PublicKey('LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi');
|
||||||
"LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import BN from "bn.js";
|
import BN from 'bn.js';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout for a public key
|
* Layout for a public key
|
||||||
*/
|
*/
|
||||||
export const publicKey = (property = "publicKey"): unknown => {
|
export const publicKey = (property = 'publicKey'): unknown => {
|
||||||
const publicKeyLayout = BufferLayout.blob(32, property);
|
const publicKeyLayout = BufferLayout.blob(32, property);
|
||||||
|
|
||||||
const _decode = publicKeyLayout.decode.bind(publicKeyLayout);
|
const _decode = publicKeyLayout.decode.bind(publicKeyLayout);
|
||||||
|
@ -26,7 +26,7 @@ export const publicKey = (property = "publicKey"): unknown => {
|
||||||
/**
|
/**
|
||||||
* Layout for a 64bit unsigned value
|
* 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 layout = BufferLayout.blob(8, property);
|
||||||
|
|
||||||
const _decode = layout.decode.bind(layout);
|
const _decode = layout.decode.bind(layout);
|
||||||
|
@ -38,7 +38,7 @@ export const uint64 = (property = "uint64"): unknown => {
|
||||||
[...data]
|
[...data]
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((i) => `00${i.toString(16)}`.slice(-2))
|
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||||
.join(""),
|
.join(''),
|
||||||
16
|
16
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -58,7 +58,7 @@ export const uint64 = (property = "uint64"): unknown => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: wrap in BN (what about decimals?)
|
// 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 layout = BufferLayout.blob(16, property);
|
||||||
|
|
||||||
const _decode = layout.decode.bind(layout);
|
const _decode = layout.decode.bind(layout);
|
||||||
|
@ -70,7 +70,7 @@ export const uint128 = (property = "uint128"): unknown => {
|
||||||
[...data]
|
[...data]
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((i) => `00${i.toString(16)}`.slice(-2))
|
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||||
.join(""),
|
.join(''),
|
||||||
16
|
16
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -93,12 +93,12 @@ export const uint128 = (property = "uint128"): unknown => {
|
||||||
/**
|
/**
|
||||||
* Layout for a Rust String type
|
* Layout for a Rust String type
|
||||||
*/
|
*/
|
||||||
export const rustString = (property = "string"): unknown => {
|
export const rustString = (property = 'string'): unknown => {
|
||||||
const rsl = BufferLayout.struct(
|
const rsl = BufferLayout.struct(
|
||||||
[
|
[
|
||||||
BufferLayout.u32("length"),
|
BufferLayout.u32('length'),
|
||||||
BufferLayout.u32("lengthPadding"),
|
BufferLayout.u32('lengthPadding'),
|
||||||
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), "chars"),
|
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
|
||||||
],
|
],
|
||||||
property
|
property
|
||||||
);
|
);
|
||||||
|
@ -107,12 +107,12 @@ export const rustString = (property = "string"): unknown => {
|
||||||
|
|
||||||
rsl.decode = (buffer: Buffer, offset: number) => {
|
rsl.decode = (buffer: Buffer, offset: number) => {
|
||||||
const data = _decode(buffer, offset);
|
const data = _decode(buffer, offset);
|
||||||
return data.chars.toString("utf8");
|
return data.chars.toString('utf8');
|
||||||
};
|
};
|
||||||
|
|
||||||
rsl.encode = (str: string, buffer: Buffer, offset: number) => {
|
rsl.encode = (str: string, buffer: Buffer, offset: number) => {
|
||||||
const data = {
|
const data = {
|
||||||
chars: Buffer.from(str, "utf8"),
|
chars: Buffer.from(str, 'utf8'),
|
||||||
};
|
};
|
||||||
return _encode(data, buffer, offset);
|
return _encode(data, buffer, offset);
|
||||||
};
|
};
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { MintInfo } from '@solana/spl-token';
|
||||||
|
|
||||||
|
import { TokenAccount } from './../models';
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { WAD, ZERO } from '../constants';
|
||||||
|
|
||||||
|
export interface KnownToken {
|
||||||
|
tokenSymbol: string;
|
||||||
|
tokenName: string;
|
||||||
|
icon: string;
|
||||||
|
mintAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KnownTokenMap = Map<string, KnownToken>;
|
||||||
|
|
||||||
|
export const formatPriceNumber = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useLocalStorageState(key: string, defaultState?: string) {
|
||||||
|
const [state, setState] = useState(() => {
|
||||||
|
// NOTE: Not sure if this is ok
|
||||||
|
const storedState = localStorage.getItem(key);
|
||||||
|
if (storedState) {
|
||||||
|
return JSON.parse(storedState);
|
||||||
|
}
|
||||||
|
return defaultState;
|
||||||
|
});
|
||||||
|
|
||||||
|
const setLocalStorageState = useCallback(
|
||||||
|
(newState) => {
|
||||||
|
const changed = state !== newState;
|
||||||
|
if (!changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(newState);
|
||||||
|
if (newState === null) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(key, JSON.stringify(newState));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[state, key]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [state, setLocalStorageState];
|
||||||
|
}
|
||||||
|
|
||||||
|
// shorten the checksummed version of the input address to have 4 characters at start and end
|
||||||
|
export function shortenAddress(address: string, chars = 4): string {
|
||||||
|
return `${address.slice(0, chars)}...${address.slice(-chars)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenName(map: KnownTokenMap, mint?: string | PublicKey, shorten = true): string {
|
||||||
|
const mintAddress = typeof mint === 'string' ? mint : mint?.toBase58();
|
||||||
|
|
||||||
|
if (!mintAddress) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
const knownSymbol = map.get(mintAddress)?.tokenSymbol;
|
||||||
|
if (knownSymbol) {
|
||||||
|
return knownSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shorten ? `${mintAddress.substring(0, 5)}...` : mintAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenByName(tokenMap: KnownTokenMap, name: string) {
|
||||||
|
let token: KnownToken | null = null;
|
||||||
|
for (const val of tokenMap.values()) {
|
||||||
|
if (val.tokenSymbol === name) {
|
||||||
|
token = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenIcon(map: KnownTokenMap, mintAddress?: string | PublicKey): string | undefined {
|
||||||
|
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
|
||||||
|
if (!address) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.get(address)?.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isKnownMint(map: KnownTokenMap, mintAddress: string) {
|
||||||
|
return !!map.get(mintAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STABLE_COINS = new Set(['USDC', 'wUSDC', 'USDT']);
|
||||||
|
|
||||||
|
export function chunks<T>(array: T[], size: number): T[][] {
|
||||||
|
return Array.apply<number, T[], T[][]>(0, new Array(Math.ceil(array.length / size))).map((_, index) =>
|
||||||
|
array.slice(index * size, (index + 1) * size)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toLamports(account?: TokenAccount | number, mint?: MintInfo): number {
|
||||||
|
if (!account) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
|
||||||
|
|
||||||
|
const precision = Math.pow(10, mint?.decimals || 0);
|
||||||
|
return Math.floor(amount * precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wadToLamports(amount?: BN): BN {
|
||||||
|
return amount?.div(WAD) || ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromLamports(account?: TokenAccount | number | BN, mint?: MintInfo, rate: number = 1.0): number {
|
||||||
|
if (!account) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = Math.floor(
|
||||||
|
typeof account === 'number' ? account : BN.isBN(account) ? account.toNumber() : account.info.amount.toNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
const precision = Math.pow(10, mint?.decimals || 0);
|
||||||
|
return (amount / precision) * rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
var SI_SYMBOL = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
|
||||||
|
|
||||||
|
const abbreviateNumber = (number: number, precision: number) => {
|
||||||
|
let tier = (Math.log10(number) / 3) | 0;
|
||||||
|
let scaled = number;
|
||||||
|
let suffix = SI_SYMBOL[tier];
|
||||||
|
if (tier !== 0) {
|
||||||
|
let scale = Math.pow(10, tier * 3);
|
||||||
|
scaled = number / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scaled.toFixed(precision) + suffix;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatAmount = (val: number, precision: number = 6, abbr: boolean = true) =>
|
||||||
|
abbr ? abbreviateNumber(val, precision) : val.toFixed(precision);
|
||||||
|
|
||||||
|
export function formatTokenAmount(
|
||||||
|
account?: TokenAccount,
|
||||||
|
mint?: MintInfo,
|
||||||
|
rate: number = 1.0,
|
||||||
|
prefix = '',
|
||||||
|
suffix = '',
|
||||||
|
precision = 6,
|
||||||
|
abbr = false
|
||||||
|
): string {
|
||||||
|
if (!account) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${[prefix]}${formatAmount(fromLamports(account, mint, rate), precision, abbr)}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatUSD = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'USD',
|
||||||
|
});
|
||||||
|
|
||||||
|
const numberFormater = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const formatNumber = {
|
||||||
|
format: (val?: number) => {
|
||||||
|
if (!val) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return numberFormater.format(val);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatPct = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'percent',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function convert(account?: TokenAccount | number, mint?: MintInfo, rate: number = 1.0): number {
|
||||||
|
if (!account) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
|
||||||
|
|
||||||
|
const precision = Math.pow(10, mint?.decimals || 0);
|
||||||
|
let result = (amount / precision) * rate;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
Binary file not shown.
|
@ -2,6 +2,7 @@
|
||||||
"name": "lending",
|
"name": "lending",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.4.0",
|
||||||
"@ant-design/pro-layout": "^6.7.0",
|
"@ant-design/pro-layout": "^6.7.0",
|
||||||
"@craco/craco": "^5.7.0",
|
"@craco/craco": "^5.7.0",
|
||||||
"@project-serum/serum": "^0.13.11",
|
"@project-serum/serum": "^0.13.11",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"buffer-layout": "^1.2.0",
|
"buffer-layout": "^1.2.0",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
|
"common": "workspace:common",
|
||||||
"craco-less": "^1.17.0",
|
"craco-less": "^1.17.0",
|
||||||
"echarts": "^4.9.0",
|
"echarts": "^4.9.0",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
|
|
|
@ -1,37 +1,28 @@
|
||||||
import {
|
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import { sendTransaction } from 'common/src/contexts/connection';
|
||||||
Connection,
|
import { notify } from 'common/src/utils/notifications';
|
||||||
PublicKey,
|
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||||
TransactionInstruction,
|
import { AccountLayout, MintInfo, MintLayout } from '@solana/spl-token';
|
||||||
} from "@solana/web3.js";
|
import { LENDING_PROGRAM_ID, LEND_HOST_FEE_ADDRESS } from 'common/src/utils/ids';
|
||||||
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 {
|
import {
|
||||||
createTempMemoryAccount,
|
createTempMemoryAccount,
|
||||||
createUninitializedAccount,
|
createUninitializedAccount,
|
||||||
createUninitializedMint,
|
createUninitializedMint,
|
||||||
createUninitializedObligation,
|
|
||||||
ensureSplAccount,
|
ensureSplAccount,
|
||||||
findOrCreateAccountByMint,
|
findOrCreateAccountByMint,
|
||||||
} from "./account";
|
} from 'common/src/actions/account';
|
||||||
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
import { createUninitializedObligation } from './obligation';
|
||||||
|
import { cache, MintParser, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
import {
|
import {
|
||||||
TokenAccount,
|
|
||||||
LendingObligationLayout,
|
LendingObligationLayout,
|
||||||
borrowInstruction,
|
borrowInstruction,
|
||||||
LendingMarket,
|
LendingMarket,
|
||||||
BorrowAmountType,
|
BorrowAmountType,
|
||||||
LendingObligation,
|
LendingObligation,
|
||||||
approve,
|
|
||||||
initObligationInstruction,
|
initObligationInstruction,
|
||||||
} from "../models";
|
} from '../models';
|
||||||
import { toLamports } from "../utils/utils";
|
import { TokenAccount, approve } from 'common/src/models';
|
||||||
|
import { toLamports } from 'common/src/utils/utils';
|
||||||
|
|
||||||
export const borrow = async (
|
export const borrow = async (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -50,9 +41,9 @@ export const borrow = async (
|
||||||
obligationAccount?: PublicKey
|
obligationAccount?: PublicKey
|
||||||
) => {
|
) => {
|
||||||
notify({
|
notify({
|
||||||
message: "Borrowing funds...",
|
message: 'Borrowing funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
let signers: Account[] = [];
|
let signers: Account[] = [];
|
||||||
|
@ -65,18 +56,14 @@ export const borrow = async (
|
||||||
LENDING_PROGRAM_ID
|
LENDING_PROGRAM_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
AccountLayout.span
|
|
||||||
);
|
|
||||||
|
|
||||||
const obligation = existingObligation
|
const obligation = existingObligation
|
||||||
? existingObligation.pubkey
|
? existingObligation.pubkey
|
||||||
: createUninitializedObligation(
|
: createUninitializedObligation(
|
||||||
instructions,
|
instructions,
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
await connection.getMinimumBalanceForRentExemption(
|
await connection.getMinimumBalanceForRentExemption(LendingObligationLayout.span),
|
||||||
LendingObligationLayout.span
|
|
||||||
),
|
|
||||||
signers
|
signers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -91,12 +78,7 @@ export const borrow = async (
|
||||||
|
|
||||||
const obligationTokenOutput = obligationAccount
|
const obligationTokenOutput = obligationAccount
|
||||||
? obligationAccount
|
? obligationAccount
|
||||||
: createUninitializedAccount(
|
: createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||||
instructions,
|
|
||||||
wallet.publicKey,
|
|
||||||
accountRentExempt,
|
|
||||||
signers
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!obligationAccount) {
|
if (!obligationAccount) {
|
||||||
instructions.push(
|
instructions.push(
|
||||||
|
@ -173,21 +155,19 @@ export const borrow = async (
|
||||||
|
|
||||||
if (instructions.length > 0) {
|
if (instructions.length > 0) {
|
||||||
// create all accounts in one transaction
|
// create all accounts in one transaction
|
||||||
let tx = await sendTransaction(connection, wallet, instructions, [
|
let tx = await sendTransaction(connection, wallet, instructions, [...signers]);
|
||||||
...signers,
|
|
||||||
]);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Obligation accounts created",
|
message: 'Obligation accounts created',
|
||||||
description: `Transaction ${tx}`,
|
description: `Transaction ${tx}`,
|
||||||
type: "success",
|
type: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Borrowing funds...",
|
message: 'Borrowing funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
signers = [];
|
signers = [];
|
||||||
|
@ -214,25 +194,14 @@ export const borrow = async (
|
||||||
throw new Error(`Dex market doesn't exist.`);
|
throw new Error(`Dex market doesn't exist.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<
|
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
LendingMarket
|
const dexOrderBookSide = market.info.quoteMint.equals(depositReserve.info.liquidityMint)
|
||||||
>;
|
|
||||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
|
||||||
depositReserve.info.liquidityMint
|
|
||||||
)
|
|
||||||
? dexMarket?.info.asks
|
? dexMarket?.info.asks
|
||||||
: dexMarket?.info.bids;
|
: dexMarket?.info.bids;
|
||||||
|
|
||||||
const memory = createTempMemoryAccount(
|
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||||
instructions,
|
|
||||||
wallet.publicKey,
|
|
||||||
signers,
|
|
||||||
LENDING_PROGRAM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey));
|
||||||
accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey)
|
|
||||||
);
|
|
||||||
// borrow
|
// borrow
|
||||||
instructions.push(
|
instructions.push(
|
||||||
borrowInstruction(
|
borrowInstruction(
|
||||||
|
@ -264,17 +233,11 @@ export const borrow = async (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
let tx = await sendTransaction(
|
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
instructions.concat(cleanupInstructions),
|
|
||||||
signers,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Funds borrowed.",
|
message: 'Funds borrowed.',
|
||||||
type: "success",
|
type: 'success',
|
||||||
description: `Transaction - ${tx}`,
|
description: `Transaction - ${tx}`,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
|
|
@ -1,25 +1,16 @@
|
||||||
import {
|
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import { sendTransaction } from 'common/src/contexts/connection';
|
||||||
Connection,
|
import { notify } from 'common/src/utils/notifications';
|
||||||
PublicKey,
|
|
||||||
TransactionInstruction,
|
|
||||||
} from "@solana/web3.js";
|
|
||||||
import { sendTransaction } from "../contexts/connection";
|
|
||||||
import { notify } from "../utils/notifications";
|
|
||||||
import {
|
import {
|
||||||
accrueInterestInstruction,
|
accrueInterestInstruction,
|
||||||
depositInstruction,
|
depositInstruction,
|
||||||
initReserveInstruction,
|
initReserveInstruction,
|
||||||
LendingReserve,
|
LendingReserve,
|
||||||
} from "./../models/lending";
|
} from './../models/lending';
|
||||||
import { AccountLayout } from "@solana/spl-token";
|
import { AccountLayout } from '@solana/spl-token';
|
||||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import {
|
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||||
createUninitializedAccount,
|
import { approve, TokenAccount } from 'common/src/models';
|
||||||
ensureSplAccount,
|
|
||||||
findOrCreateAccountByMint,
|
|
||||||
} from "./account";
|
|
||||||
import { approve, TokenAccount } from "../models";
|
|
||||||
|
|
||||||
export const deposit = async (
|
export const deposit = async (
|
||||||
from: TokenAccount,
|
from: TokenAccount,
|
||||||
|
@ -30,9 +21,9 @@ export const deposit = async (
|
||||||
wallet: any
|
wallet: any
|
||||||
) => {
|
) => {
|
||||||
notify({
|
notify({
|
||||||
message: "Depositing funds...",
|
message: 'Depositing funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isInitalized = true; // TODO: finish reserve init
|
const isInitalized = true; // TODO: finish reserve init
|
||||||
|
@ -42,9 +33,7 @@ export const deposit = async (
|
||||||
const instructions: TransactionInstruction[] = [];
|
const instructions: TransactionInstruction[] = [];
|
||||||
const cleanupInstructions: TransactionInstruction[] = [];
|
const cleanupInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
AccountLayout.span
|
|
||||||
);
|
|
||||||
|
|
||||||
const [authority] = await PublicKey.findProgramAddress(
|
const [authority] = await PublicKey.findProgramAddress(
|
||||||
[reserve.lendingMarket.toBuffer()], // which account should be authority
|
[reserve.lendingMarket.toBuffer()], // which account should be authority
|
||||||
|
@ -61,13 +50,7 @@ export const deposit = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
const transferAuthority = approve(
|
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||||
instructions,
|
|
||||||
cleanupInstructions,
|
|
||||||
fromAccount,
|
|
||||||
wallet.publicKey,
|
|
||||||
amountLamports
|
|
||||||
);
|
|
||||||
|
|
||||||
signers.push(transferAuthority);
|
signers.push(transferAuthority);
|
||||||
|
|
||||||
|
@ -84,12 +67,7 @@ export const deposit = async (
|
||||||
signers
|
signers
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
toAccount = createUninitializedAccount(
|
toAccount = createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||||
instructions,
|
|
||||||
wallet.publicKey,
|
|
||||||
accountRentExempt,
|
|
||||||
signers
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInitalized) {
|
if (isInitalized) {
|
||||||
|
@ -132,17 +110,11 @@ export const deposit = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let tx = await sendTransaction(
|
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
instructions.concat(cleanupInstructions),
|
|
||||||
signers,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Funds deposited.",
|
message: 'Funds deposited.',
|
||||||
type: "success",
|
type: 'success',
|
||||||
description: `Transaction - ${tx}`,
|
description: `Transaction - ${tx}`,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
export { borrow } from "./borrow";
|
export { borrow } from './borrow';
|
||||||
export { deposit } from "./deposit";
|
export { deposit } from './deposit';
|
||||||
export { repay } from "./repay";
|
export { repay } from './repay';
|
||||||
export { withdraw } from "./withdraw";
|
export { withdraw } from './withdraw';
|
||||||
export { liquidate } from "./liquidate";
|
export { liquidate } from './liquidate';
|
||||||
export * from "./account";
|
|
||||||
|
|
|
@ -1,30 +1,14 @@
|
||||||
import {
|
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import { sendTransaction } from 'common/src/contexts/connection';
|
||||||
Connection,
|
import { notify } from 'common/src/utils/notifications';
|
||||||
PublicKey,
|
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||||
TransactionInstruction,
|
import { liquidateInstruction } from './../models/lending/liquidate';
|
||||||
} from "@solana/web3.js";
|
import { AccountLayout } from '@solana/spl-token';
|
||||||
import { sendTransaction } from "../contexts/connection";
|
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import { notify } from "../utils/notifications";
|
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||||
import {
|
import { LendingMarket, LendingObligation } from '../models';
|
||||||
accrueInterestInstruction,
|
import { approve, TokenAccount } from 'common/src/models';
|
||||||
LendingReserve,
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
} 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";
|
|
||||||
|
|
||||||
export const liquidate = async (
|
export const liquidate = async (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -40,9 +24,9 @@ export const liquidate = async (
|
||||||
withdrawReserve: ParsedAccount<LendingReserve>
|
withdrawReserve: ParsedAccount<LendingReserve>
|
||||||
) => {
|
) => {
|
||||||
notify({
|
notify({
|
||||||
message: "Repaying funds...",
|
message: 'Repaying funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
// user from account
|
// user from account
|
||||||
|
@ -50,9 +34,7 @@ export const liquidate = async (
|
||||||
const instructions: TransactionInstruction[] = [];
|
const instructions: TransactionInstruction[] = [];
|
||||||
const cleanupInstructions: TransactionInstruction[] = [];
|
const cleanupInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
AccountLayout.span
|
|
||||||
);
|
|
||||||
|
|
||||||
const [authority] = await PublicKey.findProgramAddress(
|
const [authority] = await PublicKey.findProgramAddress(
|
||||||
[repayReserve.info.lendingMarket.toBuffer()],
|
[repayReserve.info.lendingMarket.toBuffer()],
|
||||||
|
@ -69,13 +51,7 @@ export const liquidate = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
const transferAuthority = approve(
|
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||||
instructions,
|
|
||||||
cleanupInstructions,
|
|
||||||
fromAccount,
|
|
||||||
wallet.publicKey,
|
|
||||||
amountLamports
|
|
||||||
);
|
|
||||||
signers.push(transferAuthority);
|
signers.push(transferAuthority);
|
||||||
|
|
||||||
// get destination account
|
// get destination account
|
||||||
|
@ -98,26 +74,15 @@ export const liquidate = async (
|
||||||
throw new Error(`Dex market doesn't exist.`);
|
throw new Error(`Dex market doesn't exist.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<
|
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
LendingMarket
|
|
||||||
>;
|
|
||||||
|
|
||||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
const dexOrderBookSide = market.info.quoteMint.equals(repayReserve.info.liquidityMint)
|
||||||
repayReserve.info.liquidityMint
|
|
||||||
)
|
|
||||||
? dexMarket?.info.asks
|
? dexMarket?.info.asks
|
||||||
: dexMarket?.info.bids;
|
: dexMarket?.info.bids;
|
||||||
|
|
||||||
const memory = createTempMemoryAccount(
|
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||||
instructions,
|
|
||||||
wallet.publicKey,
|
|
||||||
signers,
|
|
||||||
LENDING_PROGRAM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey));
|
||||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
|
||||||
);
|
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(
|
||||||
liquidateInstruction(
|
liquidateInstruction(
|
||||||
|
@ -138,17 +103,11 @@ export const liquidate = async (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = await sendTransaction(
|
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
instructions.concat(cleanupInstructions),
|
|
||||||
signers,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Funds liquidated.",
|
message: 'Funds liquidated.',
|
||||||
type: "success",
|
type: 'success',
|
||||||
description: `Transaction - ${tx}`,
|
description: `Transaction - ${tx}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
||||||
|
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
|
import { LendingObligationLayout } from '../models';
|
||||||
|
|
||||||
|
export function createUninitializedObligation(
|
||||||
|
instructions: TransactionInstruction[],
|
||||||
|
payer: PublicKey,
|
||||||
|
amount: number,
|
||||||
|
signers: Account[]
|
||||||
|
) {
|
||||||
|
const account = new Account();
|
||||||
|
instructions.push(
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
fromPubkey: payer,
|
||||||
|
newAccountPubkey: account.publicKey,
|
||||||
|
lamports: amount,
|
||||||
|
space: LendingObligationLayout.span,
|
||||||
|
programId: LENDING_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
signers.push(account);
|
||||||
|
|
||||||
|
return account.publicKey;
|
||||||
|
}
|
|
@ -1,21 +1,14 @@
|
||||||
import {
|
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import { sendTransaction } from 'common/src/contexts/connection';
|
||||||
Connection,
|
import { notify } from 'common/src/utils/notifications';
|
||||||
PublicKey,
|
import { accrueInterestInstruction, LendingReserve } from './../models/lending/reserve';
|
||||||
TransactionInstruction,
|
import { repayInstruction } from './../models/lending/repay';
|
||||||
} from "@solana/web3.js";
|
import { AccountLayout, Token, NATIVE_MINT } from '@solana/spl-token';
|
||||||
import { sendTransaction } from "../contexts/connection";
|
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import { notify } from "../utils/notifications";
|
import { createTokenAccount, findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||||
import {
|
import { LendingObligation } from '../models';
|
||||||
accrueInterestInstruction,
|
import { approve, TokenAccount } from 'common/src/models';
|
||||||
LendingReserve,
|
import { ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
} 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";
|
|
||||||
|
|
||||||
export const repay = async (
|
export const repay = async (
|
||||||
from: TokenAccount,
|
from: TokenAccount,
|
||||||
|
@ -34,9 +27,9 @@ export const repay = async (
|
||||||
wallet: any
|
wallet: any
|
||||||
) => {
|
) => {
|
||||||
notify({
|
notify({
|
||||||
message: "Repaying funds...",
|
message: 'Repaying funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
// user from account
|
// user from account
|
||||||
|
@ -44,9 +37,7 @@ export const repay = async (
|
||||||
const instructions: TransactionInstruction[] = [];
|
const instructions: TransactionInstruction[] = [];
|
||||||
const cleanupInstructions: TransactionInstruction[] = [];
|
const cleanupInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
AccountLayout.span
|
|
||||||
);
|
|
||||||
|
|
||||||
const [authority] = await PublicKey.findProgramAddress(
|
const [authority] = await PublicKey.findProgramAddress(
|
||||||
[repayReserve.info.lendingMarket.toBuffer()],
|
[repayReserve.info.lendingMarket.toBuffer()],
|
||||||
|
@ -54,10 +45,7 @@ export const repay = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
let fromAccount = from.pubkey;
|
let fromAccount = from.pubkey;
|
||||||
if (
|
if (wallet.publicKey.equals(fromAccount) && repayReserve.info.liquidityMint.equals(NATIVE_MINT)) {
|
||||||
wallet.publicKey.equals(fromAccount) &&
|
|
||||||
repayReserve.info.liquidityMint.equals(NATIVE_MINT)
|
|
||||||
) {
|
|
||||||
fromAccount = createTokenAccount(
|
fromAccount = createTokenAccount(
|
||||||
instructions,
|
instructions,
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
|
@ -67,24 +55,12 @@ export const repay = async (
|
||||||
signers
|
signers
|
||||||
);
|
);
|
||||||
cleanupInstructions.push(
|
cleanupInstructions.push(
|
||||||
Token.createCloseAccountInstruction(
|
Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, fromAccount, wallet.publicKey, wallet.publicKey, [])
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
fromAccount,
|
|
||||||
wallet.publicKey,
|
|
||||||
wallet.publicKey,
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
const transferAuthority = approve(
|
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, repayAmount);
|
||||||
instructions,
|
|
||||||
cleanupInstructions,
|
|
||||||
fromAccount,
|
|
||||||
wallet.publicKey,
|
|
||||||
repayAmount
|
|
||||||
);
|
|
||||||
signers.push(transferAuthority);
|
signers.push(transferAuthority);
|
||||||
|
|
||||||
// get destination account
|
// get destination account
|
||||||
|
@ -110,9 +86,7 @@ export const repay = async (
|
||||||
transferAuthority.publicKey
|
transferAuthority.publicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey));
|
||||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
|
||||||
);
|
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(
|
||||||
repayInstruction(
|
repayInstruction(
|
||||||
|
@ -132,17 +106,11 @@ export const repay = async (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = await sendTransaction(
|
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
instructions.concat(cleanupInstructions),
|
|
||||||
signers,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Funds repaid.",
|
message: 'Funds repaid.',
|
||||||
type: "success",
|
type: 'success',
|
||||||
description: `Transaction - ${tx}`,
|
description: `Transaction - ${tx}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import {
|
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||||
Account,
|
import { sendTransaction } from 'common/src/contexts/connection';
|
||||||
Connection,
|
import { notify } from 'common/src/utils/notifications';
|
||||||
PublicKey,
|
import { accrueInterestInstruction, LendingReserve, withdrawInstruction } from './../models/lending';
|
||||||
TransactionInstruction,
|
import { AccountLayout } from '@solana/spl-token';
|
||||||
} from "@solana/web3.js";
|
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import { sendTransaction } from "../contexts/connection";
|
import { findOrCreateAccountByMint } from 'common/src/actions/account';
|
||||||
import { notify } from "../utils/notifications";
|
import { approve, TokenAccount } from 'common/src/models';
|
||||||
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";
|
|
||||||
|
|
||||||
export const withdraw = async (
|
export const withdraw = async (
|
||||||
from: TokenAccount, // CollateralAccount
|
from: TokenAccount, // CollateralAccount
|
||||||
|
@ -25,9 +16,9 @@ export const withdraw = async (
|
||||||
wallet: any
|
wallet: any
|
||||||
) => {
|
) => {
|
||||||
notify({
|
notify({
|
||||||
message: "Withdrawing funds...",
|
message: 'Withdrawing funds...',
|
||||||
description: "Please review transactions to approve.",
|
description: 'Please review transactions to approve.',
|
||||||
type: "warn",
|
type: 'warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
// user from account
|
// user from account
|
||||||
|
@ -35,25 +26,14 @@ export const withdraw = async (
|
||||||
const instructions: TransactionInstruction[] = [];
|
const instructions: TransactionInstruction[] = [];
|
||||||
const cleanupInstructions: TransactionInstruction[] = [];
|
const cleanupInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
AccountLayout.span
|
|
||||||
);
|
|
||||||
|
|
||||||
const [authority] = await PublicKey.findProgramAddress(
|
const [authority] = await PublicKey.findProgramAddress([reserve.lendingMarket.toBuffer()], LENDING_PROGRAM_ID);
|
||||||
[reserve.lendingMarket.toBuffer()],
|
|
||||||
LENDING_PROGRAM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
const fromAccount = from.pubkey;
|
const fromAccount = from.pubkey;
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
const transferAuthority = approve(
|
const transferAuthority = approve(instructions, cleanupInstructions, fromAccount, wallet.publicKey, amountLamports);
|
||||||
instructions,
|
|
||||||
cleanupInstructions,
|
|
||||||
fromAccount,
|
|
||||||
wallet.publicKey,
|
|
||||||
amountLamports
|
|
||||||
);
|
|
||||||
|
|
||||||
signers.push(transferAuthority);
|
signers.push(transferAuthority);
|
||||||
|
|
||||||
|
@ -85,17 +65,11 @@ export const withdraw = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let tx = await sendTransaction(
|
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
instructions.concat(cleanupInstructions),
|
|
||||||
signers,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
message: "Funds deposited.",
|
message: 'Funds deposited.',
|
||||||
type: "success",
|
type: 'success',
|
||||||
description: `Transaction - ${tx}`,
|
description: `Transaction - ${tx}`,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -1,48 +1,32 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Button, Popover } from "antd";
|
import { Button, Popover } from 'antd';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { CurrentUserBadge } from "../CurrentUserBadge";
|
import { CurrentUserBadge } from '../CurrentUserBadge';
|
||||||
import { SettingOutlined } from "@ant-design/icons";
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
import { Settings } from "../Settings";
|
import { Settings } from '../Settings';
|
||||||
import { LABELS } from "../../constants";
|
import { LABELS } from '../../constants';
|
||||||
|
|
||||||
export const AppBar = (props: { left?: JSX.Element; right?: JSX.Element }) => {
|
export const AppBar = (props: { left?: JSX.Element; right?: JSX.Element }) => {
|
||||||
const { connected, wallet } = useWallet();
|
const { connected, wallet } = useWallet();
|
||||||
|
|
||||||
const TopBar = (
|
const TopBar = (
|
||||||
<div className="App-Bar-right">
|
<div className='App-Bar-right'>
|
||||||
<CurrentUserBadge />
|
<CurrentUserBadge />
|
||||||
<div>
|
<div>
|
||||||
{!connected && (
|
{!connected && (
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type='text'
|
||||||
size="large"
|
size='large'
|
||||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||||
style={{ color: "#2abdd2" }}
|
style={{ color: '#2abdd2' }}
|
||||||
>
|
>
|
||||||
{LABELS.CONNECT_BUTTON}
|
{LABELS.CONNECT_BUTTON}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{connected && (
|
{connected && <Popover placement='bottomRight' title={LABELS.WALLET_TOOLTIP} trigger='click'></Popover>}
|
||||||
<Popover
|
|
||||||
placement="bottomRight"
|
|
||||||
title={LABELS.WALLET_TOOLTIP}
|
|
||||||
trigger="click"
|
|
||||||
></Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Popover
|
<Popover placement='topRight' title={LABELS.SETTINGS_TOOLTIP} content={<Settings />} trigger='click'>
|
||||||
placement="topRight"
|
<Button shape='circle' size='large' type='text' icon={<SettingOutlined />} />
|
||||||
title={LABELS.SETTINGS_TOOLTIP}
|
|
||||||
content={<Settings />}
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
shape="circle"
|
|
||||||
size="large"
|
|
||||||
type="text"
|
|
||||||
icon={<SettingOutlined />}
|
|
||||||
/>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
{props.right}
|
{props.right}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,37 +1,25 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import { useSliderInput, useUserBalance, useUserDeposits, useUserObligationByReserve } from '../../hooks';
|
||||||
useSliderInput,
|
import { BorrowAmountType, LendingReserve, LendingReserveParser } from '../../models';
|
||||||
useUserBalance,
|
import { Card } from 'antd';
|
||||||
useUserDeposits,
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
useUserObligationByReserve,
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
} from "../../hooks";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import {
|
import { borrow } from '../../actions';
|
||||||
BorrowAmountType,
|
import './style.less';
|
||||||
LendingReserve,
|
import { LABELS } from '../../constants';
|
||||||
LendingReserveParser,
|
import { ActionConfirmation } from './../ActionConfirmation';
|
||||||
} from "../../models";
|
import { BackButton } from './../BackButton';
|
||||||
import { Card } from "antd";
|
import { ConnectButton } from '../ConnectButton';
|
||||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
import CollateralInput from '../CollateralInput';
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useMidPriceInUSD } from '../../contexts/market';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { RiskSlider } from '../RiskSlider';
|
||||||
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: {
|
export const BorrowInput = (props: { className?: string; reserve: ParsedAccount<LendingReserve> }) => {
|
||||||
className?: string;
|
|
||||||
reserve: ParsedAccount<LendingReserve>;
|
|
||||||
}) => {
|
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const [collateralValue, setCollateralValue] = useState("");
|
const [collateralValue, setCollateralValue] = useState('');
|
||||||
const [lastTyped, setLastTyped] = useState("collateral");
|
const [lastTyped, setLastTyped] = useState('collateral');
|
||||||
const [pendingTx, setPendingTx] = useState(false);
|
const [pendingTx, setPendingTx] = useState(false);
|
||||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||||
|
|
||||||
|
@ -40,24 +28,14 @@ export const BorrowInput = (props: {
|
||||||
const [collateralReserveKey, setCollateralReserveKey] = useState<string>();
|
const [collateralReserveKey, setCollateralReserveKey] = useState<string>();
|
||||||
|
|
||||||
const collateralReserve = useMemo(() => {
|
const collateralReserve = useMemo(() => {
|
||||||
const id: string =
|
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserveKey) || '';
|
||||||
cache
|
|
||||||
.byParser(LendingReserveParser)
|
|
||||||
.find((acc) => acc === collateralReserveKey) || "";
|
|
||||||
|
|
||||||
return cache.get(id) as ParsedAccount<LendingReserve>;
|
return cache.get(id) as ParsedAccount<LendingReserve>;
|
||||||
}, [collateralReserveKey]);
|
}, [collateralReserveKey]);
|
||||||
const borrowPrice = useMidPriceInUSD(
|
const borrowPrice = useMidPriceInUSD(borrowReserve.info.liquidityMint.toBase58()).price;
|
||||||
borrowReserve.info.liquidityMint.toBase58()
|
const collateralPrice = useMidPriceInUSD(collateralReserve?.info.liquidityMint.toBase58())?.price;
|
||||||
).price;
|
|
||||||
const collateralPrice = useMidPriceInUSD(
|
|
||||||
collateralReserve?.info.liquidityMint.toBase58()
|
|
||||||
)?.price;
|
|
||||||
|
|
||||||
const include = useMemo(
|
const include = useMemo(() => new Set([collateralReserve?.pubkey.toBase58()]), [collateralReserve]);
|
||||||
() => new Set([collateralReserve?.pubkey.toBase58()]),
|
|
||||||
[collateralReserve]
|
|
||||||
);
|
|
||||||
|
|
||||||
const exclude = useMemo(() => new Set([]), []);
|
const exclude = useMemo(() => new Set([]), []);
|
||||||
|
|
||||||
|
@ -67,7 +45,7 @@ export const BorrowInput = (props: {
|
||||||
const convert = useCallback(
|
const convert = useCallback(
|
||||||
(val: string | number) => {
|
(val: string | number) => {
|
||||||
const minAmount = Math.min(tokenBalance, Infinity);
|
const minAmount = Math.min(tokenBalance, Infinity);
|
||||||
if (typeof val === "string") {
|
if (typeof val === 'string') {
|
||||||
return (parseFloat(val) / minAmount) * 100;
|
return (parseFloat(val) / minAmount) * 100;
|
||||||
} else {
|
} else {
|
||||||
return (val * minAmount) / 100;
|
return (val * minAmount) / 100;
|
||||||
|
@ -79,7 +57,7 @@ export const BorrowInput = (props: {
|
||||||
const { value, setValue, pct } = useSliderInput(convert);
|
const { value, setValue, pct } = useSliderInput(convert);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collateralReserve && lastTyped === "collateral") {
|
if (collateralReserve && lastTyped === 'collateral') {
|
||||||
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
||||||
|
|
||||||
if (collateralValue) {
|
if (collateralValue) {
|
||||||
|
@ -88,21 +66,13 @@ export const BorrowInput = (props: {
|
||||||
const borrowAmount = borrowInUSD / borrowPrice;
|
const borrowAmount = borrowInUSD / borrowPrice;
|
||||||
setValue(borrowAmount.toString());
|
setValue(borrowAmount.toString());
|
||||||
} else {
|
} else {
|
||||||
setValue("");
|
setValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [lastTyped, collateralReserve, collateralPrice, borrowPrice, borrowReserve, collateralValue, setValue]);
|
||||||
lastTyped,
|
|
||||||
collateralReserve,
|
|
||||||
collateralPrice,
|
|
||||||
borrowPrice,
|
|
||||||
borrowReserve,
|
|
||||||
collateralValue,
|
|
||||||
setValue,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collateralReserve && lastTyped === "borrow") {
|
if (collateralReserve && lastTyped === 'borrow') {
|
||||||
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
const ltv = borrowReserve.info.config.loanToValueRatio / 100;
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -111,25 +81,13 @@ export const BorrowInput = (props: {
|
||||||
const collateralAmount = borrowInUSD / ltv / collateralPrice;
|
const collateralAmount = borrowInUSD / ltv / collateralPrice;
|
||||||
setCollateralValue(collateralAmount.toString());
|
setCollateralValue(collateralAmount.toString());
|
||||||
} else {
|
} else {
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [lastTyped, collateralReserve, collateralPrice, borrowPrice, borrowReserve, value]);
|
||||||
lastTyped,
|
|
||||||
collateralReserve,
|
|
||||||
collateralPrice,
|
|
||||||
borrowPrice,
|
|
||||||
borrowReserve,
|
|
||||||
value,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { userObligationsByReserve } = useUserObligationByReserve(
|
const { userObligationsByReserve } = useUserObligationByReserve(borrowReserve?.pubkey, collateralReserve?.pubkey);
|
||||||
borrowReserve?.pubkey,
|
const { accounts: fromAccounts } = useUserBalance(collateralReserve?.info.collateralMint);
|
||||||
collateralReserve?.pubkey
|
|
||||||
);
|
|
||||||
const { accounts: fromAccounts } = useUserBalance(
|
|
||||||
collateralReserve?.info.collateralMint
|
|
||||||
);
|
|
||||||
const onBorrow = useCallback(() => {
|
const onBorrow = useCallback(() => {
|
||||||
if (!collateralReserve) {
|
if (!collateralReserve) {
|
||||||
return;
|
return;
|
||||||
|
@ -151,17 +109,13 @@ export const BorrowInput = (props: {
|
||||||
collateralReserve,
|
collateralReserve,
|
||||||
|
|
||||||
// TODO: select exsisting obligations by collateral reserve
|
// TODO: select exsisting obligations by collateral reserve
|
||||||
userObligationsByReserve.length > 0
|
userObligationsByReserve.length > 0 ? userObligationsByReserve[0].obligation.account : undefined,
|
||||||
? userObligationsByReserve[0].obligation.account
|
|
||||||
: undefined,
|
|
||||||
|
|
||||||
userObligationsByReserve.length > 0
|
userObligationsByReserve.length > 0 ? userObligationsByReserve[0].userAccounts[0].pubkey : undefined
|
||||||
? userObligationsByReserve[0].userAccounts[0].pubkey
|
|
||||||
: undefined
|
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("");
|
setValue('');
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
setShowConfirmation(true);
|
setShowConfirmation(true);
|
||||||
} catch {
|
} catch {
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -183,11 +137,11 @@ export const BorrowInput = (props: {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bodyStyle: React.CSSProperties = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -197,27 +151,27 @@ export const BorrowInput = (props: {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="borrow-input-title">{LABELS.BORROW_QUESTION}</div>
|
<div className='borrow-input-title'>{LABELS.BORROW_QUESTION}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Collateral (estimated)"
|
title='Collateral (estimated)'
|
||||||
reserve={borrowReserve.info}
|
reserve={borrowReserve.info}
|
||||||
amount={parseFloat(collateralValue) || 0}
|
amount={parseFloat(collateralValue) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setCollateralValue(val?.toString() || "");
|
setCollateralValue(val?.toString() || '');
|
||||||
setLastTyped("collateral");
|
setLastTyped('collateral');
|
||||||
}}
|
}}
|
||||||
onCollateralReserve={(key) => {
|
onCollateralReserve={(key) => {
|
||||||
setCollateralReserveKey(key);
|
setCollateralReserveKey(key);
|
||||||
|
@ -228,35 +182,33 @@ export const BorrowInput = (props: {
|
||||||
<RiskSlider value={pct} />
|
<RiskSlider value={pct} />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Borrow Amount"
|
title='Borrow Amount'
|
||||||
reserve={borrowReserve.info}
|
reserve={borrowReserve.info}
|
||||||
amount={parseFloat(value) || 0}
|
amount={parseFloat(value) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setValue(val?.toString() || "");
|
setValue(val?.toString() || '');
|
||||||
setLastTyped("borrow");
|
setLastTyped('borrow');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
hideBalance={true}
|
hideBalance={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConnectButton
|
<ConnectButton
|
||||||
size="large"
|
size='large'
|
||||||
type="primary"
|
type='primary'
|
||||||
onClick={onBorrow}
|
onClick={onBorrow}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{fromAccounts.length === 0
|
{fromAccounts.length === 0 ? LABELS.NO_COLLATERAL : LABELS.BORROW_ACTION}
|
||||||
? LABELS.NO_COLLATERAL
|
|
||||||
: LABELS.BORROW_ACTION}
|
|
||||||
</ConnectButton>
|
</ConnectButton>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from 'react';
|
||||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
import { useConnectionConfig } from "../../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import {
|
import { useLendingReserves, useUserBalance, useUserDeposits } from '../../hooks';
|
||||||
useLendingReserves,
|
import { LendingReserve, LendingMarket, LendingReserveParser } from '../../models';
|
||||||
useUserBalance,
|
import { getTokenName } from 'common/src/utils/utils';
|
||||||
useUserDeposits,
|
import { Card, Select } from 'antd';
|
||||||
} from "../../hooks";
|
import { TokenIcon } from '../TokenIcon';
|
||||||
import {
|
import { NumericInput } from '../Input/numeric';
|
||||||
LendingReserve,
|
import './style.less';
|
||||||
LendingMarket,
|
import { TokenDisplay } from '../TokenDisplay';
|
||||||
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";
|
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
@ -41,24 +33,19 @@ export default function CollateralInput(props: {
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
const [collateralReserve, setCollateralReserve] = useState<string>();
|
const [collateralReserve, setCollateralReserve] = useState<string>();
|
||||||
const [balance, setBalance] = useState<number>(0);
|
const [balance, setBalance] = useState<number>(0);
|
||||||
const [lastAmount, setLastAmount] = useState<string>("");
|
const [lastAmount, setLastAmount] = useState<string>('');
|
||||||
const userDeposits = useUserDeposits();
|
const userDeposits = useUserDeposits();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.useWalletBalance) {
|
if (props.useWalletBalance) {
|
||||||
setBalance(tokenBalance);
|
setBalance(tokenBalance);
|
||||||
} else {
|
} else {
|
||||||
const id: string =
|
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserve) || '';
|
||||||
cache
|
|
||||||
.byParser(LendingReserveParser)
|
|
||||||
.find((acc) => acc === collateralReserve) || "";
|
|
||||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||||
|
|
||||||
if (parser) {
|
if (parser) {
|
||||||
const collateralDeposit = userDeposits.userDeposits.find(
|
const collateralDeposit = userDeposits.userDeposits.find(
|
||||||
(u) =>
|
(u) => u.reserve.info.liquidityMint.toBase58() === parser.info.liquidityMint.toBase58()
|
||||||
u.reserve.info.liquidityMint.toBase58() ===
|
|
||||||
parser.info.liquidityMint.toBase58()
|
|
||||||
);
|
);
|
||||||
if (collateralDeposit) setBalance(collateralDeposit.info.amount);
|
if (collateralDeposit) setBalance(collateralDeposit.info.amount);
|
||||||
else setBalance(0);
|
else setBalance(0);
|
||||||
|
@ -66,28 +53,16 @@ export default function CollateralInput(props: {
|
||||||
}
|
}
|
||||||
}, [collateralReserve, userDeposits, tokenBalance, props.useWalletBalance]);
|
}, [collateralReserve, userDeposits, tokenBalance, props.useWalletBalance]);
|
||||||
|
|
||||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<
|
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
LendingMarket
|
|
||||||
>;
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
|
|
||||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(
|
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
|
||||||
market?.info?.quoteMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredReserveAccounts = reserveAccounts
|
const filteredReserveAccounts = reserveAccounts
|
||||||
.filter((reserve) => reserve.info !== props.reserve)
|
.filter((reserve) => reserve.info !== props.reserve)
|
||||||
.filter(
|
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint));
|
||||||
(reserve) =>
|
|
||||||
!onlyQuoteAllowed ||
|
|
||||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (!collateralReserve && props.useFirstReserve && filteredReserveAccounts.length) {
|
||||||
!collateralReserve &&
|
|
||||||
props.useFirstReserve &&
|
|
||||||
filteredReserveAccounts.length
|
|
||||||
) {
|
|
||||||
const address = filteredReserveAccounts[0].pubkey.toBase58();
|
const address = filteredReserveAccounts[0].pubkey.toBase58();
|
||||||
setCollateralReserve(address);
|
setCollateralReserve(address);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +72,7 @@ export default function CollateralInput(props: {
|
||||||
const name = getTokenName(tokenMap, mint);
|
const name = getTokenName(tokenMap, mint);
|
||||||
return (
|
return (
|
||||||
<Option key={address} value={address} name={name} title={address}>
|
<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} />
|
<TokenIcon mintAddress={mint} />
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,30 +81,19 @@ export default function CollateralInput(props: {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className='ccy-input' style={{ borderRadius: 20 }} bodyStyle={{ padding: 0 }}>
|
||||||
className="ccy-input"
|
<div className='ccy-input-header'>
|
||||||
style={{ borderRadius: 20 }}
|
<div className='ccy-input-header-left'>{props.title}</div>
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
<div className="ccy-input-header">
|
|
||||||
<div className="ccy-input-header-left">{props.title}</div>
|
|
||||||
|
|
||||||
{!props.hideBalance && (
|
{!props.hideBalance && (
|
||||||
<div
|
<div className='ccy-input-header-right' onClick={(e) => props.onInputChange && props.onInputChange(balance)}>
|
||||||
className="ccy-input-header-right"
|
|
||||||
onClick={(e) => props.onInputChange && props.onInputChange(balance)}
|
|
||||||
>
|
|
||||||
Balance: {balance.toFixed(6)}
|
Balance: {balance.toFixed(6)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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
|
<NumericInput
|
||||||
value={
|
value={parseFloat(lastAmount || '0.00') === props.amount ? lastAmount : props.amount?.toFixed(6)?.toString()}
|
||||||
parseFloat(lastAmount || "0.00") === props.amount
|
|
||||||
? lastAmount
|
|
||||||
: props.amount?.toFixed(6)?.toString()
|
|
||||||
}
|
|
||||||
onChange={(val: string) => {
|
onChange={(val: string) => {
|
||||||
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||||
if (!val || !parseFloat(val)) props.onInputChange(null);
|
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||||
|
@ -139,19 +103,19 @@ export default function CollateralInput(props: {
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
boxShadow: "none",
|
boxShadow: 'none',
|
||||||
borderColor: "transparent",
|
borderColor: 'transparent',
|
||||||
outline: "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 && (
|
{props.showLeverageSelector && (
|
||||||
<Select
|
<Select
|
||||||
size="large"
|
size='large'
|
||||||
showSearch
|
showSearch
|
||||||
style={{ width: 80 }}
|
style={{ width: 80 }}
|
||||||
placeholder="CCY"
|
placeholder='CCY'
|
||||||
value={props.leverage}
|
value={props.leverage}
|
||||||
onChange={(item: number) => {
|
onChange={(item: number) => {
|
||||||
if (props.onLeverage) props.onLeverage(item);
|
if (props.onLeverage) props.onLeverage(item);
|
||||||
|
@ -162,22 +126,12 @@ export default function CollateralInput(props: {
|
||||||
props.onLeverage(parseFloat(item));
|
props.onLeverage(parseFloat(item));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{[1, 2, 3, 4, 5].map((val) => (
|
{[1, 2, 3, 4, 5].map((val) => (
|
||||||
<Option
|
<Option key={val} value={val} name={val + 'x'} title={val + 'x'}>
|
||||||
key={val}
|
<div key={val} style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
value={val}
|
{val + 'x'}
|
||||||
name={val + "x"}
|
|
||||||
title={val + "x"}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
key={val}
|
|
||||||
style={{ display: "flex", alignItems: "center" }}
|
|
||||||
>
|
|
||||||
{val + "x"}
|
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
|
@ -185,28 +139,23 @@ export default function CollateralInput(props: {
|
||||||
)}
|
)}
|
||||||
{!props.disabled ? (
|
{!props.disabled ? (
|
||||||
<Select
|
<Select
|
||||||
size="large"
|
size='large'
|
||||||
showSearch
|
showSearch
|
||||||
style={{ minWidth: 150 }}
|
style={{ minWidth: 150 }}
|
||||||
placeholder="CCY"
|
placeholder='CCY'
|
||||||
value={collateralReserve}
|
value={collateralReserve}
|
||||||
onChange={(item) => {
|
onChange={(item) => {
|
||||||
if (props.onCollateralReserve) props.onCollateralReserve(item);
|
if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||||
setCollateralReserve(item);
|
setCollateralReserve(item);
|
||||||
}}
|
}}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{renderReserveAccounts}
|
{renderReserveAccounts}
|
||||||
</Select>
|
</Select>
|
||||||
) : (
|
) : (
|
||||||
<TokenDisplay
|
<TokenDisplay
|
||||||
key={props.reserve.liquidityMint.toBase58()}
|
key={props.reserve.liquidityMint.toBase58()}
|
||||||
name={getTokenName(
|
name={getTokenName(tokenMap, props.reserve.liquidityMint.toBase58())}
|
||||||
tokenMap,
|
|
||||||
props.reserve.liquidityMint.toBase58()
|
|
||||||
)}
|
|
||||||
mintAddress={props.reserve.liquidityMint.toBase58()}
|
mintAddress={props.reserve.liquidityMint.toBase58()}
|
||||||
showBalance={false}
|
showBalance={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useLendingReserves, UserDeposit, useUserDeposits } from "../../hooks";
|
import { useLendingReserves, UserDeposit, useUserDeposits } from '../../hooks';
|
||||||
import { LendingMarket, LendingReserve } from "../../models";
|
import { LendingMarket, LendingReserve } from '../../models';
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from '../TokenIcon';
|
||||||
import { formatAmount, getTokenName } from "../../utils/utils";
|
import { formatAmount, getTokenName } from 'common/src/utils/utils';
|
||||||
import { Select } from "antd";
|
import { Select } from 'antd';
|
||||||
import { useConnectionConfig } from "../../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
@ -19,12 +19,10 @@ export const CollateralItem = (props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<TokenIcon mintAddress={mint} />
|
<TokenIcon mintAddress={mint} />
|
||||||
{name}
|
{name}
|
||||||
<span className="token-balance">
|
<span className='token-balance'> {userDeposit ? formatAmount(userDeposit.info.amount) : '--'}</span>
|
||||||
{userDeposit ? formatAmount(userDeposit.info.amount) : "--"}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -40,22 +38,19 @@ export const CollateralSelector = (props: {
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
const { userDeposits } = useUserDeposits();
|
const { userDeposits } = useUserDeposits();
|
||||||
|
|
||||||
const market = cache.get(props.reserve?.lendingMarket) as ParsedAccount<
|
const market = cache.get(props.reserve?.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
LendingMarket
|
|
||||||
>;
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
|
|
||||||
const quoteMintAddress = market?.info?.quoteMint?.toBase58();
|
const quoteMintAddress = market?.info?.quoteMint?.toBase58();
|
||||||
|
|
||||||
const onlyQuoteAllowed =
|
const onlyQuoteAllowed = props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress;
|
||||||
props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
size="large"
|
size='large'
|
||||||
showSearch
|
showSearch
|
||||||
style={{ minWidth: 300, margin: "5px 0px" }}
|
style={{ minWidth: 300, margin: '5px 0px' }}
|
||||||
placeholder="Collateral"
|
placeholder='Collateral'
|
||||||
value={props.collateralReserve}
|
value={props.collateralReserve}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
defaultValue={props.collateralReserve}
|
defaultValue={props.collateralReserve}
|
||||||
|
@ -64,17 +59,11 @@ export const CollateralSelector = (props: {
|
||||||
props.onCollateralReserve(item);
|
props.onCollateralReserve(item);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{reserveAccounts
|
{reserveAccounts
|
||||||
.filter((reserve) => reserve.info !== props.reserve)
|
.filter((reserve) => reserve.info !== props.reserve)
|
||||||
.filter(
|
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint))
|
||||||
(reserve) =>
|
|
||||||
!onlyQuoteAllowed ||
|
|
||||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
|
||||||
)
|
|
||||||
.map((reserve) => {
|
.map((reserve) => {
|
||||||
const mint = reserve.info.liquidityMint.toBase58();
|
const mint = reserve.info.liquidityMint.toBase58();
|
||||||
const address = reserve.pubkey.toBase58();
|
const address = reserve.pubkey.toBase58();
|
||||||
|
@ -84,9 +73,7 @@ export const CollateralSelector = (props: {
|
||||||
<Option key={address} value={address} name={name} title={address}>
|
<Option key={address} value={address} name={name} title={address}>
|
||||||
<CollateralItem
|
<CollateralItem
|
||||||
reserve={reserve}
|
reserve={reserve}
|
||||||
userDeposit={userDeposits.find(
|
userDeposit={userDeposits.find((dep) => dep.reserve.pubkey.toBase58() === address)}
|
||||||
(dep) => dep.reserve.pubkey.toBase58() === address
|
|
||||||
)}
|
|
||||||
mint={mint}
|
mint={mint}
|
||||||
name={name}
|
name={name}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
import { Button } from "antd";
|
import { Button } from 'antd';
|
||||||
import { ButtonProps } from "antd/lib/button";
|
import { ButtonProps } from 'antd/lib/button';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { LABELS } from "./../../constants";
|
import { LABELS } from './../../constants';
|
||||||
|
|
||||||
export const ConnectButton = (
|
export const ConnectButton = (props: ButtonProps & React.RefAttributes<HTMLElement>) => {
|
||||||
props: ButtonProps & React.RefAttributes<HTMLElement>
|
|
||||||
) => {
|
|
||||||
const { wallet, connected } = useWallet();
|
const { wallet, connected } = useWallet();
|
||||||
const { onClick, children, disabled, ...rest } = props;
|
const { onClick, children, disabled, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button {...rest} onClick={connected ? onClick : wallet.connect} disabled={connected && disabled}>
|
||||||
{...rest}
|
|
||||||
onClick={connected ? onClick : wallet.connect}
|
|
||||||
disabled={connected && disabled}
|
|
||||||
>
|
|
||||||
{connected ? props.children : LABELS.CONNECT_LABEL}
|
{connected ? props.children : LABELS.CONNECT_LABEL}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { formatNumber, shortenAddress } from "../../utils/utils";
|
import { formatNumber, shortenAddress } from 'common/src/utils/utils';
|
||||||
import { Identicon } from "../Identicon";
|
import { Identicon } from '../Identicon';
|
||||||
import { useNativeAccount } from "../../contexts/accounts";
|
import { useNativeAccount } from 'common/src/contexts/accounts';
|
||||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||||
|
|
||||||
export const CurrentUserBadge = (props: {}) => {
|
export const CurrentUserBadge = (props: {}) => {
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
|
@ -16,16 +16,11 @@ export const CurrentUserBadge = (props: {}) => {
|
||||||
// should use SOL ◎ ?
|
// should use SOL ◎ ?
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wallet-wrapper">
|
<div className='wallet-wrapper'>
|
||||||
<span>
|
<span>{formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL</span>
|
||||||
{formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL
|
<div className='wallet-key'>
|
||||||
</span>
|
|
||||||
<div className="wallet-key">
|
|
||||||
{shortenAddress(`${wallet.publicKey}`)}
|
{shortenAddress(`${wallet.publicKey}`)}
|
||||||
<Identicon
|
<Identicon address={wallet.publicKey?.toBase58()} style={{ marginLeft: '0.5rem', display: 'flex' }} />
|
||||||
address={wallet.publicKey?.toBase58()}
|
|
||||||
style={{ marginLeft: "0.5rem", display: "flex" }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,58 +1,39 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from 'react';
|
||||||
import {
|
import { useUserBalance, useUserCollateralBalance } from './../../hooks';
|
||||||
useTokenName,
|
import { useTokenName } from 'common/src/hooks';
|
||||||
useUserBalance,
|
import { calculateDepositAPY, LendingReserve } from '../../models/lending';
|
||||||
useUserCollateralBalance,
|
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||||
} from "./../../hooks";
|
import { Card, Col, Row, Statistic } from 'antd';
|
||||||
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
|
import './style.less';
|
||||||
import { formatNumber, formatPct } from "../../utils/utils";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { Card, Col, Row, Statistic } from "antd";
|
import { GUTTER } from '../../constants';
|
||||||
import "./style.less";
|
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { GUTTER } from "../../constants";
|
|
||||||
|
|
||||||
export const DepositInfoLine = (props: {
|
export const DepositInfoLine = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||||
className?: string;
|
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const name = useTokenName(props.reserve.liquidityMint);
|
const name = useTokenName(props.reserve.liquidityMint);
|
||||||
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
|
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
|
||||||
const { balance: collateralBalance } = useUserCollateralBalance(
|
const { balance: collateralBalance } = useUserCollateralBalance(props.reserve);
|
||||||
props.reserve
|
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [props.reserve]);
|
||||||
);
|
|
||||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
|
|
||||||
props.reserve,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={GUTTER}>
|
<Row gutter={GUTTER}>
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic
|
<Statistic title='Your balance in Oyster' value={formatNumber.format(collateralBalance)} suffix={name} />
|
||||||
title="Your balance in Oyster"
|
|
||||||
value={formatNumber.format(collateralBalance)}
|
|
||||||
suffix={name}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic
|
<Statistic title='Your wallet balance' value={formatNumber.format(tokenBalance)} suffix={name} />
|
||||||
title="Your wallet balance"
|
|
||||||
value={formatNumber.format(tokenBalance)}
|
|
||||||
suffix={name}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic title="Health Factor" value="--" />
|
<Statistic title='Health Factor' value='--' />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} xl={9}>
|
<Col xs={24} xl={9}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic title="APY" value={formatPct.format(depositAPY)} />
|
<Statistic title='APY' value={formatPct.format(depositAPY)} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from 'react';
|
||||||
import { InputType, useSliderInput, useUserBalance } from "../../hooks";
|
import { InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { LendingReserve } from '../../models/lending';
|
||||||
import { Card, Slider } from "antd";
|
import { Card, Slider } from 'antd';
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { deposit } from "../../actions/deposit";
|
import { deposit } from '../../actions/deposit';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import "./style.less";
|
import './style.less';
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { ActionConfirmation } from './../ActionConfirmation';
|
||||||
import { LABELS, marks } from "../../constants";
|
import { LABELS, marks } from '../../constants';
|
||||||
import { ConnectButton } from "../ConnectButton";
|
import { ConnectButton } from '../ConnectButton';
|
||||||
import CollateralInput from "../CollateralInput";
|
import CollateralInput from '../CollateralInput';
|
||||||
|
|
||||||
export const DepositInput = (props: {
|
export const DepositInput = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||||
className?: string;
|
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const [pendingTx, setPendingTx] = useState(false);
|
const [pendingTx, setPendingTx] = useState(false);
|
||||||
|
@ -25,13 +21,11 @@ export const DepositInput = (props: {
|
||||||
const reserve = props.reserve;
|
const reserve = props.reserve;
|
||||||
const address = props.address;
|
const address = props.address;
|
||||||
|
|
||||||
const { accounts: fromAccounts, balance, balanceLamports } = useUserBalance(
|
const { accounts: fromAccounts, balance, balanceLamports } = useUserBalance(reserve?.liquidityMint);
|
||||||
reserve?.liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const convert = useCallback(
|
const convert = useCallback(
|
||||||
(val: string | number) => {
|
(val: string | number) => {
|
||||||
if (typeof val === "string") {
|
if (typeof val === 'string') {
|
||||||
return (parseFloat(val) / balance) * 100;
|
return (parseFloat(val) / balance) * 100;
|
||||||
} else {
|
} else {
|
||||||
return (val * balance) / 100;
|
return (val * balance) / 100;
|
||||||
|
@ -58,7 +52,7 @@ export const DepositInput = (props: {
|
||||||
wallet
|
wallet
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("");
|
setValue('');
|
||||||
setShowConfirmation(true);
|
setShowConfirmation(true);
|
||||||
} catch {
|
} catch {
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -66,26 +60,14 @@ export const DepositInput = (props: {
|
||||||
setPendingTx(false);
|
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 = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -95,26 +77,26 @@ export const DepositInput = (props: {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="deposit-input-title">{LABELS.DEPOSIT_QUESTION}</div>
|
<div className='deposit-input-title'>{LABELS.DEPOSIT_QUESTION}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Amount"
|
title='Amount'
|
||||||
reserve={reserve}
|
reserve={reserve}
|
||||||
amount={parseFloat(value) || 0}
|
amount={parseFloat(value) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setValue(val?.toString() || "");
|
setValue(val?.toString() || '');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
hideBalance={true}
|
hideBalance={true}
|
||||||
|
@ -124,8 +106,8 @@ export const DepositInput = (props: {
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
|
|
||||||
<ConnectButton
|
<ConnectButton
|
||||||
size="large"
|
size='large'
|
||||||
type="primary"
|
type='primary'
|
||||||
onClick={onDeposit}
|
onClick={onDeposit}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import "./../../App.less";
|
import './../../App.less';
|
||||||
import { Menu } from "antd";
|
import { Menu } from 'antd';
|
||||||
import {
|
import {
|
||||||
PieChartOutlined,
|
PieChartOutlined,
|
||||||
GithubOutlined,
|
GithubOutlined,
|
||||||
|
@ -11,103 +11,97 @@ import {
|
||||||
RocketOutlined,
|
RocketOutlined,
|
||||||
ForkOutlined,
|
ForkOutlined,
|
||||||
// LineChartOutlined
|
// LineChartOutlined
|
||||||
} from "@ant-design/icons";
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
import BasicLayout from "@ant-design/pro-layout";
|
import BasicLayout from '@ant-design/pro-layout';
|
||||||
import { AppBar } from "./../AppBar";
|
import { AppBar } from './../AppBar';
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { useConnectionConfig } from "../../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { LABELS } from "../../constants";
|
import { LABELS } from '../../constants';
|
||||||
import config from "./../../../package.json";
|
import config from './../../../package.json';
|
||||||
|
|
||||||
export const AppLayout = React.memo((props: any) => {
|
export const AppLayout = React.memo((props: any) => {
|
||||||
const { env } = useConnectionConfig();
|
const { env } = useConnectionConfig();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const paths: { [key: string]: string } = {
|
const paths: { [key: string]: string } = {
|
||||||
"/dashboard": "2",
|
'/dashboard': '2',
|
||||||
"/deposit": "3",
|
'/deposit': '3',
|
||||||
"/borrow": "4",
|
'/borrow': '4',
|
||||||
"/liquidate": "5",
|
'/liquidate': '5',
|
||||||
"/margin": "6",
|
'/margin': '6',
|
||||||
"/faucet": "7",
|
'/faucet': '7',
|
||||||
};
|
};
|
||||||
|
|
||||||
const current =
|
const current = [...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) || '';
|
||||||
[...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) ||
|
const defaultKey = paths[current] || '1';
|
||||||
"";
|
const theme = 'dark';
|
||||||
const defaultKey = paths[current] || "1";
|
|
||||||
const theme = "dark";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className='App'>
|
||||||
<div className="Banner">
|
<div className='Banner'>
|
||||||
<div className="Banner-description">{LABELS.AUDIT_WARNING}</div>
|
<div className='Banner-description'>{LABELS.AUDIT_WARNING}</div>
|
||||||
</div>
|
</div>
|
||||||
<BasicLayout
|
<BasicLayout
|
||||||
title={LABELS.APP_TITLE}
|
title={LABELS.APP_TITLE}
|
||||||
footerRender={() => (
|
footerRender={() => (
|
||||||
<div className="footer" title={LABELS.FOOTER}>
|
<div className='footer' title={LABELS.FOOTER}>
|
||||||
{LABELS.FOOTER}
|
{LABELS.FOOTER}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
navTheme={theme}
|
navTheme={theme}
|
||||||
headerTheme={theme}
|
headerTheme={theme}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
layout="mix"
|
layout='mix'
|
||||||
fixSiderbar={true}
|
fixSiderbar={true}
|
||||||
primaryColor="#d83aeb"
|
primaryColor='#d83aeb'
|
||||||
logo={<div className="App-logo" />}
|
logo={<div className='App-logo' />}
|
||||||
rightContentRender={() => <AppBar />}
|
rightContentRender={() => <AppBar />}
|
||||||
links={[]}
|
links={[]}
|
||||||
menuContentRender={() => {
|
menuContentRender={() => {
|
||||||
return (
|
return (
|
||||||
<div className="links">
|
<div className='links'>
|
||||||
<Menu
|
<Menu theme={theme} defaultSelectedKeys={[defaultKey]} mode='inline'>
|
||||||
theme={theme}
|
<Menu.Item key='1' icon={<HomeOutlined />}>
|
||||||
defaultSelectedKeys={[defaultKey]}
|
|
||||||
mode="inline"
|
|
||||||
>
|
|
||||||
<Menu.Item key="1" icon={<HomeOutlined />}>
|
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/",
|
pathname: '/',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_HOME}
|
{LABELS.MENU_HOME}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="2" icon={<PieChartOutlined />}>
|
<Menu.Item key='2' icon={<PieChartOutlined />}>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/dashboard",
|
pathname: '/dashboard',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_DASHBOARD}
|
{LABELS.MENU_DASHBOARD}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="3" icon={<BankOutlined />}>
|
<Menu.Item key='3' icon={<BankOutlined />}>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/deposit",
|
pathname: '/deposit',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_DEPOSIT}
|
{LABELS.MENU_DEPOSIT}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="4" icon={<LogoutOutlined />}>
|
<Menu.Item key='4' icon={<LogoutOutlined />}>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/borrow",
|
pathname: '/borrow',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_BORROW}
|
{LABELS.MENU_BORROW}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="5" icon={<ShoppingOutlined />}>
|
<Menu.Item key='5' icon={<ShoppingOutlined />}>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/liquidate",
|
pathname: '/liquidate',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_LIQUIDATE}
|
{LABELS.MENU_LIQUIDATE}
|
||||||
|
@ -123,11 +117,11 @@ export const AppLayout = React.memo((props: any) => {
|
||||||
{LABELS.MARGIN_TRADING}
|
{LABELS.MARGIN_TRADING}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item> */}
|
</Menu.Item> */}
|
||||||
{env !== "mainnet-beta" && (
|
{env !== 'mainnet-beta' && (
|
||||||
<Menu.Item key="7" icon={<RocketOutlined />}>
|
<Menu.Item key='7' icon={<RocketOutlined />}>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/faucet",
|
pathname: '/faucet',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{LABELS.MENU_FAUCET}
|
{LABELS.MENU_FAUCET}
|
||||||
|
@ -139,27 +133,17 @@ export const AppLayout = React.memo((props: any) => {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
defaultSelectedKeys={[defaultKey]}
|
defaultSelectedKeys={[defaultKey]}
|
||||||
selectable={false}
|
selectable={false}
|
||||||
mode="inline"
|
mode='inline'
|
||||||
className="bottom-links"
|
className='bottom-links'
|
||||||
>
|
>
|
||||||
<Menu.Item key="16" icon={<ForkOutlined />}>
|
<Menu.Item key='16' icon={<ForkOutlined />}>
|
||||||
<a
|
<a title='Fork' href={`${config.repository.url}/fork`} target='_blank' rel='noopener noreferrer'>
|
||||||
title="Fork"
|
|
||||||
href={`${config.repository.url}/fork`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Fork
|
Fork
|
||||||
</a>
|
</a>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
,
|
,
|
||||||
<Menu.Item key="15" icon={<GithubOutlined />}>
|
<Menu.Item key='15' icon={<GithubOutlined />}>
|
||||||
<a
|
<a title='Gtihub' href={config.repository.url} target='_blank' rel='noopener noreferrer'>
|
||||||
title="Gtihub"
|
|
||||||
href={config.repository.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Github
|
Github
|
||||||
</a>
|
</a>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
import { Slider } from "antd";
|
import { Slider } from 'antd';
|
||||||
import Card from "antd/lib/card";
|
import Card from 'antd/lib/card';
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useState } from "react";
|
import { useState } from 'react';
|
||||||
import { LABELS, marks } from "../../constants";
|
import { LABELS, marks } from '../../constants';
|
||||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||||
import {
|
import { EnrichedLendingObligation, InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||||
EnrichedLendingObligation,
|
import { LendingReserve } from '../../models';
|
||||||
InputType,
|
import { ActionConfirmation } from '../ActionConfirmation';
|
||||||
useSliderInput,
|
import { liquidate } from '../../actions';
|
||||||
useUserBalance,
|
import './style.less';
|
||||||
} from "../../hooks";
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
import { LendingReserve } from "../../models";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { ActionConfirmation } from "../ActionConfirmation";
|
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
import { liquidate } from "../../actions";
|
import CollateralInput from '../CollateralInput';
|
||||||
import "./style.less";
|
import { notify } from 'common/src/utils/notifications';
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { ConnectButton } from '../ConnectButton';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useMidPriceInUSD } from '../../contexts/market';
|
||||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
|
||||||
import CollateralInput from "../CollateralInput";
|
|
||||||
import { notify } from "../../utils/notifications";
|
|
||||||
import { ConnectButton } from "../ConnectButton";
|
|
||||||
import { useMidPriceInUSD } from "../../contexts/market";
|
|
||||||
|
|
||||||
export const LiquidateInput = (props: {
|
export const LiquidateInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -31,26 +26,22 @@ export const LiquidateInput = (props: {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const { repayReserve, withdrawReserve, obligation } = props;
|
const { repayReserve, withdrawReserve, obligation } = props;
|
||||||
const [lastTyped, setLastTyped] = useState("liquidate");
|
const [lastTyped, setLastTyped] = useState('liquidate');
|
||||||
const [pendingTx, setPendingTx] = useState(false);
|
const [pendingTx, setPendingTx] = useState(false);
|
||||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||||
const [collateralValue, setCollateralValue] = useState("");
|
const [collateralValue, setCollateralValue] = useState('');
|
||||||
|
|
||||||
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
||||||
const { accounts: fromAccounts, balance: tokenBalance } = useUserBalance(
|
const { accounts: fromAccounts, balance: tokenBalance } = useUserBalance(repayReserve?.info.liquidityMint);
|
||||||
repayReserve?.info.liquidityMint
|
const borrowAmountLamports = wadToLamports(obligation.info.borrowAmountWad).toNumber();
|
||||||
);
|
|
||||||
const borrowAmountLamports = wadToLamports(
|
|
||||||
obligation.info.borrowAmountWad
|
|
||||||
).toNumber();
|
|
||||||
|
|
||||||
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
||||||
|
|
||||||
const convert = useCallback(
|
const convert = useCallback(
|
||||||
(val: string | number) => {
|
(val: string | number) => {
|
||||||
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
||||||
setLastTyped("liquidate");
|
setLastTyped('liquidate');
|
||||||
if (typeof val === "string") {
|
if (typeof val === 'string') {
|
||||||
return (parseFloat(val) / minAmount) * 100;
|
return (parseFloat(val) / minAmount) * 100;
|
||||||
} else {
|
} else {
|
||||||
return (val * minAmount) / 100;
|
return (val * minAmount) / 100;
|
||||||
|
@ -73,9 +64,7 @@ export const LiquidateInput = (props: {
|
||||||
const toLiquidateLamports =
|
const toLiquidateLamports =
|
||||||
type === InputType.Percent && tokenBalance >= borrowAmount
|
type === InputType.Percent && tokenBalance >= borrowAmount
|
||||||
? (pct * borrowAmountLamports) / 100
|
? (pct * borrowAmountLamports) / 100
|
||||||
: Math.ceil(
|
: Math.ceil(borrowAmountLamports * (parseFloat(value) / borrowAmount));
|
||||||
borrowAmountLamports * (parseFloat(value) / borrowAmount)
|
|
||||||
);
|
|
||||||
await liquidate(
|
await liquidate(
|
||||||
connection,
|
connection,
|
||||||
wallet,
|
wallet,
|
||||||
|
@ -87,14 +76,14 @@ export const LiquidateInput = (props: {
|
||||||
withdrawReserve
|
withdrawReserve
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("");
|
setValue('');
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
setShowConfirmation(true);
|
setShowConfirmation(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO:
|
// TODO:
|
||||||
notify({
|
notify({
|
||||||
message: "Unable to liquidate loan.",
|
message: 'Unable to liquidate loan.',
|
||||||
type: "error",
|
type: 'error',
|
||||||
description: error.message,
|
description: error.message,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -117,12 +106,10 @@ export const LiquidateInput = (props: {
|
||||||
type,
|
type,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const collateralPrice = useMidPriceInUSD(
|
const collateralPrice = useMidPriceInUSD(withdrawReserve?.info.liquidityMint.toBase58())?.price;
|
||||||
withdrawReserve?.info.liquidityMint.toBase58()
|
|
||||||
)?.price;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (withdrawReserve && lastTyped === "liquidate") {
|
if (withdrawReserve && lastTyped === 'liquidate') {
|
||||||
const collateralInQuote = obligation.info.collateralInQuote;
|
const collateralInQuote = obligation.info.collateralInQuote;
|
||||||
const collateral = collateralInQuote / collateralPrice;
|
const collateral = collateralInQuote / collateralPrice;
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -130,29 +117,21 @@ export const LiquidateInput = (props: {
|
||||||
const collateralAmount = (borrowRatio * collateral) / 100;
|
const collateralAmount = (borrowRatio * collateral) / 100;
|
||||||
setCollateralValue(collateralAmount.toString());
|
setCollateralValue(collateralAmount.toString());
|
||||||
} else {
|
} else {
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [borrowAmount, collateralPrice, withdrawReserve, lastTyped, obligation.info.collateralInQuote, value]);
|
||||||
borrowAmount,
|
|
||||||
collateralPrice,
|
|
||||||
withdrawReserve,
|
|
||||||
lastTyped,
|
|
||||||
obligation.info.collateralInQuote,
|
|
||||||
value,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (withdrawReserve && lastTyped === "collateral") {
|
if (withdrawReserve && lastTyped === 'collateral') {
|
||||||
const collateralInQuote = obligation.info.collateralInQuote;
|
const collateralInQuote = obligation.info.collateralInQuote;
|
||||||
const collateral = collateralInQuote / collateralPrice;
|
const collateral = collateralInQuote / collateralPrice;
|
||||||
if (collateralValue) {
|
if (collateralValue) {
|
||||||
const collateralRatio =
|
const collateralRatio = (parseFloat(collateralValue) / collateral) * 100;
|
||||||
(parseFloat(collateralValue) / collateral) * 100;
|
|
||||||
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
||||||
setValue(borrowValue.toString());
|
setValue(borrowValue.toString());
|
||||||
} else {
|
} else {
|
||||||
setValue("");
|
setValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
@ -167,11 +146,11 @@ export const LiquidateInput = (props: {
|
||||||
|
|
||||||
if (!withdrawReserve) return null;
|
if (!withdrawReserve) return null;
|
||||||
const bodyStyle: React.CSSProperties = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -181,27 +160,27 @@ export const LiquidateInput = (props: {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="repay-input-title">{LABELS.LIQUIDATE_QUESTION}</div>
|
<div className='repay-input-title'>{LABELS.LIQUIDATE_QUESTION}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Liquidate Amount"
|
title='Liquidate Amount'
|
||||||
reserve={repayReserve.info}
|
reserve={repayReserve.info}
|
||||||
amount={parseFloat(value) || 0}
|
amount={parseFloat(value) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setValue(val?.toString() || "");
|
setValue(val?.toString() || '');
|
||||||
setLastTyped("liquidate");
|
setLastTyped('liquidate');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
useWalletBalance={true}
|
useWalletBalance={true}
|
||||||
|
@ -210,28 +189,28 @@ export const LiquidateInput = (props: {
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Collateral Amount (estimated)"
|
title='Collateral Amount (estimated)'
|
||||||
reserve={withdrawReserve?.info}
|
reserve={withdrawReserve?.info}
|
||||||
amount={parseFloat(collateralValue) || 0}
|
amount={parseFloat(collateralValue) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setCollateralValue(val?.toString() || "");
|
setCollateralValue(val?.toString() || '');
|
||||||
setLastTyped("collateral");
|
setLastTyped('collateral');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
hideBalance={true}
|
hideBalance={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConnectButton
|
<ConnectButton
|
||||||
type="primary"
|
type='primary'
|
||||||
size="large"
|
size='large'
|
||||||
onClick={onLiquidate}
|
onClick={onLiquidate}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
|
|
|
@ -1,24 +1,13 @@
|
||||||
import { Card, Col, Row, Statistic } from "antd";
|
import { Card, Col, Row, Statistic } from 'antd';
|
||||||
import {
|
import { formatNumber, formatPct, fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
formatNumber,
|
import React, { useMemo } from 'react';
|
||||||
formatPct,
|
import { EnrichedLendingObligation, useLendingReserve } from '../../hooks';
|
||||||
fromLamports,
|
import { useTokenName } from 'common/src/hooks';
|
||||||
wadToLamports,
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
} from "../../utils/utils";
|
import { calculateBorrowAPY, collateralToLiquidity } from '../../models';
|
||||||
import React, { useMemo } from "react";
|
import { GUTTER } from '../../constants';
|
||||||
import {
|
|
||||||
EnrichedLendingObligation,
|
|
||||||
useLendingReserve,
|
|
||||||
useTokenName,
|
|
||||||
} from "../../hooks";
|
|
||||||
import { useMint } from "../../contexts/accounts";
|
|
||||||
import { calculateBorrowAPY, collateralToLiquidity } from "../../models";
|
|
||||||
import { GUTTER } from "../../constants";
|
|
||||||
|
|
||||||
export const LoanInfoLine = (props: {
|
export const LoanInfoLine = (props: { className?: string; obligation: EnrichedLendingObligation }) => {
|
||||||
className?: string;
|
|
||||||
obligation: EnrichedLendingObligation;
|
|
||||||
}) => {
|
|
||||||
const obligation = props.obligation;
|
const obligation = props.obligation;
|
||||||
|
|
||||||
const repayReserve = useLendingReserve(obligation?.info.borrowReserve);
|
const repayReserve = useLendingReserve(obligation?.info.borrowReserve);
|
||||||
|
@ -29,21 +18,12 @@ export const LoanInfoLine = (props: {
|
||||||
const repayName = useTokenName(repayReserve?.info.liquidityMint);
|
const repayName = useTokenName(repayReserve?.info.liquidityMint);
|
||||||
const withdrawName = useTokenName(withdrawReserve?.info.liquidityMint);
|
const withdrawName = useTokenName(withdrawReserve?.info.liquidityMint);
|
||||||
|
|
||||||
const borrowAPY = useMemo(
|
const borrowAPY = useMemo(() => (repayReserve ? calculateBorrowAPY(repayReserve?.info) : 0), [repayReserve]);
|
||||||
() => (repayReserve ? calculateBorrowAPY(repayReserve?.info) : 0),
|
|
||||||
[repayReserve]
|
|
||||||
);
|
|
||||||
if (!obligation || !repayReserve) {
|
if (!obligation || !repayReserve) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const borrowAmount = fromLamports(
|
const borrowAmount = fromLamports(wadToLamports(obligation?.info.borrowAmountWad), liquidityMint);
|
||||||
wadToLamports(obligation?.info.borrowAmountWad),
|
const collateralLamports = collateralToLiquidity(obligation?.info.depositedCollateral, repayReserve.info);
|
||||||
liquidityMint
|
|
||||||
);
|
|
||||||
const collateralLamports = collateralToLiquidity(
|
|
||||||
obligation?.info.depositedCollateral,
|
|
||||||
repayReserve.info
|
|
||||||
);
|
|
||||||
const collateral = fromLamports(collateralLamports, collateralMint);
|
const collateral = fromLamports(collateralLamports, collateralMint);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -51,16 +31,14 @@ export const LoanInfoLine = (props: {
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="Loan Balance"
|
title='Loan Balance'
|
||||||
value={obligation.info.borrowedInQuote}
|
value={obligation.info.borrowedInQuote}
|
||||||
formatter={(val) => (
|
formatter={(val) => (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(borrowAmount)}</em> {repayName}
|
<em>{formatNumber.format(borrowAmount)}</em> {repayName}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(parseFloat(val.toString()))}</div>
|
||||||
${formatNumber.format(parseFloat(val.toString()))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -69,16 +47,14 @@ export const LoanInfoLine = (props: {
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="Collateral"
|
title='Collateral'
|
||||||
value={obligation.info.collateralInQuote}
|
value={obligation.info.collateralInQuote}
|
||||||
formatter={(val) => (
|
formatter={(val) => (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(collateral)}</em> {withdrawName}
|
<em>{formatNumber.format(collateral)}</em> {withdrawName}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(parseFloat(val.toString()))}</div>
|
||||||
${formatNumber.format(parseFloat(val.toString()))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -86,15 +62,12 @@ export const LoanInfoLine = (props: {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic title="APY" value={formatPct.format(borrowAPY)} />
|
<Statistic title='APY' value={formatPct.format(borrowAPY)} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} xl={9}>
|
<Col xs={24} xl={9}>
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
<Statistic
|
<Statistic title='Health Factor' value={obligation.info.health.toFixed(2)} />
|
||||||
title="Health Factor"
|
|
||||||
value={obligation.info.health.toFixed(2)}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { Card, Row, Col } from "antd";
|
import { Card, Row, Col } from 'antd';
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from 'react';
|
||||||
import { useMint } from "../../contexts/accounts";
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
import { useEnrichedPools } from "../../contexts/market";
|
import { useEnrichedPools } from '../../contexts/market';
|
||||||
import { useUserAccounts } from "../../hooks";
|
import { useUserAccounts } from 'common/src/hooks';
|
||||||
import { PoolInfo } from "../../models";
|
import { PoolInfo } from '../../models';
|
||||||
import { formatPriceNumber } from "../../utils/utils";
|
import { formatPriceNumber } from 'common/src/utils/utils';
|
||||||
|
|
||||||
export const PoolPrice = (props: { pool: PoolInfo }) => {
|
export const PoolPrice = (props: { pool: PoolInfo }) => {
|
||||||
const pool = props.pool;
|
const pool = props.pool;
|
||||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [
|
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [props.pool]);
|
||||||
props.pool,
|
|
||||||
]);
|
|
||||||
const enriched = useEnrichedPools(pools)[0];
|
const enriched = useEnrichedPools(pools)[0];
|
||||||
|
|
||||||
const { userAccounts } = useUserAccounts();
|
const { userAccounts } = useUserAccounts();
|
||||||
|
@ -19,37 +17,32 @@ export const PoolPrice = (props: { pool: PoolInfo }) => {
|
||||||
const ratio =
|
const ratio =
|
||||||
userAccounts
|
userAccounts
|
||||||
.filter((f) => pool.pubkeys.mint.equals(f.info.mint))
|
.filter((f) => pool.pubkeys.mint.equals(f.info.mint))
|
||||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) /
|
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) / (lpMint?.supply.toNumber() || 0);
|
||||||
(lpMint?.supply.toNumber() || 0);
|
|
||||||
|
|
||||||
if (!enriched) {
|
if (!enriched) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="ccy-input"
|
className='ccy-input'
|
||||||
style={{ borderRadius: 20, width: "100%" }}
|
style={{ borderRadius: 20, width: '100%' }}
|
||||||
bodyStyle={{ padding: "7px" }}
|
bodyStyle={{ padding: '7px' }}
|
||||||
size="small"
|
size='small'
|
||||||
title="Prices and pool share"
|
title='Prices and pool share'
|
||||||
>
|
>
|
||||||
<Row style={{ width: "100%" }}>
|
<Row style={{ width: '100%' }}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
{formatPriceNumber.format(
|
{formatPriceNumber.format(parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB))}
|
||||||
parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB)
|
|
||||||
)}
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
{formatPriceNumber.format(
|
{formatPriceNumber.format(parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA))}
|
||||||
parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA)
|
|
||||||
)}
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
{ratio * 100 < 0.001 && ratio > 0 ? "<" : ""}
|
{ratio * 100 < 0.001 && ratio > 0 ? '<' : ''}
|
||||||
{formatPriceNumber.format(ratio * 100)}%
|
{formatPriceNumber.format(ratio * 100)}%
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ width: "100%" }}>
|
<Row style={{ width: '100%' }}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
{enriched.names[0]} per {enriched.names[1]}
|
{enriched.names[0]} per {enriched.names[1]}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import {
|
import { EnrichedLendingObligation, InputType, useSliderInput, useUserBalance } from '../../hooks';
|
||||||
EnrichedLendingObligation,
|
import { useAccountByMint } from 'common/src/hooks';
|
||||||
InputType,
|
import { LendingReserve } from '../../models';
|
||||||
useAccountByMint,
|
import { Card, Slider } from 'antd';
|
||||||
useSliderInput,
|
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||||
useUserBalance,
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
} from "../../hooks";
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
import { LendingReserve } from "../../models";
|
import { repay } from '../../actions';
|
||||||
import { Card, Slider } from "antd";
|
import './style.less';
|
||||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
import { LABELS, marks } from '../../constants';
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { ActionConfirmation } from './../ActionConfirmation';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
import { repay } from "../../actions";
|
import { notify } from 'common/src/utils/notifications';
|
||||||
import "./style.less";
|
import { ConnectButton } from '../ConnectButton';
|
||||||
import { LABELS, marks } from "../../constants";
|
import CollateralInput from '../CollateralInput';
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { useMidPriceInUSD } from '../../contexts/market';
|
||||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
|
||||||
import { notify } from "../../utils/notifications";
|
|
||||||
import { ConnectButton } from "../ConnectButton";
|
|
||||||
import CollateralInput from "../CollateralInput";
|
|
||||||
import { useMidPriceInUSD } from "../../contexts/market";
|
|
||||||
|
|
||||||
export const RepayInput = (props: {
|
export const RepayInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -29,36 +24,30 @@ export const RepayInput = (props: {
|
||||||
}) => {
|
}) => {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const [lastTyped, setLastTyped] = useState("repay");
|
const [lastTyped, setLastTyped] = useState('repay');
|
||||||
const [pendingTx, setPendingTx] = useState(false);
|
const [pendingTx, setPendingTx] = useState(false);
|
||||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||||
const [collateralValue, setCollateralValue] = useState("");
|
const [collateralValue, setCollateralValue] = useState('');
|
||||||
|
|
||||||
const repayReserve = props.borrowReserve;
|
const repayReserve = props.borrowReserve;
|
||||||
const obligation = props.obligation;
|
const obligation = props.obligation;
|
||||||
|
|
||||||
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
||||||
const { balance: tokenBalance } = useUserBalance(
|
const { balance: tokenBalance } = useUserBalance(repayReserve.info.liquidityMint);
|
||||||
repayReserve.info.liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const borrowAmountLamports = wadToLamports(
|
const borrowAmountLamports = wadToLamports(obligation.info.borrowAmountWad).toNumber();
|
||||||
obligation.info.borrowAmountWad
|
|
||||||
).toNumber();
|
|
||||||
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
||||||
const collateralReserve = props.collateralReserve;
|
const collateralReserve = props.collateralReserve;
|
||||||
|
|
||||||
const { accounts: fromAccounts } = useUserBalance(
|
const { accounts: fromAccounts } = useUserBalance(repayReserve.info.liquidityMint);
|
||||||
repayReserve.info.liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const obligationAccount = useAccountByMint(obligation?.info.tokenMint);
|
const obligationAccount = useAccountByMint(obligation?.info.tokenMint);
|
||||||
|
|
||||||
const convert = useCallback(
|
const convert = useCallback(
|
||||||
(val: string | number) => {
|
(val: string | number) => {
|
||||||
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
||||||
setLastTyped("repay");
|
setLastTyped('repay');
|
||||||
if (typeof val === "string") {
|
if (typeof val === 'string') {
|
||||||
return (parseFloat(val) / minAmount) * 100;
|
return (parseFloat(val) / minAmount) * 100;
|
||||||
} else {
|
} else {
|
||||||
return (val * minAmount) / 100;
|
return (val * minAmount) / 100;
|
||||||
|
@ -70,12 +59,7 @@ export const RepayInput = (props: {
|
||||||
const { value, setValue, pct, setPct, type } = useSliderInput(convert);
|
const { value, setValue, pct, setPct, type } = useSliderInput(convert);
|
||||||
|
|
||||||
const onRepay = useCallback(() => {
|
const onRepay = useCallback(() => {
|
||||||
if (
|
if (!collateralReserve || !obligation || !repayReserve || !obligationAccount) {
|
||||||
!collateralReserve ||
|
|
||||||
!obligation ||
|
|
||||||
!repayReserve ||
|
|
||||||
!obligationAccount
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +70,7 @@ export const RepayInput = (props: {
|
||||||
const toRepayLamports =
|
const toRepayLamports =
|
||||||
type === InputType.Percent
|
type === InputType.Percent
|
||||||
? (pct * borrowAmountLamports) / 100
|
? (pct * borrowAmountLamports) / 100
|
||||||
: Math.ceil(
|
: Math.ceil(borrowAmountLamports * (parseFloat(value) / borrowAmount));
|
||||||
borrowAmountLamports * (parseFloat(value) / borrowAmount)
|
|
||||||
);
|
|
||||||
await repay(
|
await repay(
|
||||||
fromAccounts[0],
|
fromAccounts[0],
|
||||||
toRepayLamports,
|
toRepayLamports,
|
||||||
|
@ -100,13 +82,13 @@ export const RepayInput = (props: {
|
||||||
wallet
|
wallet
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("");
|
setValue('');
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
setShowConfirmation(true);
|
setShowConfirmation(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify({
|
notify({
|
||||||
message: "Unable to repay loan.",
|
message: 'Unable to repay loan.',
|
||||||
type: "error",
|
type: 'error',
|
||||||
description: error.message,
|
description: error.message,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -129,12 +111,10 @@ export const RepayInput = (props: {
|
||||||
wallet,
|
wallet,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const collateralPrice = useMidPriceInUSD(
|
const collateralPrice = useMidPriceInUSD(collateralReserve?.info.liquidityMint.toBase58())?.price;
|
||||||
collateralReserve?.info.liquidityMint.toBase58()
|
|
||||||
)?.price;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collateralReserve && lastTyped === "repay") {
|
if (collateralReserve && lastTyped === 'repay') {
|
||||||
const collateralInQuote = obligation.info.collateralInQuote;
|
const collateralInQuote = obligation.info.collateralInQuote;
|
||||||
const collateral = collateralInQuote / collateralPrice;
|
const collateral = collateralInQuote / collateralPrice;
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -142,29 +122,21 @@ export const RepayInput = (props: {
|
||||||
const collateralAmount = (borrowRatio * collateral) / 100;
|
const collateralAmount = (borrowRatio * collateral) / 100;
|
||||||
setCollateralValue(collateralAmount.toString());
|
setCollateralValue(collateralAmount.toString());
|
||||||
} else {
|
} else {
|
||||||
setCollateralValue("");
|
setCollateralValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [borrowAmount, collateralPrice, collateralReserve, lastTyped, obligation.info.collateralInQuote, value]);
|
||||||
borrowAmount,
|
|
||||||
collateralPrice,
|
|
||||||
collateralReserve,
|
|
||||||
lastTyped,
|
|
||||||
obligation.info.collateralInQuote,
|
|
||||||
value,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collateralReserve && lastTyped === "collateral") {
|
if (collateralReserve && lastTyped === 'collateral') {
|
||||||
const collateralInQuote = obligation.info.collateralInQuote;
|
const collateralInQuote = obligation.info.collateralInQuote;
|
||||||
const collateral = collateralInQuote / collateralPrice;
|
const collateral = collateralInQuote / collateralPrice;
|
||||||
if (collateralValue) {
|
if (collateralValue) {
|
||||||
const collateralRatio =
|
const collateralRatio = (parseFloat(collateralValue) / collateral) * 100;
|
||||||
(parseFloat(collateralValue) / collateral) * 100;
|
|
||||||
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
||||||
setValue(borrowValue.toString());
|
setValue(borrowValue.toString());
|
||||||
} else {
|
} else {
|
||||||
setValue("");
|
setValue('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
@ -178,11 +150,11 @@ export const RepayInput = (props: {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bodyStyle: React.CSSProperties = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -192,27 +164,27 @@ export const RepayInput = (props: {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="repay-input-title">{LABELS.REPAY_QUESTION}</div>
|
<div className='repay-input-title'>{LABELS.REPAY_QUESTION}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Repay Amount"
|
title='Repay Amount'
|
||||||
reserve={repayReserve.info}
|
reserve={repayReserve.info}
|
||||||
amount={parseFloat(value) || 0}
|
amount={parseFloat(value) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setValue(val?.toString() || "");
|
setValue(val?.toString() || '');
|
||||||
setLastTyped("repay");
|
setLastTyped('repay');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
useWalletBalance={true}
|
useWalletBalance={true}
|
||||||
|
@ -221,28 +193,28 @@ export const RepayInput = (props: {
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Collateral Amount (estimated)"
|
title='Collateral Amount (estimated)'
|
||||||
reserve={collateralReserve?.info}
|
reserve={collateralReserve?.info}
|
||||||
amount={parseFloat(collateralValue) || 0}
|
amount={parseFloat(collateralValue) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setCollateralValue(val?.toString() || "");
|
setCollateralValue(val?.toString() || '');
|
||||||
setLastTyped("collateral");
|
setLastTyped('collateral');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
hideBalance={true}
|
hideBalance={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConnectButton
|
<ConnectButton
|
||||||
type="primary"
|
type='primary'
|
||||||
size="large"
|
size='large'
|
||||||
onClick={onRepay}
|
onClick={onRepay}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
|
|
|
@ -1,51 +1,38 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
|
import { calculateDepositAPY, LendingReserve } from '../../models/lending';
|
||||||
import { Card, Col, Row, Statistic } from "antd";
|
import { Card, Col, Row, Statistic } from 'antd';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import "./style.less";
|
import './style.less';
|
||||||
import { GUTTER, LABELS } from "../../constants";
|
import { GUTTER, LABELS } from '../../constants';
|
||||||
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
|
import { ReserveUtilizationChart } from './../../components/ReserveUtilizationChart';
|
||||||
import { useMemo } from "react";
|
import { useMemo } from 'react';
|
||||||
import { formatNumber, fromLamports, wadToLamports } from "../../utils/utils";
|
import { formatNumber, fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
import { useMint } from "../../contexts/accounts";
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
import { useMidPriceInUSD } from "../../contexts/market";
|
import { useMidPriceInUSD } from '../../contexts/market';
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from '../TokenIcon';
|
||||||
|
|
||||||
export const ReserveStatus = (props: {
|
export const ReserveStatus = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||||
className?: string;
|
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const bodyStyle: React.CSSProperties = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
||||||
const liquidityMint = useMint(mintAddress);
|
const liquidityMint = useMint(mintAddress);
|
||||||
const { price } = useMidPriceInUSD(mintAddress);
|
const { price } = useMidPriceInUSD(mintAddress);
|
||||||
const availableLiquidity = fromLamports(
|
const availableLiquidity = fromLamports(props.reserve.state.availableLiquidity, liquidityMint);
|
||||||
props.reserve.state.availableLiquidity,
|
|
||||||
liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const availableLiquidityInUSD = price * availableLiquidity;
|
const availableLiquidityInUSD = price * availableLiquidity;
|
||||||
|
|
||||||
const totalBorrows = useMemo(
|
const totalBorrows = useMemo(
|
||||||
() =>
|
() => fromLamports(wadToLamports(props.reserve.state.borrowedLiquidityWad), liquidityMint),
|
||||||
fromLamports(
|
|
||||||
wadToLamports(props.reserve.state.borrowedLiquidityWad),
|
|
||||||
liquidityMint
|
|
||||||
),
|
|
||||||
[props.reserve, liquidityMint]
|
[props.reserve, liquidityMint]
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalBorrowsInUSD = price * totalBorrows;
|
const totalBorrowsInUSD = price * totalBorrows;
|
||||||
|
|
||||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
|
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [props.reserve]);
|
||||||
props.reserve,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const liquidationThreshold = props.reserve.config.liquidationThreshold;
|
const liquidationThreshold = props.reserve.config.liquidationThreshold;
|
||||||
const liquidationPenalty = props.reserve.config.liquidationBonus;
|
const liquidationPenalty = props.reserve.config.liquidationBonus;
|
||||||
|
@ -60,7 +47,7 @@ export const ReserveStatus = (props: {
|
||||||
style={{
|
style={{
|
||||||
marginRight: 0,
|
marginRight: 0,
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
left: 15,
|
left: 15,
|
||||||
}}
|
}}
|
||||||
mintAddress={mintAddress}
|
mintAddress={mintAddress}
|
||||||
|
@ -71,18 +58,16 @@ export const ReserveStatus = (props: {
|
||||||
}
|
}
|
||||||
bodyStyle={bodyStyle}
|
bodyStyle={bodyStyle}
|
||||||
>
|
>
|
||||||
<div className="flexColumn">
|
<div className='flexColumn'>
|
||||||
<Row gutter={GUTTER}>
|
<Row gutter={GUTTER}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="Available Liquidity"
|
title='Available Liquidity'
|
||||||
value={availableLiquidity}
|
value={availableLiquidity}
|
||||||
valueRender={(node) => (
|
valueRender={(node) => (
|
||||||
<div>
|
<div>
|
||||||
{node}
|
{node}
|
||||||
<div className="dashboard-amount-quote-stat">
|
<div className='dashboard-amount-quote-stat'>${formatNumber.format(availableLiquidityInUSD)}</div>
|
||||||
${formatNumber.format(availableLiquidityInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
precision={2}
|
precision={2}
|
||||||
|
@ -90,14 +75,12 @@ export const ReserveStatus = (props: {
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="Total Borrowed"
|
title='Total Borrowed'
|
||||||
value={totalBorrows}
|
value={totalBorrows}
|
||||||
valueRender={(node) => (
|
valueRender={(node) => (
|
||||||
<div>
|
<div>
|
||||||
{node}
|
{node}
|
||||||
<div className="dashboard-amount-quote-stat">
|
<div className='dashboard-amount-quote-stat'>${formatNumber.format(totalBorrowsInUSD)}</div>
|
||||||
${formatNumber.format(totalBorrowsInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
precision={2}
|
precision={2}
|
||||||
|
@ -108,9 +91,9 @@ export const ReserveStatus = (props: {
|
||||||
<Col
|
<Col
|
||||||
span={24}
|
span={24}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReserveUtilizationChart reserve={props.reserve} />
|
<ReserveUtilizationChart reserve={props.reserve} />
|
||||||
|
@ -118,39 +101,33 @@ export const ReserveStatus = (props: {
|
||||||
</Row>
|
</Row>
|
||||||
<Row gutter={GUTTER}>
|
<Row gutter={GUTTER}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Statistic
|
<Statistic title={LABELS.MAX_LTV} className='small-statisitc' value={maxLTV} precision={2} suffix='%' />
|
||||||
title={LABELS.MAX_LTV}
|
|
||||||
className="small-statisitc"
|
|
||||||
value={maxLTV}
|
|
||||||
precision={2}
|
|
||||||
suffix="%"
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={LABELS.LIQUIDATION_THRESHOLD}
|
title={LABELS.LIQUIDATION_THRESHOLD}
|
||||||
className="small-statisitc"
|
className='small-statisitc'
|
||||||
value={liquidationThreshold}
|
value={liquidationThreshold}
|
||||||
precision={2}
|
precision={2}
|
||||||
suffix="%"
|
suffix='%'
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={LABELS.LIQUIDATION_PENALTY}
|
title={LABELS.LIQUIDATION_PENALTY}
|
||||||
className="small-statisitc"
|
className='small-statisitc'
|
||||||
value={liquidationPenalty}
|
value={liquidationPenalty}
|
||||||
precision={2}
|
precision={2}
|
||||||
suffix="%"
|
suffix='%'
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={LABELS.TABLE_TITLE_DEPOSIT_APY}
|
title={LABELS.TABLE_TITLE_DEPOSIT_APY}
|
||||||
className="small-statisitc"
|
className='small-statisitc'
|
||||||
value={depositAPY * 100}
|
value={depositAPY * 100}
|
||||||
precision={2}
|
precision={2}
|
||||||
suffix="%"
|
suffix='%'
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from 'react';
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { LendingReserve } from '../../models/lending';
|
||||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
import { useMint } from "../../contexts/accounts";
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
import { WaterWave } from "./../WaterWave";
|
import { WaterWave } from './../WaterWave';
|
||||||
import { Statistic } from "antd";
|
import { Statistic } from 'antd';
|
||||||
|
|
||||||
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
||||||
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
const mintAddress = props.reserve.liquidityMint?.toBase58();
|
||||||
const liquidityMint = useMint(mintAddress);
|
const liquidityMint = useMint(mintAddress);
|
||||||
const availableLiquidity = fromLamports(
|
const availableLiquidity = fromLamports(props.reserve.state.availableLiquidity, liquidityMint);
|
||||||
props.reserve.state.availableLiquidity,
|
|
||||||
liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalBorrows = useMemo(
|
const totalBorrows = useMemo(
|
||||||
() =>
|
() => fromLamports(wadToLamports(props.reserve.state.borrowedLiquidityWad), liquidityMint),
|
||||||
fromLamports(
|
|
||||||
wadToLamports(props.reserve.state.borrowedLiquidityWad),
|
|
||||||
liquidityMint
|
|
||||||
),
|
|
||||||
[props.reserve, liquidityMint]
|
[props.reserve, liquidityMint]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -29,14 +22,7 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
||||||
<WaterWave
|
<WaterWave
|
||||||
style={{ height: 300 }}
|
style={{ height: 300 }}
|
||||||
showPercent={false}
|
showPercent={false}
|
||||||
title={
|
title={<Statistic title='Utilization' value={percent} suffix='%' precision={2} />}
|
||||||
<Statistic
|
|
||||||
title="Utilization"
|
|
||||||
value={percent}
|
|
||||||
suffix="%"
|
|
||||||
precision={2}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
percent={percent}
|
percent={percent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Select } from "antd";
|
import { Select } from 'antd';
|
||||||
import { ENDPOINTS, useConnectionConfig } from "../../contexts/connection";
|
import { ENDPOINTS, useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { useWallet, WALLET_PROVIDERS } from "../../contexts/wallet";
|
import { useWallet, WALLET_PROVIDERS } from 'common/src/contexts/wallet';
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const { providerUrl, setProvider } = useWallet();
|
const { providerUrl, setProvider } = useWallet();
|
||||||
|
@ -9,13 +9,9 @@ export const Settings = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: "grid" }}>
|
<div style={{ display: 'grid' }}>
|
||||||
Network:{" "}
|
Network:{' '}
|
||||||
<Select
|
<Select onSelect={setEndpoint} value={endpoint} style={{ marginRight: 8 }}>
|
||||||
onSelect={setEndpoint}
|
|
||||||
value={endpoint}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
>
|
|
||||||
{ENDPOINTS.map(({ name, endpoint }) => (
|
{ENDPOINTS.map(({ name, endpoint }) => (
|
||||||
<Select.Option value={endpoint} key={endpoint}>
|
<Select.Option value={endpoint} key={endpoint}>
|
||||||
{name}
|
{name}
|
||||||
|
@ -23,8 +19,8 @@ export const Settings = () => {
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "grid" }}>
|
<div style={{ display: 'grid' }}>
|
||||||
Wallet:{" "}
|
Wallet:{' '}
|
||||||
<Select onSelect={setProvider} value={providerUrl}>
|
<Select onSelect={setProvider} value={providerUrl}>
|
||||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||||
<Select.Option value={url} key={url}>
|
<Select.Option value={url} key={url}>
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useTokenName } from "./../../hooks";
|
import { useTokenName } from 'common/src/hooks';
|
||||||
import {
|
import {
|
||||||
calculateBorrowAPY,
|
calculateBorrowAPY,
|
||||||
calculateDepositAPY,
|
calculateDepositAPY,
|
||||||
calculateUtilizationRatio,
|
calculateUtilizationRatio,
|
||||||
LendingReserve,
|
LendingReserve,
|
||||||
} from "../../models/lending";
|
} from '../../models/lending';
|
||||||
import { TokenIcon } from "../../components/TokenIcon";
|
import { TokenIcon } from '../../components/TokenIcon';
|
||||||
import { formatNumber, formatPct, fromLamports } from "../../utils/utils";
|
import { formatNumber, formatPct, fromLamports } from 'common/src/utils/utils';
|
||||||
import { Card, Typography } from "antd";
|
import { Card, Typography } from 'antd';
|
||||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
import { ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import { LABELS } from "../../constants";
|
import { LABELS } from '../../constants';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export enum SideReserveOverviewMode {
|
export enum SideReserveOverviewMode {
|
||||||
Deposit = "deposit",
|
Deposit = 'deposit',
|
||||||
Borrow = "borrow",
|
Borrow = 'borrow',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SideReserveOverview = (props: {
|
export const SideReserveOverview = (props: {
|
||||||
|
@ -30,10 +30,7 @@ export const SideReserveOverview = (props: {
|
||||||
const name = useTokenName(reserve?.liquidityMint);
|
const name = useTokenName(reserve?.liquidityMint);
|
||||||
const liquidityMint = useMint(reserve.liquidityMint);
|
const liquidityMint = useMint(reserve.liquidityMint);
|
||||||
|
|
||||||
const availableLiquidity = fromLamports(
|
const availableLiquidity = fromLamports(reserve.state.availableLiquidity, liquidityMint);
|
||||||
reserve.state.availableLiquidity,
|
|
||||||
liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const depositApy = calculateDepositAPY(reserve);
|
const depositApy = calculateDepositAPY(reserve);
|
||||||
const borrowApr = calculateBorrowAPY(reserve);
|
const borrowApr = calculateBorrowAPY(reserve);
|
||||||
|
@ -47,47 +44,43 @@ export const SideReserveOverview = (props: {
|
||||||
if (mode === SideReserveOverviewMode.Deposit) {
|
if (mode === SideReserveOverviewMode.Deposit) {
|
||||||
extraInfo = (
|
extraInfo = (
|
||||||
<>
|
<>
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
{LABELS.TABLE_TITLE_DEPOSIT_APY}:
|
{LABELS.TABLE_TITLE_DEPOSIT_APY}:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{formatPct.format(depositApy)}</div>
|
<div className='card-cell '>{formatPct.format(depositApy)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Maximum LTV:
|
Maximum LTV:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{formatPct.format(maxLTV)}</div>
|
<div className='card-cell '>{formatPct.format(maxLTV)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Liquidation threshold:
|
Liquidation threshold:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>{formatPct.format(liquidationThreshold)}</div>
|
||||||
{formatPct.format(liquidationThreshold)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Liquidation penalty:
|
Liquidation penalty:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>{formatPct.format(liquidationPenalty)}</div>
|
||||||
{formatPct.format(liquidationPenalty)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (mode === SideReserveOverviewMode.Borrow) {
|
} else if (mode === SideReserveOverviewMode.Borrow) {
|
||||||
extraInfo = (
|
extraInfo = (
|
||||||
<>
|
<>
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
{LABELS.TABLE_TITLE_BORROW_APY}:
|
{LABELS.TABLE_TITLE_BORROW_APY}:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{formatPct.format(borrowApr)}</div>
|
<div className='card-cell '>{formatPct.format(borrowApr)}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -99,34 +92,30 @@ export const SideReserveOverview = (props: {
|
||||||
title={
|
title={
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
fontSize: "1.2rem",
|
fontSize: '1.2rem',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link to={`/reserve/${props.reserve.pubkey}`}>
|
<Link to={`/reserve/${props.reserve.pubkey}`}>
|
||||||
<TokenIcon
|
<TokenIcon mintAddress={reserve?.liquidityMint} style={{ width: 30, height: 30 }} /> {name} Reserve Overview
|
||||||
mintAddress={reserve?.liquidityMint}
|
|
||||||
style={{ width: 30, height: 30 }}
|
|
||||||
/>{" "}
|
|
||||||
{name} Reserve Overview
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Utilization rate:
|
Utilization rate:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{formatPct.format(utilizationRate)}</div>
|
<div className='card-cell '>{formatPct.format(utilizationRate)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Available liquidity:
|
Available liquidity:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>
|
||||||
{formatNumber.format(availableLiquidity)} {name}
|
{formatNumber.format(availableLiquidity)} {name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
import { PoolInfo } from "../../models";
|
import { PoolInfo } from '../../models';
|
||||||
import echarts from "echarts";
|
import echarts from 'echarts';
|
||||||
import { formatNumber, formatUSD } from "../../utils/utils";
|
import { formatNumber, formatUSD } from 'common/src/utils/utils';
|
||||||
import { useEnrichedPools } from "../../contexts/market";
|
import { useEnrichedPools } from '../../contexts/market';
|
||||||
|
|
||||||
export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||||
const { pool } = props;
|
const { pool } = props;
|
||||||
|
@ -44,7 +44,7 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||||
|
|
||||||
instance.setOption({
|
instance.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: 'item',
|
||||||
formatter: function (params: any) {
|
formatter: function (params: any) {
|
||||||
var val = formatUSD.format(params.value);
|
var val = formatUSD.format(params.value);
|
||||||
var tokenAmount = formatNumber.format(params.data.tokens);
|
var tokenAmount = formatNumber.format(params.data.tokens);
|
||||||
|
@ -53,8 +53,8 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "Liquidity",
|
name: 'Liquidity',
|
||||||
type: "pie",
|
type: 'pie',
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
@ -70,20 +70,20 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||||
},
|
},
|
||||||
rich: {
|
rich: {
|
||||||
c: {
|
c: {
|
||||||
color: "black",
|
color: 'black',
|
||||||
lineHeight: 22,
|
lineHeight: 22,
|
||||||
align: "center",
|
align: 'center',
|
||||||
},
|
},
|
||||||
r: {
|
r: {
|
||||||
color: "black",
|
color: 'black',
|
||||||
align: "right",
|
align: 'right',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
color: "rgba(255, 255, 255, 0.5)",
|
color: 'rgba(255, 255, 255, 0.5)',
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
normal: {
|
normal: {
|
||||||
borderColor: "#000",
|
borderColor: '#000',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
|
@ -96,5 +96,5 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div ref={chartDiv} style={{ height: 150, width: "100%" }} />;
|
return <div ref={chartDiv} style={{ height: 150, width: '100%' }} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useMint, useAccountByMint } from "../../contexts/accounts";
|
import { useMint, useAccountByMint } from 'common/src/contexts/accounts';
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from '../TokenIcon';
|
||||||
|
|
||||||
export const TokenDisplay = (props: {
|
export const TokenDisplay = (props: {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -16,8 +16,7 @@ export const TokenDisplay = (props: {
|
||||||
let hasBalance: boolean = false;
|
let hasBalance: boolean = false;
|
||||||
if (showBalance) {
|
if (showBalance) {
|
||||||
if (tokenAccount && tokenMint) {
|
if (tokenAccount && tokenMint) {
|
||||||
balance =
|
balance = tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||||
tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
|
||||||
hasBalance = balance > 0;
|
hasBalance = balance > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,27 +27,18 @@ export const TokenDisplay = (props: {
|
||||||
title={mintAddress}
|
title={mintAddress}
|
||||||
key={mintAddress}
|
key={mintAddress}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
{icon || <TokenIcon mintAddress={mintAddress} />}
|
{icon || <TokenIcon mintAddress={mintAddress} />}
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
{showBalance ? (
|
{showBalance ? (
|
||||||
<span
|
<span title={balance.toString()} key={mintAddress} className='token-balance'>
|
||||||
title={balance.toString()}
|
{hasBalance ? (balance < 0.001 ? '<0.001' : balance.toFixed(3)) : '-'}
|
||||||
key={mintAddress}
|
|
||||||
className="token-balance"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{hasBalance
|
|
||||||
? balance < 0.001
|
|
||||||
? "<0.001"
|
|
||||||
: balance.toFixed(3)
|
|
||||||
: "-"}
|
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Identicon } from "../Identicon";
|
import { Identicon } from '../Identicon';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { getTokenIcon } from "../../utils/utils";
|
import { getTokenIcon } from 'common/src/utils/utils';
|
||||||
import { useConnectionConfig } from "../../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
|
||||||
export const TokenIcon = (props: {
|
export const TokenIcon = (props: {
|
||||||
mintAddress?: string | PublicKey;
|
mintAddress?: string | PublicKey;
|
||||||
|
@ -18,18 +18,18 @@ export const TokenIcon = (props: {
|
||||||
if (icon) {
|
if (icon) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt="Token icon"
|
alt='Token icon'
|
||||||
className={props.className}
|
className={props.className}
|
||||||
key={icon}
|
key={icon}
|
||||||
width={props.style?.width || size.toString()}
|
width={props.style?.width || size.toString()}
|
||||||
height={props.style?.height || size.toString()}
|
height={props.style?.height || size.toString()}
|
||||||
src={icon}
|
src={icon}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "0.5rem",
|
marginRight: '0.5rem',
|
||||||
marginTop: "0.11rem",
|
marginTop: '0.11rem',
|
||||||
borderRadius: "10rem",
|
borderRadius: '10rem',
|
||||||
backgroundColor: "white",
|
backgroundColor: 'white',
|
||||||
backgroundClip: "padding-box",
|
backgroundClip: 'padding-box',
|
||||||
...props.style,
|
...props.style,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -40,7 +40,7 @@ export const TokenIcon = (props: {
|
||||||
<Identicon
|
<Identicon
|
||||||
address={props.mintAddress}
|
address={props.mintAddress}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "0.5rem",
|
marginRight: '0.5rem',
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
|
@ -50,18 +50,10 @@ export const TokenIcon = (props: {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PoolIcon = (props: {
|
export const PoolIcon = (props: { mintA: string; mintB: string; style?: React.CSSProperties; className?: string }) => {
|
||||||
mintA: string;
|
|
||||||
mintB: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
className?: string;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={props.className} style={{ display: "flex" }}>
|
<div className={props.className} style={{ display: 'flex' }}>
|
||||||
<TokenIcon
|
<TokenIcon mintAddress={props.mintA} style={{ marginRight: '-0.5rem', ...props.style }} />
|
||||||
mintAddress={props.mintA}
|
|
||||||
style={{ marginRight: "-0.5rem", ...props.style }}
|
|
||||||
/>
|
|
||||||
<TokenIcon mintAddress={props.mintB} />
|
<TokenIcon mintAddress={props.mintB} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,50 +1,32 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
useUserCollateralBalance,
|
useUserCollateralBalance,
|
||||||
useTokenName,
|
|
||||||
useUserBalance,
|
useUserBalance,
|
||||||
useBorrowedAmount,
|
useBorrowedAmount,
|
||||||
useBorrowingPower,
|
useBorrowingPower,
|
||||||
useUserObligationByReserve,
|
useUserObligationByReserve,
|
||||||
} from "./../../hooks";
|
} from './../../hooks';
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { useTokenName } from 'common/src/hooks';
|
||||||
import { formatNumber } from "../../utils/utils";
|
import { LendingReserve } from '../../models/lending';
|
||||||
import { Button, Card, Typography } from "antd";
|
import { formatNumber } from 'common/src/utils/utils';
|
||||||
import { Link } from "react-router-dom";
|
import { Button, Card, Typography } from 'antd';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { Link } from 'react-router-dom';
|
||||||
import { LABELS } from "../../constants";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { LABELS } from '../../constants';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const UserLendingCard = (props: {
|
export const UserLendingCard = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||||
className?: string;
|
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const reserve = props.reserve;
|
const reserve = props.reserve;
|
||||||
const address = props.address;
|
const address = props.address;
|
||||||
|
|
||||||
const name = useTokenName(reserve?.liquidityMint);
|
const name = useTokenName(reserve?.liquidityMint);
|
||||||
|
|
||||||
const {
|
const { balance: tokenBalance, balanceInUSD: tokenBalanceInUSD } = useUserBalance(props.reserve.liquidityMint);
|
||||||
balance: tokenBalance,
|
const { balance: collateralBalance, balanceInUSD: collateralBalanceInUSD } = useUserCollateralBalance(props.reserve);
|
||||||
balanceInUSD: tokenBalanceInUSD,
|
|
||||||
} = useUserBalance(props.reserve.liquidityMint);
|
|
||||||
const {
|
|
||||||
balance: collateralBalance,
|
|
||||||
balanceInUSD: collateralBalanceInUSD,
|
|
||||||
} = useUserCollateralBalance(props.reserve);
|
|
||||||
|
|
||||||
const {
|
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
|
||||||
borrowed: totalBorrowed,
|
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
|
||||||
borrowedInUSD,
|
|
||||||
ltv,
|
|
||||||
health,
|
|
||||||
} = useBorrowedAmount(address);
|
|
||||||
const {
|
|
||||||
totalInQuote: borrowingPowerInUSD,
|
|
||||||
borrowingPower,
|
|
||||||
} = useBorrowingPower(address);
|
|
||||||
const { userObligationsByReserve } = useUserObligationByReserve(address);
|
const { userObligationsByReserve } = useUserObligationByReserve(address);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -53,10 +35,10 @@ export const UserLendingCard = (props: {
|
||||||
title={
|
title={
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
fontSize: "1.2rem",
|
fontSize: '1.2rem',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Your Information
|
Your Information
|
||||||
|
@ -65,90 +47,79 @@ export const UserLendingCard = (props: {
|
||||||
>
|
>
|
||||||
<h3>{LABELS.BORROWS}</h3>
|
<h3>{LABELS.BORROWS}</h3>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Borrowed
|
Borrowed
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(totalBorrowed)}</em> {name}
|
<em>{formatNumber.format(totalBorrowed)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(borrowedInUSD)}</div>
|
||||||
${formatNumber.format(borrowedInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
{LABELS.TABLE_TITLE_HEALTH}:
|
{LABELS.TABLE_TITLE_HEALTH}:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{health.toFixed(2)}</div>
|
<div className='card-cell '>{health.toFixed(2)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
{LABELS.LOAN_TO_VALUE}:
|
{LABELS.LOAN_TO_VALUE}:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">{formatNumber.format(ltv)}</div>
|
<div className='card-cell '>{formatNumber.format(ltv)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
Available to you:
|
Available to you:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(borrowingPowerInUSD)}</div>
|
||||||
${formatNumber.format(borrowingPowerInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>{LABELS.DEPOSITS}</h3>
|
<h3>{LABELS.DEPOSITS}</h3>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
{LABELS.WALLET_BALANCE}:
|
{LABELS.WALLET_BALANCE}:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(tokenBalance)}</em> {name}
|
<em>{formatNumber.format(tokenBalance)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(tokenBalanceInUSD)}</div>
|
||||||
${formatNumber.format(tokenBalanceInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-row">
|
<div className='card-row'>
|
||||||
<Text type="secondary" className="card-cell ">
|
<Text type='secondary' className='card-cell '>
|
||||||
You already deposited:
|
You already deposited:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<div className='card-cell '>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(collateralBalance)}</em> {name}
|
<em>{formatNumber.format(collateralBalance)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(collateralBalanceInUSD)}</div>
|
||||||
${formatNumber.format(collateralBalanceInUSD)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className='card-row' style={{ marginTop: 20, justifyContent: 'space-evenly' }}>
|
||||||
className="card-row"
|
|
||||||
style={{ marginTop: 20, justifyContent: "space-evenly" }}
|
|
||||||
>
|
|
||||||
<Link to={`/deposit/${address}`}>
|
<Link to={`/deposit/${address}`}>
|
||||||
<Button>{LABELS.DEPOSIT_ACTION}</Button>
|
<Button>{LABELS.DEPOSIT_ACTION}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -159,9 +130,7 @@ export const UserLendingCard = (props: {
|
||||||
<Button>{LABELS.WITHDRAW_ACTION}</Button>
|
<Button>{LABELS.WITHDRAW_ACTION}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
{!!userObligationsByReserve.length && (
|
{!!userObligationsByReserve.length && (
|
||||||
<Link
|
<Link to={`/repay/loan/${userObligationsByReserve[0].obligation.account.pubkey.toBase58()}`}>
|
||||||
to={`/repay/loan/${userObligationsByReserve[0].obligation.account.pubkey.toBase58()}`}
|
|
||||||
>
|
|
||||||
<Button>{LABELS.REPAY_ACTION}</Button>
|
<Button>{LABELS.REPAY_ACTION}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from 'react';
|
||||||
import {
|
import { InputType, useUserCollateralBalance, useSliderInput, useUserBalance } from '../../hooks';
|
||||||
InputType,
|
import { LendingReserve } from '../../models/lending';
|
||||||
useUserCollateralBalance,
|
import { Card, Slider } from 'antd';
|
||||||
useSliderInput,
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
useUserBalance,
|
import { useWallet } from 'common/src/contexts/wallet';
|
||||||
} from "../../hooks";
|
import { withdraw } from '../../actions';
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { Card, Slider } from "antd";
|
import './style.less';
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { LABELS, marks } from '../../constants';
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { ActionConfirmation } from './../ActionConfirmation';
|
||||||
import { withdraw } from "../../actions";
|
import { ConnectButton } from '../ConnectButton';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import CollateralInput from '../CollateralInput';
|
||||||
import "./style.less";
|
|
||||||
import { LABELS, marks } from "../../constants";
|
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
|
||||||
import { ConnectButton } from "../ConnectButton";
|
|
||||||
import CollateralInput from "../CollateralInput";
|
|
||||||
|
|
||||||
export const WithdrawInput = (props: {
|
export const WithdrawInput = (props: { className?: string; reserve: LendingReserve; address: PublicKey }) => {
|
||||||
className?: string;
|
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const [pendingTx, setPendingTx] = useState(false);
|
const [pendingTx, setPendingTx] = useState(false);
|
||||||
|
@ -30,17 +21,14 @@ export const WithdrawInput = (props: {
|
||||||
const reserve = props.reserve;
|
const reserve = props.reserve;
|
||||||
const address = props.address;
|
const address = props.address;
|
||||||
|
|
||||||
const {
|
const { balanceLamports: collateralBalanceLamports, accounts: fromAccounts } = useUserBalance(
|
||||||
balanceLamports: collateralBalanceLamports,
|
reserve?.collateralMint
|
||||||
accounts: fromAccounts,
|
|
||||||
} = useUserBalance(reserve?.collateralMint);
|
|
||||||
const { balance: collateralBalanceInLiquidity } = useUserCollateralBalance(
|
|
||||||
reserve
|
|
||||||
);
|
);
|
||||||
|
const { balance: collateralBalanceInLiquidity } = useUserCollateralBalance(reserve);
|
||||||
|
|
||||||
const convert = useCallback(
|
const convert = useCallback(
|
||||||
(val: string | number) => {
|
(val: string | number) => {
|
||||||
if (typeof val === "string") {
|
if (typeof val === 'string') {
|
||||||
return (parseFloat(val) / collateralBalanceInLiquidity) * 100;
|
return (parseFloat(val) / collateralBalanceInLiquidity) * 100;
|
||||||
} else {
|
} else {
|
||||||
return (val * collateralBalanceInLiquidity) / 100;
|
return (val * collateralBalanceInLiquidity) / 100;
|
||||||
|
@ -59,22 +47,12 @@ export const WithdrawInput = (props: {
|
||||||
const withdrawAmount = Math.min(
|
const withdrawAmount = Math.min(
|
||||||
type === InputType.Percent
|
type === InputType.Percent
|
||||||
? (pct * collateralBalanceLamports) / 100
|
? (pct * collateralBalanceLamports) / 100
|
||||||
: Math.ceil(
|
: Math.ceil(collateralBalanceLamports * (parseFloat(value) / collateralBalanceInLiquidity)),
|
||||||
collateralBalanceLamports *
|
|
||||||
(parseFloat(value) / collateralBalanceInLiquidity)
|
|
||||||
),
|
|
||||||
collateralBalanceLamports
|
collateralBalanceLamports
|
||||||
);
|
);
|
||||||
await withdraw(
|
await withdraw(fromAccounts[0], withdrawAmount, reserve, address, connection, wallet);
|
||||||
fromAccounts[0],
|
|
||||||
withdrawAmount,
|
|
||||||
reserve,
|
|
||||||
address,
|
|
||||||
connection,
|
|
||||||
wallet
|
|
||||||
);
|
|
||||||
|
|
||||||
setValue("");
|
setValue('');
|
||||||
setShowConfirmation(true);
|
setShowConfirmation(true);
|
||||||
} catch {
|
} catch {
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -97,11 +75,11 @@ export const WithdrawInput = (props: {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bodyStyle: React.CSSProperties = {
|
const bodyStyle: React.CSSProperties = {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -111,26 +89,26 @@ export const WithdrawInput = (props: {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-around",
|
justifyContent: 'space-around',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="withdraw-input-title">{LABELS.WITHDRAW_QUESTION}</div>
|
<div className='withdraw-input-title'>{LABELS.WITHDRAW_QUESTION}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-evenly",
|
justifyContent: 'space-evenly',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollateralInput
|
<CollateralInput
|
||||||
title="Amount"
|
title='Amount'
|
||||||
reserve={reserve}
|
reserve={reserve}
|
||||||
amount={parseFloat(value) || 0}
|
amount={parseFloat(value) || 0}
|
||||||
onInputChange={(val: number | null) => {
|
onInputChange={(val: number | null) => {
|
||||||
setValue(val?.toString() || "");
|
setValue(val?.toString() || '');
|
||||||
}}
|
}}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
hideBalance={true}
|
hideBalance={true}
|
||||||
|
@ -140,15 +118,13 @@ export const WithdrawInput = (props: {
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
|
|
||||||
<ConnectButton
|
<ConnectButton
|
||||||
size="large"
|
size='large'
|
||||||
type="primary"
|
type='primary'
|
||||||
onClick={onWithdraw}
|
onClick={onWithdraw}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{fromAccounts.length === 0
|
{fromAccounts.length === 0 ? LABELS.NO_COLLATERAL : LABELS.WITHDRAW_ACTION}
|
||||||
? LABELS.NO_COLLATERAL
|
|
||||||
: LABELS.WITHDRAW_ACTION}
|
|
||||||
</ConnectButton>
|
</ConnectButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./labels";
|
export * from './labels';
|
||||||
export * from "./math";
|
export * from './marks';
|
||||||
export * from "./marks";
|
export * from './style';
|
||||||
export * from "./style";
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useConnection } from "./connection";
|
import { useConnection } from '@common/contexts/connection';
|
||||||
import { LENDING_PROGRAM_ID } from "./../utils/ids";
|
import { LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import {
|
import {
|
||||||
LendingMarketParser,
|
LendingMarketParser,
|
||||||
isLendingReserve,
|
isLendingReserve,
|
||||||
|
@ -9,17 +9,12 @@ import {
|
||||||
LendingReserve,
|
LendingReserve,
|
||||||
isLendingObligation,
|
isLendingObligation,
|
||||||
LendingObligationParser,
|
LendingObligationParser,
|
||||||
} from "./../models/lending";
|
} from './../models/lending';
|
||||||
import {
|
import { cache, getMultipleAccounts, MintParser, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
cache,
|
import { PublicKey, AccountInfo } from '@solana/web3.js';
|
||||||
getMultipleAccounts,
|
import { DexMarketParser } from '../models/dex';
|
||||||
MintParser,
|
import { usePrecacheMarket } from './market';
|
||||||
ParsedAccount,
|
import { useLendingReserves } from '../hooks';
|
||||||
} from "./accounts";
|
|
||||||
import { PublicKey, AccountInfo } from "@solana/web3.js";
|
|
||||||
import { DexMarketParser } from "../models/dex";
|
|
||||||
import { usePrecacheMarket } from "./market";
|
|
||||||
import { useLendingReserves } from "../hooks";
|
|
||||||
|
|
||||||
export interface LendingContextState {}
|
export interface LendingContextState {}
|
||||||
|
|
||||||
|
@ -46,38 +41,21 @@ export const useLending = () => {
|
||||||
|
|
||||||
// TODO: query for all the dex from reserves
|
// TODO: query for all the dex from reserves
|
||||||
|
|
||||||
const processAccount = useCallback(
|
const processAccount = useCallback((item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
|
||||||
(item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
|
if (isLendingReserve(item.account)) {
|
||||||
if (isLendingReserve(item.account)) {
|
const reserve = cache.add(item.pubkey.toBase58(), item.account, LendingReserveParser);
|
||||||
const reserve = cache.add(
|
|
||||||
item.pubkey.toBase58(),
|
|
||||||
item.account,
|
|
||||||
LendingReserveParser
|
|
||||||
);
|
|
||||||
|
|
||||||
return reserve;
|
return reserve;
|
||||||
} else if (isLendingMarket(item.account)) {
|
} else if (isLendingMarket(item.account)) {
|
||||||
return cache.add(
|
return cache.add(item.pubkey.toBase58(), item.account, LendingMarketParser);
|
||||||
item.pubkey.toBase58(),
|
} else if (isLendingObligation(item.account)) {
|
||||||
item.account,
|
return cache.add(item.pubkey.toBase58(), item.account, LendingObligationParser);
|
||||||
LendingMarketParser
|
}
|
||||||
);
|
}, []);
|
||||||
} else if (isLendingObligation(item.account)) {
|
|
||||||
return cache.add(
|
|
||||||
item.pubkey.toBase58(),
|
|
||||||
item.account,
|
|
||||||
LendingObligationParser
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reserveAccounts.length > 0) {
|
if (reserveAccounts.length > 0) {
|
||||||
precacheMarkets(
|
precacheMarkets(reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58()));
|
||||||
reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [reserveAccounts, precacheMarkets]);
|
}, [reserveAccounts, precacheMarkets]);
|
||||||
|
|
||||||
|
@ -86,36 +64,21 @@ export const useLending = () => {
|
||||||
setLendingAccounts([]);
|
setLendingAccounts([]);
|
||||||
|
|
||||||
const queryLendingAccounts = async () => {
|
const queryLendingAccounts = async () => {
|
||||||
const programAccounts = await connection.getProgramAccounts(
|
const programAccounts = await connection.getProgramAccounts(LENDING_PROGRAM_ID);
|
||||||
LENDING_PROGRAM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
const accounts = programAccounts
|
const accounts = programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||||
.map(processAccount)
|
|
||||||
.filter((item) => item !== undefined);
|
|
||||||
|
|
||||||
const lendingReserves = accounts
|
const lendingReserves = accounts
|
||||||
.filter(
|
.filter((acc) => (acc?.info as LendingReserve).lendingMarket !== undefined)
|
||||||
(acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
|
|
||||||
)
|
|
||||||
.map((acc) => acc as ParsedAccount<LendingReserve>);
|
.map((acc) => acc as ParsedAccount<LendingReserve>);
|
||||||
|
|
||||||
const toQuery = [
|
const toQuery = [
|
||||||
...lendingReserves.map((acc) => {
|
...lendingReserves.map((acc) => {
|
||||||
const result = [
|
const result = [
|
||||||
cache.registerParser(
|
cache.registerParser(acc?.info.collateralMint.toBase58(), MintParser),
|
||||||
acc?.info.collateralMint.toBase58(),
|
cache.registerParser(acc?.info.liquidityMint.toBase58(), MintParser),
|
||||||
MintParser
|
|
||||||
),
|
|
||||||
cache.registerParser(
|
|
||||||
acc?.info.liquidityMint.toBase58(),
|
|
||||||
MintParser
|
|
||||||
),
|
|
||||||
// ignore dex if its not set
|
// ignore dex if its not set
|
||||||
cache.registerParser(
|
cache.registerParser(acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : '', DexMarketParser),
|
||||||
acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : "",
|
|
||||||
DexMarketParser
|
|
||||||
),
|
|
||||||
].filter((_) => _);
|
].filter((_) => _);
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
|
@ -123,15 +86,13 @@ export const useLending = () => {
|
||||||
|
|
||||||
// This will pre-cache all accounts used by pools
|
// This will pre-cache all accounts used by pools
|
||||||
// All those accounts are updated whenever there is a change
|
// All those accounts are updated whenever there is a change
|
||||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||||
({ keys, array }) => {
|
return array.map((obj, index) => {
|
||||||
return array.map((obj, index) => {
|
const address = keys[index];
|
||||||
const address = keys[index];
|
cache.add(address, obj);
|
||||||
cache.add(address, obj);
|
return obj;
|
||||||
return obj;
|
}) as any[];
|
||||||
}) as any[];
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// HACK: fix, force account refresh
|
// HACK: fix, force account refresh
|
||||||
programAccounts.map(processAccount).filter((item) => item !== undefined);
|
programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||||
|
@ -155,7 +116,7 @@ export const useLending = () => {
|
||||||
};
|
};
|
||||||
processAccount(item);
|
processAccount(item);
|
||||||
},
|
},
|
||||||
"singleGossip"
|
'singleGossip'
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -1,26 +1,20 @@
|
||||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { MINT_TO_MARKET } from "./../models/marketOverrides";
|
import { MINT_TO_MARKET } from './../models/marketOverrides';
|
||||||
import { POOLS_WITH_AIRDROP } from "./../models/airdrops";
|
import { POOLS_WITH_AIRDROP } from './../models/airdrops';
|
||||||
import {
|
import { convert, fromLamports, getTokenName, KnownTokenMap, STABLE_COINS } from 'common/src/utils/utils';
|
||||||
convert,
|
import { getPoolName } from '../utils/utils';
|
||||||
fromLamports,
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
getPoolName,
|
import { cache, getMultipleAccounts, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
getTokenName,
|
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum';
|
||||||
KnownTokenMap,
|
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||||
STABLE_COINS,
|
import { useMemo } from 'react';
|
||||||
} from "./../utils/utils";
|
import { EventEmitter } from 'common/src/utils/eventEmitter';
|
||||||
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 { DexMarketParser } from "./../models/dex";
|
import { DexMarketParser } from './../models/dex';
|
||||||
import { LendingMarket, LendingReserve, PoolInfo } from "../models";
|
import { LendingMarket, LendingReserve, PoolInfo } from '../models';
|
||||||
import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from "../utils/pools";
|
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
|
export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min
|
||||||
|
|
||||||
interface RecentPoolData {
|
interface RecentPoolData {
|
||||||
|
@ -50,27 +44,19 @@ export function MarketProvider({ children = null as any }) {
|
||||||
const { endpoint } = useConnectionConfig();
|
const { endpoint } = useConnectionConfig();
|
||||||
const accountsToObserve = useMemo(() => new Map<string, number>(), []);
|
const accountsToObserve = useMemo(() => new Map<string, number>(), []);
|
||||||
const [marketMints, setMarketMints] = useState<string[]>([]);
|
const [marketMints, setMarketMints] = useState<string[]>([]);
|
||||||
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(
|
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(new Map());
|
||||||
new Map()
|
|
||||||
);
|
|
||||||
|
|
||||||
const connection = useMemo(() => new Connection(endpoint, "recent"), [
|
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||||
endpoint,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const marketByMint = useMemo(() => {
|
const marketByMint = useMemo(() => {
|
||||||
return [...new Set(marketMints).values()].reduce((acc, key) => {
|
return [...new Set(marketMints).values()].reduce((acc, key) => {
|
||||||
const mintAddress = key;
|
const mintAddress = key;
|
||||||
|
|
||||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||||
(a) => a.address.toBase58() === mintAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
const marketAddress = MINT_TO_MARKET[mintAddress];
|
const marketAddress = MINT_TO_MARKET[mintAddress];
|
||||||
const marketName = `${SERUM_TOKEN?.name}/USDC`;
|
const marketName = `${SERUM_TOKEN?.name}/USDC`;
|
||||||
const marketInfo = MARKETS.find(
|
const marketInfo = MARKETS.find((m) => m.name === marketName || m.address.toBase58() === marketAddress);
|
||||||
(m) => m.name === marketName || m.address.toBase58() === marketAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
if (marketInfo) {
|
if (marketInfo) {
|
||||||
acc.set(mintAddress, {
|
acc.set(mintAddress, {
|
||||||
|
@ -109,7 +95,7 @@ export function MarketProvider({ children = null as any }) {
|
||||||
connection,
|
connection,
|
||||||
// only query for markets that are not in cahce
|
// only query for markets that are not in cahce
|
||||||
allMarkets.filter((a) => cache.get(a) === undefined),
|
allMarkets.filter((a) => cache.get(a) === undefined),
|
||||||
"single"
|
'single'
|
||||||
).then(({ keys, array }) => {
|
).then(({ keys, array }) => {
|
||||||
allMarkets.forEach(() => {});
|
allMarkets.forEach(() => {});
|
||||||
|
|
||||||
|
@ -167,10 +153,7 @@ export function MarketProvider({ children = null as any }) {
|
||||||
|
|
||||||
const midPriceInUSD = useCallback(
|
const midPriceInUSD = useCallback(
|
||||||
(mintAddress: string) => {
|
(mintAddress: string) => {
|
||||||
return getMidPrice(
|
return getMidPrice(marketByMint.get(mintAddress)?.marketInfo.address.toBase58(), mintAddress);
|
||||||
marketByMint.get(mintAddress)?.marketInfo.address.toBase58(),
|
|
||||||
mintAddress
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[marketByMint]
|
[marketByMint]
|
||||||
);
|
);
|
||||||
|
@ -178,7 +161,7 @@ export function MarketProvider({ children = null as any }) {
|
||||||
const subscribeToMarket = useCallback(
|
const subscribeToMarket = useCallback(
|
||||||
(mintAddress: string) => {
|
(mintAddress: string) => {
|
||||||
const info = marketByMint.get(mintAddress);
|
const info = marketByMint.get(mintAddress);
|
||||||
const market = cache.get(info?.marketInfo.address.toBase58() || "");
|
const market = cache.get(info?.marketInfo.address.toBase58() || '');
|
||||||
if (!market) {
|
if (!market) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
@ -248,7 +231,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
||||||
const marketEmitter = context?.marketEmitter;
|
const marketEmitter = context?.marketEmitter;
|
||||||
const marketsByMint = context?.marketByMint;
|
const marketsByMint = context?.marketByMint;
|
||||||
const dailyVolume = context?.dailyVolume;
|
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(() => {
|
useEffect(() => {
|
||||||
if (!marketEmitter || !subscribeToMarket || pools.length === 0) {
|
if (!marketEmitter || !subscribeToMarket || pools.length === 0) {
|
||||||
|
@ -260,9 +243,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
||||||
const subscriptions = mints.map((m) => subscribeToMarket(m));
|
const subscriptions = mints.map((m) => subscribeToMarket(m));
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
setEnriched(
|
setEnriched(createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap));
|
||||||
createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispose = marketEmitter.onMarket(update);
|
const dispose = marketEmitter.onMarket(update);
|
||||||
|
@ -274,15 +255,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
||||||
subscriptions.forEach((dispose) => dispose && dispose());
|
subscriptions.forEach((dispose) => dispose && dispose());
|
||||||
};
|
};
|
||||||
// Do not add pools here, causes a really bad infinite rendering loop. Use poolKeys instead.
|
// 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;
|
return enriched;
|
||||||
};
|
};
|
||||||
|
@ -307,30 +280,20 @@ function createEnrichedPools(
|
||||||
const result = pools
|
const result = pools
|
||||||
.filter((p) => p.pubkeys.holdingMints && p.pubkeys.holdingMints.length > 1)
|
.filter((p) => p.pubkeys.holdingMints && p.pubkeys.holdingMints.length > 1)
|
||||||
.map((p, index) => {
|
.map((p, index) => {
|
||||||
const mints = (p.pubkeys.holdingMints || [])
|
const mints = (p.pubkeys.holdingMints || []).map((a) => a.toBase58()).sort();
|
||||||
.map((a) => a.toBase58())
|
|
||||||
.sort();
|
|
||||||
const mintA = cache.getMint(mints[0]);
|
const mintA = cache.getMint(mints[0]);
|
||||||
const mintB = cache.getMint(mints[1]);
|
const mintB = cache.getMint(mints[1]);
|
||||||
|
|
||||||
const account0 = cache.get(p.pubkeys.holdingAccounts[0]);
|
const account0 = cache.get(p.pubkeys.holdingAccounts[0]);
|
||||||
const account1 = cache.get(p.pubkeys.holdingAccounts[1]);
|
const account1 = cache.get(p.pubkeys.holdingAccounts[1]);
|
||||||
|
|
||||||
const accountA =
|
const accountA = account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
||||||
account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
const accountB = account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
||||||
const accountB =
|
|
||||||
account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
|
||||||
|
|
||||||
const baseMid = getMidPrice(
|
const baseMid = getMidPrice(marketByMint.get(mints[0])?.marketInfo.address.toBase58() || '', mints[0]);
|
||||||
marketByMint.get(mints[0])?.marketInfo.address.toBase58() || "",
|
|
||||||
mints[0]
|
|
||||||
);
|
|
||||||
const baseReserveUSD = baseMid * convert(accountA, mintA);
|
const baseReserveUSD = baseMid * convert(accountA, mintA);
|
||||||
|
|
||||||
const quote = getMidPrice(
|
const quote = getMidPrice(marketByMint.get(mints[1])?.marketInfo.address.toBase58() || '', mints[1]);
|
||||||
marketByMint.get(mints[1])?.marketInfo.address.toBase58() || "",
|
|
||||||
mints[1]
|
|
||||||
);
|
|
||||||
const quoteReserveUSD = quote * convert(accountB, mintB);
|
const quoteReserveUSD = quote * convert(accountB, mintB);
|
||||||
|
|
||||||
const poolMint = cache.getMint(p.pubkeys.mint);
|
const poolMint = cache.getMint(p.pubkeys.mint);
|
||||||
|
@ -338,16 +301,10 @@ function createEnrichedPools(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let airdropYield = calculateAirdropYield(
|
let airdropYield = calculateAirdropYield(p, marketByMint, baseReserveUSD, quoteReserveUSD);
|
||||||
p,
|
|
||||||
marketByMint,
|
|
||||||
baseReserveUSD,
|
|
||||||
quoteReserveUSD
|
|
||||||
);
|
|
||||||
|
|
||||||
let volume = 0;
|
let volume = 0;
|
||||||
let volume24h =
|
let volume24h = baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
||||||
baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
|
||||||
let fees24h = volume24h * (LIQUIDITY_PROVIDER_FEE - SERUM_FEE);
|
let fees24h = volume24h * (LIQUIDITY_PROVIDER_FEE - SERUM_FEE);
|
||||||
let fees = 0;
|
let fees = 0;
|
||||||
let apy = airdropYield;
|
let apy = airdropYield;
|
||||||
|
@ -355,18 +312,13 @@ function createEnrichedPools(
|
||||||
if (p.pubkeys.feeAccount) {
|
if (p.pubkeys.feeAccount) {
|
||||||
const feeAccount = cache.get(p.pubkeys.feeAccount);
|
const feeAccount = cache.get(p.pubkeys.feeAccount);
|
||||||
|
|
||||||
if (
|
if (poolMint && feeAccount && feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()) {
|
||||||
poolMint &&
|
|
||||||
feeAccount &&
|
|
||||||
feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()
|
|
||||||
) {
|
|
||||||
const feeBalance = feeAccount?.info.amount.toNumber();
|
const feeBalance = feeAccount?.info.amount.toNumber();
|
||||||
const supply = poolMint?.supply.toNumber();
|
const supply = poolMint?.supply.toNumber();
|
||||||
|
|
||||||
const ownedPct = feeBalance / supply;
|
const ownedPct = feeBalance / supply;
|
||||||
|
|
||||||
const poolOwnerFees =
|
const poolOwnerFees = ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
||||||
ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
|
||||||
volume = poolOwnerFees / 0.0004;
|
volume = poolOwnerFees / 0.0004;
|
||||||
fees = volume * LIQUIDITY_PROVIDER_FEE;
|
fees = volume * LIQUIDITY_PROVIDER_FEE;
|
||||||
|
|
||||||
|
@ -376,27 +328,16 @@ function createEnrichedPools(
|
||||||
|
|
||||||
// Aproximation not true for all pools we need to fine a better way
|
// Aproximation not true for all pools we need to fine a better way
|
||||||
const daysSinceInception = Math.floor(
|
const daysSinceInception = Math.floor(
|
||||||
(TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) /
|
(TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) / (24 * 3600 * 1000)
|
||||||
(24 * 3600 * 1000)
|
|
||||||
);
|
);
|
||||||
const apy0 =
|
const apy0 =
|
||||||
parseFloat(
|
parseFloat(((baseVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||||
((baseVolume / daysSinceInception) *
|
|
||||||
LIQUIDITY_PROVIDER_FEE *
|
|
||||||
356) as any
|
|
||||||
) / baseReserveUSD;
|
|
||||||
const apy1 =
|
const apy1 =
|
||||||
parseFloat(
|
parseFloat(((quoteVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / quoteReserveUSD;
|
||||||
((quoteVolume / daysSinceInception) *
|
|
||||||
LIQUIDITY_PROVIDER_FEE *
|
|
||||||
356) as any
|
|
||||||
) / quoteReserveUSD;
|
|
||||||
|
|
||||||
apy = apy + Math.max(apy0, apy1);
|
apy = apy + Math.max(apy0, apy1);
|
||||||
|
|
||||||
const apy24h0 =
|
const apy24h0 = parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||||
parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) /
|
|
||||||
baseReserveUSD;
|
|
||||||
apy24h = apy24h + apy24h0;
|
apy24h = apy24h + apy24h0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,10 +346,7 @@ function createEnrichedPools(
|
||||||
const lpMint = cache.getMint(p.pubkeys.mint);
|
const lpMint = cache.getMint(p.pubkeys.mint);
|
||||||
|
|
||||||
const name = getPoolName(tokenMap, p);
|
const name = getPoolName(tokenMap, p);
|
||||||
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace(
|
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace('/', '-')}`;
|
||||||
"/",
|
|
||||||
"-"
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: p.pubkeys.account.toBase58(),
|
key: p.pubkeys.account.toBase58(),
|
||||||
|
@ -423,11 +361,7 @@ function createEnrichedPools(
|
||||||
liquidityAinUsd: baseReserveUSD,
|
liquidityAinUsd: baseReserveUSD,
|
||||||
liquidityB: convert(accountB, mintB),
|
liquidityB: convert(accountB, mintB),
|
||||||
liquidityBinUsd: quoteReserveUSD,
|
liquidityBinUsd: quoteReserveUSD,
|
||||||
supply:
|
supply: lpMint && (lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)).toFixed(9),
|
||||||
lpMint &&
|
|
||||||
(
|
|
||||||
lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)
|
|
||||||
).toFixed(9),
|
|
||||||
fees,
|
fees,
|
||||||
fees24h,
|
fees24h,
|
||||||
liquidity: baseReserveUSD + quoteReserveUSD,
|
liquidity: baseReserveUSD + quoteReserveUSD,
|
||||||
|
@ -451,9 +385,7 @@ function calculateAirdropYield(
|
||||||
quoteReserveUSD: number
|
quoteReserveUSD: number
|
||||||
) {
|
) {
|
||||||
let airdropYield = 0;
|
let airdropYield = 0;
|
||||||
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) =>
|
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) => drop.pool.equals(p.pubkeys.mint));
|
||||||
drop.pool.equals(p.pubkeys.mint)
|
|
||||||
);
|
|
||||||
if (poolWithAirdrop) {
|
if (poolWithAirdrop) {
|
||||||
airdropYield = poolWithAirdrop.airdrops.reduce((acc, item) => {
|
airdropYield = poolWithAirdrop.airdrops.reduce((acc, item) => {
|
||||||
const market = marketByMint.get(item.mint.toBase58())?.marketInfo.address;
|
const market = marketByMint.get(item.mint.toBase58())?.marketInfo.address;
|
||||||
|
@ -463,8 +395,7 @@ function calculateAirdropYield(
|
||||||
acc =
|
acc =
|
||||||
acc +
|
acc +
|
||||||
// airdrop yield
|
// airdrop yield
|
||||||
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) *
|
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) * (365 / 30);
|
||||||
(365 / 30);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -474,9 +405,7 @@ function calculateAirdropYield(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMidPriceInUSD = (mint: string) => {
|
export const useMidPriceInUSD = (mint: string) => {
|
||||||
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(
|
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(MarketsContext) as MarketsContextState;
|
||||||
MarketsContext
|
|
||||||
) as MarketsContextState;
|
|
||||||
const [price, setPrice] = useState<number>(0);
|
const [price, setPrice] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -504,12 +433,7 @@ export const usePrecacheMarket = () => {
|
||||||
return context.precacheMarkets;
|
return context.precacheMarkets;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const simulateMarketOrderFill = (
|
export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve, dex: PublicKey, useBBO = false) => {
|
||||||
amount: number,
|
|
||||||
reserve: LendingReserve,
|
|
||||||
dex: PublicKey,
|
|
||||||
useBBO = false
|
|
||||||
) => {
|
|
||||||
const liquidityMint = cache.get(reserve.liquidityMint);
|
const liquidityMint = cache.get(reserve.liquidityMint);
|
||||||
const collateralMint = cache.get(reserve.collateralMint);
|
const collateralMint = cache.get(reserve.collateralMint);
|
||||||
if (!liquidityMint || !collateralMint) {
|
if (!liquidityMint || !collateralMint) {
|
||||||
|
@ -522,22 +446,12 @@ export const simulateMarketOrderFill = (
|
||||||
}
|
}
|
||||||
const decodedMarket = marketInfo.info;
|
const decodedMarket = marketInfo.info;
|
||||||
|
|
||||||
const baseMintDecimals =
|
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||||
const quoteMintDecimals =
|
|
||||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
|
||||||
|
|
||||||
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<
|
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
LendingMarket
|
|
||||||
>;
|
|
||||||
|
|
||||||
const dexMarket = new Market(
|
const dexMarket = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||||
decodedMarket,
|
|
||||||
baseMintDecimals,
|
|
||||||
quoteMintDecimals,
|
|
||||||
undefined,
|
|
||||||
decodedMarket.programId
|
|
||||||
);
|
|
||||||
|
|
||||||
const bidInfo = cache.get(decodedMarket?.bids)?.info;
|
const bidInfo = cache.get(decodedMarket?.bids)?.info;
|
||||||
const askInfo = cache.get(decodedMarket?.asks)?.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 bids = new Orderbook(dexMarket, bidInfo.accountFlags, bidInfo.slab);
|
||||||
const asks = new Orderbook(dexMarket, askInfo.accountFlags, askInfo.slab);
|
const asks = new Orderbook(dexMarket, askInfo.accountFlags, askInfo.slab);
|
||||||
|
|
||||||
const book = lendingMarket.info.quoteMint.equals(reserve.liquidityMint)
|
const book = lendingMarket.info.quoteMint.equals(reserve.liquidityMint) ? bids : asks;
|
||||||
? bids
|
|
||||||
: asks;
|
|
||||||
|
|
||||||
let cost = 0;
|
let cost = 0;
|
||||||
let remaining = fromLamports(amount, liquidityMint.info);
|
let remaining = fromLamports(amount, liquidityMint.info);
|
||||||
|
@ -593,11 +505,9 @@ const bbo = (bidsBook: Orderbook, asksBook: Orderbook) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||||
(a) => a.address.toBase58() === mintAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
if (STABLE_COINS.has(SERUM_TOKEN?.name || "")) {
|
if (STABLE_COINS.has(SERUM_TOKEN?.name || '')) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,18 +522,10 @@ const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||||
|
|
||||||
const decodedMarket = marketInfo.info;
|
const decodedMarket = marketInfo.info;
|
||||||
|
|
||||||
const baseMintDecimals =
|
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||||
const quoteMintDecimals =
|
|
||||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
|
||||||
|
|
||||||
const market = new Market(
|
const market = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||||
decodedMarket,
|
|
||||||
baseMintDecimals,
|
|
||||||
quoteMintDecimals,
|
|
||||||
undefined,
|
|
||||||
decodedMarket.programId
|
|
||||||
);
|
|
||||||
|
|
||||||
const bids = cache.get(decodedMarket.bids)?.info;
|
const bids = cache.get(decodedMarket.bids)?.info;
|
||||||
const asks = cache.get(decodedMarket.asks)?.info;
|
const asks = cache.get(decodedMarket.asks)?.info;
|
||||||
|
@ -643,14 +545,12 @@ const refreshAccounts = async (connection: Connection, keys: string[]) => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMultipleAccounts(connection, keys, "single").then(
|
return getMultipleAccounts(connection, keys, 'single').then(({ keys, array }) => {
|
||||||
({ keys, array }) => {
|
return array.map((item, index) => {
|
||||||
return array.map((item, index) => {
|
const address = keys[index];
|
||||||
const address = keys[index];
|
return cache.add(new PublicKey(address), item);
|
||||||
return cache.add(new PublicKey(address), item);
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SerumMarket {
|
interface SerumMarket {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { useContext, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { PoolInfo } from '../models';
|
||||||
|
|
||||||
|
const AccountsContext = React.createContext<any>(null);
|
||||||
|
|
||||||
|
export function useCachedPool(legacy = false) {
|
||||||
|
const context = useContext(AccountsContext);
|
||||||
|
|
||||||
|
const allPools = context.pools as PoolInfo[];
|
||||||
|
const pools = useMemo(() => {
|
||||||
|
return allPools.filter((p) => p.legacy === legacy);
|
||||||
|
}, [allPools, legacy]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pools,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
export * from "./useUserAccounts";
|
export * from './useLendingReserves';
|
||||||
export * from "./useAccountByMint";
|
export * from './useCollateralBalance';
|
||||||
export * from "./useLendingReserves";
|
export * from './useLendingObligations';
|
||||||
export * from "./useTokenName";
|
export * from './useUserObligations';
|
||||||
export * from "./useUserBalance";
|
export * from './useUserObligationByReserve';
|
||||||
export * from "./useCollateralBalance";
|
export * from './useBorrowedAmount';
|
||||||
export * from "./useLendingObligations";
|
export * from './useUserDeposits';
|
||||||
export * from "./useUserObligations";
|
export * from './useSliderInput';
|
||||||
export * from "./useUserObligationByReserve";
|
export * from './useUserBalance';
|
||||||
export * from "./useBorrowedAmount";
|
export * from './useEnrichedLendingObligations';
|
||||||
export * from "./useUserDeposits";
|
export * from './useBorrowingPower';
|
||||||
export * from "./useSliderInput";
|
export * from './useLendingMarket';
|
||||||
export * from "./useEnrichedLendingObligations";
|
|
||||||
export * from "./useBorrowingPower";
|
|
||||||
export * from "./useLendingMarket";
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { useUserAccounts } from "./useUserAccounts";
|
|
||||||
|
|
||||||
export const useAccountByMint = (mint?: string | PublicKey) => {
|
|
||||||
const { userAccounts } = useUserAccounts();
|
|
||||||
const mintAddress = typeof mint === "string" ? mint : mint?.toBase58();
|
|
||||||
|
|
||||||
const index = userAccounts.findIndex(
|
|
||||||
(acc) => acc.info.mint.toBase58() === mintAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
return userAccounts[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
|
@ -1,17 +1,11 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useUserObligationByReserve } from "./useUserObligationByReserve";
|
import { useUserObligationByReserve } from './useUserObligationByReserve';
|
||||||
import { fromLamports, wadToLamports } from "../utils/utils";
|
import { fromLamports, wadToLamports } from 'common/src/utils/utils';
|
||||||
import {
|
import { cache, getMultipleAccounts, MintParser, ParsedAccount, useMint } from 'common/src/contexts/accounts';
|
||||||
cache,
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
getMultipleAccounts,
|
import { MintInfo } from '@solana/spl-token';
|
||||||
MintParser,
|
import { useLendingReserve } from './useLendingReserves';
|
||||||
ParsedAccount,
|
|
||||||
useMint,
|
|
||||||
} from "../contexts/accounts";
|
|
||||||
import { useConnection } from "../contexts/connection";
|
|
||||||
import { MintInfo } from "@solana/spl-token";
|
|
||||||
import { useLendingReserve } from "./useLendingReserves";
|
|
||||||
|
|
||||||
export function useBorrowedAmount(address?: string | PublicKey) {
|
export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
|
@ -39,10 +33,8 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
// precache obligation mints
|
// precache obligation mints
|
||||||
const { keys, array } = await getMultipleAccounts(
|
const { keys, array } = await getMultipleAccounts(
|
||||||
connection,
|
connection,
|
||||||
userObligationsByReserve.map((item) =>
|
userObligationsByReserve.map((item) => item.obligation.info.tokenMint.toBase58()),
|
||||||
item.obligation.info.tokenMint.toBase58()
|
'single'
|
||||||
),
|
|
||||||
"single"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
array.forEach((item, index) => {
|
array.forEach((item, index) => {
|
||||||
|
@ -61,20 +53,12 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
let liquidationThreshold = 0;
|
let liquidationThreshold = 0;
|
||||||
|
|
||||||
userObligationsByReserve.forEach((item) => {
|
userObligationsByReserve.forEach((item) => {
|
||||||
const borrowed = wadToLamports(
|
const borrowed = wadToLamports(item.obligation.info.borrowAmountWad).toNumber();
|
||||||
item.obligation.info.borrowAmountWad
|
|
||||||
).toNumber();
|
|
||||||
|
|
||||||
const owned = item.userAccounts.reduce(
|
const owned = item.userAccounts.reduce((amount, acc) => (amount += acc.info.amount.toNumber()), 0);
|
||||||
(amount, acc) => (amount += acc.info.amount.toNumber()),
|
const obligationMint = cache.get(item.obligation.info.tokenMint) as ParsedAccount<MintInfo>;
|
||||||
0
|
|
||||||
);
|
|
||||||
const obligationMint = cache.get(
|
|
||||||
item.obligation.info.tokenMint
|
|
||||||
) as ParsedAccount<MintInfo>;
|
|
||||||
|
|
||||||
result.borrowedLamports +=
|
result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber());
|
||||||
borrowed * (owned / obligationMint?.info.supply.toNumber());
|
|
||||||
result.borrowedInUSD += item.obligation.info.borrowedInQuote;
|
result.borrowedInUSD += item.obligation.info.borrowedInQuote;
|
||||||
result.colateralInUSD += item.obligation.info.collateralInQuote;
|
result.colateralInUSD += item.obligation.info.collateralInQuote;
|
||||||
liquidationThreshold = item.obligation.info.liquidationThreshold;
|
liquidationThreshold = item.obligation.info.liquidationThreshold;
|
||||||
|
@ -85,10 +69,7 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
result.health = userObligationsByReserve[0].obligation.info.health;
|
result.health = userObligationsByReserve[0].obligation.info.health;
|
||||||
} else {
|
} else {
|
||||||
result.ltv = (100 * result.borrowedInUSD) / result.colateralInUSD;
|
result.ltv = (100 * result.borrowedInUSD) / result.colateralInUSD;
|
||||||
result.health =
|
result.health = (result.colateralInUSD * liquidationThreshold) / 100 / result.borrowedInUSD;
|
||||||
(result.colateralInUSD * liquidationThreshold) /
|
|
||||||
100 /
|
|
||||||
result.borrowedInUSD;
|
|
||||||
result.health = Number.isFinite(result.health) ? result.health : 0;
|
result.health = Number.isFinite(result.health) ? result.health : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,32 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useMint } from "../contexts/accounts";
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
import { useMarkets } from "../contexts/market";
|
import { useMarkets } from '../contexts/market';
|
||||||
import { LendingReserve, reserveMarketCap } from "../models/lending";
|
import { LendingReserve, reserveMarketCap } from '../models/lending';
|
||||||
import { fromLamports } from "../utils/utils";
|
import { fromLamports } from 'common/src/utils/utils';
|
||||||
import { useUserBalance } from "./useUserBalance";
|
import { useUserBalance } from './useUserBalance';
|
||||||
|
|
||||||
export function useUserCollateralBalance(
|
export function useUserCollateralBalance(reserve?: LendingReserve, account?: PublicKey) {
|
||||||
reserve?: LendingReserve,
|
|
||||||
account?: PublicKey
|
|
||||||
) {
|
|
||||||
const mint = useMint(reserve?.collateralMint);
|
const mint = useMint(reserve?.collateralMint);
|
||||||
const { balanceLamports: userBalance, accounts } = useUserBalance(
|
const { balanceLamports: userBalance, accounts } = useUserBalance(reserve?.collateralMint, account);
|
||||||
reserve?.collateralMint,
|
|
||||||
account
|
|
||||||
);
|
|
||||||
|
|
||||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||||
|
|
||||||
const balanceLamports = useMemo(
|
const balanceLamports = useMemo(() => reserve && calculateCollateralBalance(reserve, userBalance), [
|
||||||
() => reserve && calculateCollateralBalance(reserve, userBalance),
|
userBalance,
|
||||||
[userBalance, reserve]
|
reserve,
|
||||||
);
|
|
||||||
|
|
||||||
const balance = useMemo(() => fromLamports(balanceLamports, mint), [
|
|
||||||
balanceLamports,
|
|
||||||
mint,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const balance = useMemo(() => fromLamports(balanceLamports, mint), [balanceLamports, mint]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateBalance = () => {
|
const updateBalance = () => {
|
||||||
setBalanceInUSD(
|
setBalanceInUSD(balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || ''));
|
||||||
balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || "")
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispose = marketEmitter.onMarket((args) => {
|
const dispose = marketEmitter.onMarket((args) => {
|
||||||
if (args.ids.has(reserve?.dexMarket.toBase58() || "")) {
|
if (args.ids.has(reserve?.dexMarket.toBase58() || '')) {
|
||||||
updateBalance();
|
updateBalance();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -58,12 +47,6 @@ export function useUserCollateralBalance(
|
||||||
hasBalance: accounts.length > 0 && balance > 0,
|
hasBalance: accounts.length > 0 && balance > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function calculateCollateralBalance(
|
export function calculateCollateralBalance(reserve: LendingReserve, balanceLamports: number) {
|
||||||
reserve: LendingReserve,
|
return reserveMarketCap(reserve) * (balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1));
|
||||||
balanceLamports: number
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
reserveMarketCap(reserve) *
|
|
||||||
(balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
import { useLendingObligations } from "./useLendingObligations";
|
import { useLendingObligations } from './useLendingObligations';
|
||||||
import {
|
import { collateralToLiquidity, LendingObligation, LendingReserve } from '../models/lending';
|
||||||
collateralToLiquidity,
|
import { useLendingReserves } from './useLendingReserves';
|
||||||
LendingObligation,
|
import { fromLamports, getTokenName, wadToLamports } from 'common/src/utils/utils';
|
||||||
LendingReserve,
|
import { MintInfo } from '@solana/spl-token';
|
||||||
} from "../models/lending";
|
import { simulateMarketOrderFill, useMarkets } from '../contexts/market';
|
||||||
import { useLendingReserves } from "./useLendingReserves";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { fromLamports, getTokenName, wadToLamports } from "../utils/utils";
|
|
||||||
import { MintInfo } from "@solana/spl-token";
|
|
||||||
import { simulateMarketOrderFill, useMarkets } from "../contexts/market";
|
|
||||||
import { useConnectionConfig } from "../contexts/connection";
|
|
||||||
|
|
||||||
interface EnrichedLendingObligationInfo extends LendingObligation {
|
interface EnrichedLendingObligationInfo extends LendingObligation {
|
||||||
ltv: number;
|
ltv: number;
|
||||||
|
@ -50,9 +46,7 @@ export function useEnrichedLendingObligations() {
|
||||||
obligations
|
obligations
|
||||||
.map((obligation) => ({
|
.map((obligation) => ({
|
||||||
obligation,
|
obligation,
|
||||||
reserve: availableReserves.get(
|
reserve: availableReserves.get(obligation.info.borrowReserve.toBase58()) as ParsedAccount<LendingReserve>,
|
||||||
obligation.info.borrowReserve.toBase58()
|
|
||||||
) as ParsedAccount<LendingReserve>,
|
|
||||||
collateralReserve: availableReserves.get(
|
collateralReserve: availableReserves.get(
|
||||||
obligation.info.collateralReserve.toBase58()
|
obligation.info.collateralReserve.toBase58()
|
||||||
) as ParsedAccount<LendingReserve>,
|
) as ParsedAccount<LendingReserve>,
|
||||||
|
@ -60,63 +54,42 @@ export function useEnrichedLendingObligations() {
|
||||||
// use obligations with reserves available
|
// use obligations with reserves available
|
||||||
.filter((item) => item.reserve)
|
.filter((item) => item.reserve)
|
||||||
// use reserves with borrow amount greater than zero
|
// use reserves with borrow amount greater than zero
|
||||||
.filter(
|
.filter((item) => wadToLamports(item.obligation.info.borrowAmountWad).toNumber() > 0)
|
||||||
(item) =>
|
|
||||||
wadToLamports(item.obligation.info.borrowAmountWad).toNumber() > 0
|
|
||||||
)
|
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const obligation = item.obligation;
|
const obligation = item.obligation;
|
||||||
const reserve = item.reserve.info;
|
const reserve = item.reserve.info;
|
||||||
const collateralReserve = item.reserve.info;
|
const collateralReserve = item.reserve.info;
|
||||||
const liquidityMint = cache.get(
|
const liquidityMint = cache.get(reserve.liquidityMint) as ParsedAccount<MintInfo>;
|
||||||
reserve.liquidityMint
|
|
||||||
) as ParsedAccount<MintInfo>;
|
|
||||||
let ltv = 0;
|
let ltv = 0;
|
||||||
let health = 0;
|
let health = 0;
|
||||||
let borrowedInQuote = 0;
|
let borrowedInQuote = 0;
|
||||||
let collateralInQuote = 0;
|
let collateralInQuote = 0;
|
||||||
|
|
||||||
if (liquidityMint) {
|
if (liquidityMint) {
|
||||||
const collateralMint = cache.get(
|
const collateralMint = cache.get(item.collateralReserve.info.liquidityMint);
|
||||||
item.collateralReserve.info.liquidityMint
|
|
||||||
);
|
|
||||||
|
|
||||||
const collateral = fromLamports(
|
const collateral = fromLamports(
|
||||||
collateralToLiquidity(
|
collateralToLiquidity(obligation.info.depositedCollateral, item.reserve.info),
|
||||||
obligation.info.depositedCollateral,
|
|
||||||
item.reserve.info
|
|
||||||
),
|
|
||||||
collateralMint?.info
|
collateralMint?.info
|
||||||
);
|
);
|
||||||
|
|
||||||
const borrowed = wadToLamports(
|
const borrowed = wadToLamports(obligation.info.borrowAmountWad).toNumber();
|
||||||
obligation.info.borrowAmountWad
|
|
||||||
).toNumber();
|
|
||||||
|
|
||||||
const borrowedAmount = simulateMarketOrderFill(
|
const borrowedAmount = simulateMarketOrderFill(
|
||||||
borrowed,
|
borrowed,
|
||||||
item.reserve.info,
|
item.reserve.info,
|
||||||
item.reserve.info.dexMarketOption
|
item.reserve.info.dexMarketOption ? item.reserve.info.dexMarket : item.collateralReserve.info.dexMarket,
|
||||||
? item.reserve.info.dexMarket
|
|
||||||
: item.collateralReserve.info.dexMarket,
|
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const liquidityMintAddress = item.reserve.info.liquidityMint.toBase58();
|
const liquidityMintAddress = item.reserve.info.liquidityMint.toBase58();
|
||||||
const liquidityMint = cache.get(
|
const liquidityMint = cache.get(liquidityMintAddress) as ParsedAccount<MintInfo>;
|
||||||
liquidityMintAddress
|
borrowedInQuote = fromLamports(borrowed, liquidityMint.info) * midPriceInUSD(liquidityMintAddress);
|
||||||
) as ParsedAccount<MintInfo>;
|
collateralInQuote = collateral * midPriceInUSD(collateralMint?.pubkey.toBase58() || '');
|
||||||
borrowedInQuote =
|
|
||||||
fromLamports(borrowed, liquidityMint.info) *
|
|
||||||
midPriceInUSD(liquidityMintAddress);
|
|
||||||
collateralInQuote =
|
|
||||||
collateral *
|
|
||||||
midPriceInUSD(collateralMint?.pubkey.toBase58() || "");
|
|
||||||
|
|
||||||
ltv = (100 * borrowedAmount) / collateral;
|
ltv = (100 * borrowedAmount) / collateral;
|
||||||
|
|
||||||
const liquidationThreshold =
|
const liquidationThreshold = item.reserve.info.config.liquidationThreshold;
|
||||||
item.reserve.info.config.liquidationThreshold;
|
|
||||||
health = (collateral * liquidationThreshold) / 100 / borrowedAmount;
|
health = (collateral * liquidationThreshold) / 100 / borrowedAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,13 +101,9 @@ export function useEnrichedLendingObligations() {
|
||||||
health,
|
health,
|
||||||
borrowedInQuote,
|
borrowedInQuote,
|
||||||
collateralInQuote,
|
collateralInQuote,
|
||||||
liquidationThreshold:
|
liquidationThreshold: item.reserve.info.config.liquidationThreshold,
|
||||||
item.reserve.info.config.liquidationThreshold,
|
|
||||||
repayName: getTokenName(tokenMap, reserve.liquidityMint),
|
repayName: getTokenName(tokenMap, reserve.liquidityMint),
|
||||||
collateralName: getTokenName(
|
collateralName: getTokenName(tokenMap, collateralReserve.liquidityMint),
|
||||||
tokenMap,
|
|
||||||
collateralReserve.liquidityMint
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
} as EnrichedLendingObligation;
|
} as EnrichedLendingObligation;
|
||||||
})
|
})
|
||||||
|
@ -142,9 +111,7 @@ export function useEnrichedLendingObligations() {
|
||||||
);
|
);
|
||||||
}, [obligations, availableReserves, midPriceInUSD, tokenMap]);
|
}, [obligations, availableReserves, midPriceInUSD, tokenMap]);
|
||||||
|
|
||||||
const [enriched, setEnriched] = useState<EnrichedLendingObligation[]>(
|
const [enriched, setEnriched] = useState<EnrichedLendingObligation[]>(enrichedFactory());
|
||||||
enrichedFactory()
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = marketEmitter.onMarket(() => {
|
const dispose = marketEmitter.onMarket(() => {
|
||||||
|
@ -162,7 +129,7 @@ export function useEnrichedLendingObligations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEnrichedLendingObligation(address?: string | PublicKey) {
|
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 { obligations } = useEnrichedLendingObligations();
|
||||||
|
|
||||||
const obligation = useMemo(() => {
|
const obligation = useMemo(() => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react';
|
||||||
import { LendingMarketParser, LendingMarket } from "../models/lending";
|
import { LendingMarketParser, LendingMarket } from '../models/lending';
|
||||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
|
|
||||||
const getLendingMarkets = () => {
|
const getLendingMarkets = () => {
|
||||||
return cache
|
return cache
|
||||||
|
@ -11,9 +11,7 @@ const getLendingMarkets = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useLendingMarkets() {
|
export function useLendingMarkets() {
|
||||||
const [lendingMarkets, setLendingMarket] = useState<
|
const [lendingMarkets, setLendingMarket] = useState<ParsedAccount<LendingMarket>[]>(getLendingMarkets());
|
||||||
ParsedAccount<LendingMarket>[]
|
|
||||||
>(getLendingMarkets());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = cache.emitter.onCache((args) => {
|
const dispose = cache.emitter.onCache((args) => {
|
||||||
|
@ -33,10 +31,10 @@ export function useLendingMarkets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLendingMarket(address?: string | PublicKey) {
|
export function useLendingMarket(address?: string | PublicKey) {
|
||||||
const id = typeof address === "string" ? address : address?.toBase58();
|
const id = typeof address === 'string' ? address : address?.toBase58();
|
||||||
const [lendingMarket, setLendingMarket] = useState<
|
const [lendingMarket, setLendingMarket] = useState<ParsedAccount<LendingMarket>>(
|
||||||
ParsedAccount<LendingMarket>
|
cache.get(id || '') as ParsedAccount<LendingMarket>
|
||||||
>(cache.get(id || "") as ParsedAccount<LendingMarket>);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = cache.emitter.onCache((args) => {
|
const dispose = cache.emitter.onCache((args) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react';
|
||||||
import { LendingObligation, LendingObligationParser } from "../models/lending";
|
import { LendingObligation, LendingObligationParser } from '../models/lending';
|
||||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
|
|
||||||
const getLendingObligations = () => {
|
const getLendingObligations = () => {
|
||||||
return cache
|
return cache
|
||||||
|
@ -31,10 +31,8 @@ export function useLendingObligations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLendingObligation(address?: string | PublicKey) {
|
export function useLendingObligation(address?: string | PublicKey) {
|
||||||
const id = typeof address === "string" ? address : address?.toBase58();
|
const id = typeof address === 'string' ? address : address?.toBase58();
|
||||||
const [obligationAccount, setObligationAccount] = useState(
|
const [obligationAccount, setObligationAccount] = useState(cache.get(id || '') as ParsedAccount<LendingObligation>);
|
||||||
cache.get(id || "") as ParsedAccount<LendingObligation>
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = cache.emitter.onCache((args) => {
|
const dispose = cache.emitter.onCache((args) => {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { LendingReserve, LendingReserveParser } from "../models/lending";
|
import { LendingReserve, LendingReserveParser } from '../models/lending';
|
||||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
import { useConnectionConfig } from "./../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { getTokenByName, KnownToken } from "../utils/utils";
|
import { getTokenByName, KnownToken } from 'common/src/utils/utils';
|
||||||
|
|
||||||
export const getLendingReserves = () => {
|
export const getLendingReserves = () => {
|
||||||
return cache
|
return cache
|
||||||
|
@ -13,9 +13,7 @@ export const getLendingReserves = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useLendingReserves() {
|
export function useLendingReserves() {
|
||||||
const [reserveAccounts, setReserveAccounts] = useState<
|
const [reserveAccounts, setReserveAccounts] = useState<ParsedAccount<LendingReserve>[]>(getLendingReserves());
|
||||||
ParsedAccount<LendingReserve>[]
|
|
||||||
>(getLendingReserves());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = cache.emitter.onCache((args) => {
|
const dispose = cache.emitter.onCache((args) => {
|
||||||
|
@ -38,26 +36,20 @@ export function useLendingReserve(address?: string | PublicKey) {
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
const { reserveAccounts } = useLendingReserves();
|
const { reserveAccounts } = useLendingReserves();
|
||||||
let addressName = address;
|
let addressName = address;
|
||||||
if (typeof address === "string") {
|
if (typeof address === 'string') {
|
||||||
const token: KnownToken | null = getTokenByName(tokenMap, address);
|
const token: KnownToken | null = getTokenByName(tokenMap, address);
|
||||||
if (token) {
|
if (token) {
|
||||||
const account = reserveAccounts.filter(
|
const account = reserveAccounts.filter((acc) => acc.info.liquidityMint.toBase58() === token.mintAddress)[0];
|
||||||
(acc) => acc.info.liquidityMint.toBase58() === token.mintAddress
|
|
||||||
)[0];
|
|
||||||
if (account) {
|
if (account) {
|
||||||
addressName = account.pubkey;
|
addressName = account.pubkey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const id = useMemo(
|
const id = useMemo(() => (typeof addressName === 'string' ? addressName : addressName?.toBase58()), [addressName]);
|
||||||
() =>
|
|
||||||
typeof addressName === "string" ? addressName : addressName?.toBase58(),
|
|
||||||
[addressName]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [reserveAccount, setReserveAccount] = useState<
|
const [reserveAccount, setReserveAccount] = useState<ParsedAccount<LendingReserve>>(
|
||||||
ParsedAccount<LendingReserve>
|
cache.get(id || '') as ParsedAccount<LendingReserve>
|
||||||
>(cache.get(id || "") as ParsedAccount<LendingReserve>);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = cache.emitter.onCache((args) => {
|
const dispose = cache.emitter.onCache((args) => {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { useConnectionConfig } from "../contexts/connection";
|
|
||||||
import { getTokenName } from "../utils/utils";
|
|
||||||
|
|
||||||
export function useTokenName(mintAddress?: string | PublicKey) {
|
|
||||||
const { tokenMap } = useConnectionConfig();
|
|
||||||
const address =
|
|
||||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58();
|
|
||||||
return getTokenName(tokenMap, address);
|
|
||||||
}
|
|
|
@ -1,19 +1,12 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useMint } from "../contexts/accounts";
|
import { useMint } from 'common/src/contexts/accounts';
|
||||||
import { useMarkets } from "../contexts/market";
|
import { useMarkets } from '../contexts/market';
|
||||||
import { fromLamports } from "../utils/utils";
|
import { fromLamports } from 'common/src/utils/utils';
|
||||||
import { useUserAccounts } from "./useUserAccounts";
|
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||||
|
|
||||||
export function useUserBalance(
|
export function useUserBalance(mintAddress?: PublicKey | string, account?: PublicKey) {
|
||||||
mintAddress?: PublicKey | string,
|
const mint = useMemo(() => (typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58()), [mintAddress]);
|
||||||
account?: PublicKey
|
|
||||||
) {
|
|
||||||
const mint = useMemo(
|
|
||||||
() =>
|
|
||||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58(),
|
|
||||||
[mintAddress]
|
|
||||||
);
|
|
||||||
const { userAccounts } = useUserAccounts();
|
const { userAccounts } = useUserAccounts();
|
||||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||||
|
@ -21,29 +14,19 @@ export function useUserBalance(
|
||||||
const mintInfo = useMint(mint);
|
const mintInfo = useMint(mint);
|
||||||
const accounts = useMemo(() => {
|
const accounts = useMemo(() => {
|
||||||
return userAccounts
|
return userAccounts
|
||||||
.filter(
|
.filter((acc) => mint === acc.info.mint.toBase58() && (!account || account.equals(acc.pubkey)))
|
||||||
(acc) =>
|
|
||||||
mint === acc.info.mint.toBase58() &&
|
|
||||||
(!account || account.equals(acc.pubkey))
|
|
||||||
)
|
|
||||||
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
||||||
}, [userAccounts, mint, account]);
|
}, [userAccounts, mint, account]);
|
||||||
|
|
||||||
const balanceLamports = useMemo(() => {
|
const balanceLamports = useMemo(() => {
|
||||||
return accounts.reduce(
|
return accounts.reduce((res, item) => (res += item.info.amount.toNumber()), 0);
|
||||||
(res, item) => (res += item.info.amount.toNumber()),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}, [accounts]);
|
}, [accounts]);
|
||||||
|
|
||||||
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [
|
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [mintInfo, balanceLamports]);
|
||||||
mintInfo,
|
|
||||||
balanceLamports,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateBalance = () => {
|
const updateBalance = () => {
|
||||||
setBalanceInUSD(balance * midPriceInUSD(mint || ""));
|
setBalanceInUSD(balance * midPriceInUSD(mint || ''));
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispose = marketEmitter.onMarket((args) => {
|
const dispose = marketEmitter.onMarket((args) => {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { cache, ParsedAccount } from "../contexts/accounts";
|
import { cache, ParsedAccount } from 'common/src/contexts/accounts';
|
||||||
import { calculateDepositAPY, LendingReserve } from "../models/lending";
|
import { calculateDepositAPY, LendingReserve } from '../models/lending';
|
||||||
import { useUserAccounts } from "./useUserAccounts";
|
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||||
import { useLendingReserves } from "./useLendingReserves";
|
import { useLendingReserves } from './useLendingReserves';
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { TokenAccount } from "../models";
|
import { TokenAccount } from 'common/src/models';
|
||||||
import { useMarkets } from "../contexts/market";
|
import { useMarkets } from '../contexts/market';
|
||||||
import { fromLamports, getTokenName } from "../utils/utils";
|
import { fromLamports, getTokenName } from 'common/src/utils/utils';
|
||||||
import { useConnectionConfig } from "../contexts/connection";
|
import { useConnectionConfig } from 'common/src/contexts/connection';
|
||||||
import { calculateCollateralBalance } from "./useCollateralBalance";
|
import { calculateCollateralBalance } from './useCollateralBalance';
|
||||||
import { MintInfo } from "@solana/spl-token";
|
import { MintInfo } from '@solana/spl-token';
|
||||||
|
|
||||||
export interface UserDeposit {
|
export interface UserDeposit {
|
||||||
account: TokenAccount;
|
account: TokenAccount;
|
||||||
|
@ -45,28 +45,17 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
||||||
}, [reserveAccounts, exclude, include]);
|
}, [reserveAccounts, exclude, include]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const activeMarkets = new Set(
|
const activeMarkets = new Set(reserveAccounts.map((r) => r.info.dexMarket.toBase58()));
|
||||||
reserveAccounts.map((r) => r.info.dexMarket.toBase58())
|
|
||||||
);
|
|
||||||
|
|
||||||
const userDepositsFactory = () => {
|
const userDepositsFactory = () => {
|
||||||
return userAccounts
|
return userAccounts
|
||||||
.filter((acc) =>
|
.filter((acc) => reservesByCollateralMint.has(acc?.info.mint.toBase58()))
|
||||||
reservesByCollateralMint.has(acc?.info.mint.toBase58())
|
|
||||||
)
|
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const reserve = reservesByCollateralMint.get(
|
const reserve = reservesByCollateralMint.get(item?.info.mint.toBase58()) as ParsedAccount<LendingReserve>;
|
||||||
item?.info.mint.toBase58()
|
|
||||||
) as ParsedAccount<LendingReserve>;
|
|
||||||
|
|
||||||
let collateralMint = cache.get(
|
let collateralMint = cache.get(reserve.info.collateralMint) as ParsedAccount<MintInfo>;
|
||||||
reserve.info.collateralMint
|
|
||||||
) as ParsedAccount<MintInfo>;
|
|
||||||
|
|
||||||
const amountLamports = calculateCollateralBalance(
|
const amountLamports = calculateCollateralBalance(reserve.info, item?.info.amount.toNumber());
|
||||||
reserve.info,
|
|
||||||
item?.info.amount.toNumber()
|
|
||||||
);
|
|
||||||
const amount = fromLamports(amountLamports, collateralMint?.info);
|
const amount = fromLamports(amountLamports, collateralMint?.info);
|
||||||
const price = midPriceInUSD(reserve.info.liquidityMint.toBase58());
|
const price = midPriceInUSD(reserve.info.liquidityMint.toBase58());
|
||||||
const amountInQuote = price * amount;
|
const amountInQuote = price * amount;
|
||||||
|
@ -99,20 +88,10 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
||||||
return () => {
|
return () => {
|
||||||
dispose();
|
dispose();
|
||||||
};
|
};
|
||||||
}, [
|
}, [userAccounts, reserveAccounts, reservesByCollateralMint, tokenMap, midPriceInUSD, marketEmitter]);
|
||||||
userAccounts,
|
|
||||||
reserveAccounts,
|
|
||||||
reservesByCollateralMint,
|
|
||||||
tokenMap,
|
|
||||||
midPriceInUSD,
|
|
||||||
marketEmitter,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userDeposits,
|
userDeposits,
|
||||||
totalInQuote: userDeposits.reduce(
|
totalInQuote: userDeposits.reduce((res, item) => res + item.info.amountInQuote, 0),
|
||||||
(res, item) => res + item.info.amountInQuote,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from 'react';
|
||||||
import { useUserAccounts } from "./useUserAccounts";
|
import { useUserAccounts } from 'common/src/hooks/useUserAccounts';
|
||||||
import { useEnrichedLendingObligations } from "./useEnrichedLendingObligations";
|
import { useEnrichedLendingObligations } from './useEnrichedLendingObligations';
|
||||||
import { TokenAccount } from "../models";
|
import { TokenAccount } from 'common/src/models';
|
||||||
|
|
||||||
export function useUserObligations() {
|
export function useUserObligations() {
|
||||||
const { userAccounts } = useUserAccounts();
|
const { userAccounts } = useUserAccounts();
|
||||||
|
@ -21,26 +21,18 @@ export function useUserObligations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return obligations
|
return obligations
|
||||||
.filter(
|
.filter((acc) => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined)
|
||||||
(acc) => accountsByMint.get(acc.info.tokenMint.toBase58()) !== undefined
|
|
||||||
)
|
|
||||||
.map((ob) => {
|
.map((ob) => {
|
||||||
return {
|
return {
|
||||||
obligation: ob,
|
obligation: ob,
|
||||||
userAccounts: [...accountsByMint.get(ob.info.tokenMint.toBase58())],
|
userAccounts: [...accountsByMint.get(ob.info.tokenMint.toBase58())],
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort(
|
.sort((a, b) => b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote);
|
||||||
(a, b) =>
|
|
||||||
b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote
|
|
||||||
);
|
|
||||||
}, [accountsByMint, obligations]);
|
}, [accountsByMint, obligations]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userObligations,
|
userObligations,
|
||||||
totalInQuote: userObligations.reduce(
|
totalInQuote: userObligations.reduce((result, item) => result + item.obligation.info.borrowedInQuote, 0),
|
||||||
(result, item) => result + item.obligation.info.borrowedInQuote,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { Market, MARKETS, Orderbook } from "@project-serum/serum";
|
import { Market, MARKETS, Orderbook } from '@project-serum/serum';
|
||||||
import { AccountInfo, PublicKey } from "@solana/web3.js";
|
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||||
import {
|
import { MintParser, ParsedAccountBase, cache } from 'common/src/contexts/accounts';
|
||||||
MintParser,
|
|
||||||
ParsedAccountBase,
|
|
||||||
cache,
|
|
||||||
} from "./../../contexts/accounts";
|
|
||||||
|
|
||||||
export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||||
const decoded = Orderbook.LAYOUT.decode(acc.data);
|
const decoded = Orderbook.LAYOUT.decode(acc.data);
|
||||||
|
@ -20,18 +16,11 @@ export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||||
return details;
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_DEX_ID = new PublicKey(
|
const DEFAULT_DEX_ID = new PublicKey('EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o');
|
||||||
"EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o"
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DexMarketParser = (
|
export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||||
pubkey: PublicKey,
|
|
||||||
acc: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const market = MARKETS.find((m) => m.address.equals(pubkey));
|
const market = MARKETS.find((m) => m.address.equals(pubkey));
|
||||||
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(
|
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID).decode(acc.data);
|
||||||
acc.data
|
|
||||||
);
|
|
||||||
|
|
||||||
const details = {
|
const details = {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
export * from "./account";
|
export * from './lending';
|
||||||
export * from "./lending";
|
export * from './pool';
|
||||||
export * from "./tokenSwap";
|
export * from './totals';
|
||||||
export * from "./pool";
|
|
||||||
export * from "./totals";
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import {
|
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||||
PublicKey,
|
import BN from 'bn.js';
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
import * as BufferLayout from 'buffer-layout';
|
||||||
TransactionInstruction,
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
} from "@solana/web3.js";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import BN from "bn.js";
|
import { LendingInstruction } from './lending';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||||
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";
|
|
||||||
|
|
||||||
export enum BorrowAmountType {
|
export enum BorrowAmountType {
|
||||||
LiquidityBorrowAmount = 0,
|
LiquidityBorrowAmount = 0,
|
||||||
|
@ -64,9 +60,9 @@ export const borrowInstruction = (
|
||||||
hostFeeReceiver?: PublicKey
|
hostFeeReceiver?: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([
|
||||||
BufferLayout.u8("instruction"),
|
BufferLayout.u8('instruction'),
|
||||||
Layout.uint64("amount"),
|
Layout.uint64('amount'),
|
||||||
BufferLayout.u8("amountType"),
|
BufferLayout.u8('amountType'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
@ -133,16 +129,12 @@ export const calculateBorrowAPY = (reserve: LendingReserve) => {
|
||||||
const normalizedFactor = currentUtilization / optimalUtilization;
|
const normalizedFactor = currentUtilization / optimalUtilization;
|
||||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||||
const minBorrowRate = reserve.config.minBorrowRate / 100;
|
const minBorrowRate = reserve.config.minBorrowRate / 100;
|
||||||
borrowAPY =
|
borrowAPY = normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
||||||
normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
|
||||||
} else {
|
} else {
|
||||||
const normalizedFactor =
|
const normalizedFactor = (currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
||||||
(currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
|
||||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||||
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
|
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
|
||||||
borrowAPY =
|
borrowAPY = normalizedFactor * (maxBorrowRate - optimalBorrowRate) + optimalBorrowRate;
|
||||||
normalizedFactor * (maxBorrowRate - optimalBorrowRate) +
|
|
||||||
optimalBorrowRate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return borrowAPY;
|
return borrowAPY;
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import {
|
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||||
PublicKey,
|
import BN from 'bn.js';
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
import * as BufferLayout from 'buffer-layout';
|
||||||
TransactionInstruction,
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
} from "@solana/web3.js";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import BN from "bn.js";
|
import { calculateBorrowAPY } from './borrow';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import { LendingInstruction } from './lending';
|
||||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||||
import * as Layout from "./../../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
|
/// Deposit liquidity into a reserve. The output is a collateral token representing ownership
|
||||||
/// of the reserve liquidity pool.
|
/// of the reserve liquidity pool.
|
||||||
|
@ -35,10 +31,7 @@ export const depositInstruction = (
|
||||||
reserveSupply: PublicKey,
|
reserveSupply: PublicKey,
|
||||||
collateralMint: PublicKey
|
collateralMint: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||||
BufferLayout.u8("instruction"),
|
|
||||||
Layout.uint64("liquidityAmount"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
import {
|
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||||
PublicKey,
|
import BN from 'bn.js';
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
import { LendingInstruction } from './lending';
|
||||||
TransactionInstruction,
|
import * as BufferLayout from 'buffer-layout';
|
||||||
} from "@solana/web3.js";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import BN from "bn.js";
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
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";
|
|
||||||
|
|
||||||
/// Purchase collateral tokens at a discount rate if the chosen obligation is unhealthy.
|
/// Purchase collateral tokens at a discount rate if the chosen obligation is unhealthy.
|
||||||
///
|
///
|
||||||
|
@ -43,10 +39,7 @@ export const liquidateInstruction = (
|
||||||
dexOrderBookSide: PublicKey,
|
dexOrderBookSide: PublicKey,
|
||||||
memory: PublicKey
|
memory: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||||
BufferLayout.u8("instruction"),
|
|
||||||
Layout.uint64("liquidityAmount"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import { AccountInfo, PublicKey } from "@solana/web3.js";
|
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import * as BufferLayout from 'buffer-layout';
|
||||||
import * as Layout from "./../../utils/layout";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
|
|
||||||
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||||
[
|
BufferLayout.u8('version'),
|
||||||
BufferLayout.u8("version"),
|
BufferLayout.u8('bumpSeed'),
|
||||||
BufferLayout.u8("bumpSeed"),
|
Layout.publicKey('owner'),
|
||||||
Layout.publicKey("owner"),
|
Layout.publicKey('quoteMint'),
|
||||||
Layout.publicKey("quoteMint"),
|
Layout.publicKey('tokenProgramId'),
|
||||||
Layout.publicKey("tokenProgramId"),
|
|
||||||
|
|
||||||
// extra space for future contract changes
|
// extra space for future contract changes
|
||||||
BufferLayout.blob(62, "padding"),
|
BufferLayout.blob(62, 'padding'),
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
export interface LendingMarket {
|
export interface LendingMarket {
|
||||||
version: number;
|
version: number;
|
||||||
|
@ -27,10 +25,7 @@ export const isLendingMarket = (info: AccountInfo<Buffer>) => {
|
||||||
return info.data.length === LendingMarketLayout.span;
|
return info.data.length === LendingMarketLayout.span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LendingMarketParser = (
|
export const LendingMarketParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
pubKey: PublicKey,
|
|
||||||
info: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
const data = LendingMarketLayout.decode(buffer);
|
const data = LendingMarketLayout.decode(buffer);
|
||||||
|
|
||||||
|
|
|
@ -4,33 +4,31 @@ import {
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
} from "@solana/web3.js";
|
} from '@solana/web3.js';
|
||||||
import BN from "bn.js";
|
import BN from 'bn.js';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import * as BufferLayout from 'buffer-layout';
|
||||||
import { LendingInstruction } from ".";
|
import { LendingInstruction } from '.';
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../utils/ids";
|
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import * as Layout from "./../../utils/layout";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
|
|
||||||
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||||
[
|
BufferLayout.u8('version'),
|
||||||
BufferLayout.u8("version"),
|
/// Amount of collateral tokens deposited for this obligation
|
||||||
/// Amount of collateral tokens deposited for this obligation
|
Layout.uint64('depositedCollateral'),
|
||||||
Layout.uint64("depositedCollateral"),
|
/// Reserve which collateral tokens were deposited into
|
||||||
/// Reserve which collateral tokens were deposited into
|
Layout.publicKey('collateralReserve'),
|
||||||
Layout.publicKey("collateralReserve"),
|
/// Borrow rate used for calculating interest.
|
||||||
/// Borrow rate used for calculating interest.
|
Layout.uint128('cumulativeBorrowRateWad'),
|
||||||
Layout.uint128("cumulativeBorrowRateWad"),
|
/// Amount of tokens borrowed for this obligation plus interest
|
||||||
/// Amount of tokens borrowed for this obligation plus interest
|
Layout.uint128('borrowAmountWad'),
|
||||||
Layout.uint128("borrowAmountWad"),
|
/// Reserve which tokens were borrowed from
|
||||||
/// Reserve which tokens were borrowed from
|
Layout.publicKey('borrowReserve'),
|
||||||
Layout.publicKey("borrowReserve"),
|
/// Mint address of the tokens for this obligation
|
||||||
/// Mint address of the tokens for this obligation
|
Layout.publicKey('tokenMint'),
|
||||||
Layout.publicKey("tokenMint"),
|
|
||||||
|
|
||||||
// extra space for future contract changes
|
// extra space for future contract changes
|
||||||
BufferLayout.blob(128, "padding"),
|
BufferLayout.blob(128, 'padding'),
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
export const isLendingObligation = (info: AccountInfo<Buffer>) => {
|
export const isLendingObligation = (info: AccountInfo<Buffer>) => {
|
||||||
return info.data.length === LendingObligationLayout.span;
|
return info.data.length === LendingObligationLayout.span;
|
||||||
|
@ -47,10 +45,7 @@ export interface LendingObligation {
|
||||||
tokenMint: PublicKey;
|
tokenMint: PublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LendingObligationParser = (
|
export const LendingObligationParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
pubKey: PublicKey,
|
|
||||||
info: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
const data = LendingObligationLayout.decode(buffer);
|
const data = LendingObligationLayout.decode(buffer);
|
||||||
|
|
||||||
|
@ -66,7 +61,7 @@ export const LendingObligationParser = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const healthFactorToRiskColor = (health: number) => {
|
export const healthFactorToRiskColor = (health: number) => {
|
||||||
return "";
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initObligationInstruction = (
|
export const initObligationInstruction = (
|
||||||
|
@ -79,7 +74,7 @@ export const initObligationInstruction = (
|
||||||
lendingMarket: PublicKey,
|
lendingMarket: PublicKey,
|
||||||
lendingMarketAuthority: PublicKey
|
lendingMarketAuthority: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
import {
|
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||||
PublicKey,
|
import BN from 'bn.js';
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
import { LendingInstruction } from './lending';
|
||||||
TransactionInstruction,
|
import * as BufferLayout from 'buffer-layout';
|
||||||
} from "@solana/web3.js";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import BN from "bn.js";
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
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";
|
|
||||||
|
|
||||||
/// Repay loaned tokens to a reserve and receive collateral tokens. The obligation balance
|
/// Repay loaned tokens to a reserve and receive collateral tokens. The obligation balance
|
||||||
/// will be recalculated for interest.
|
/// will be recalculated for interest.
|
||||||
|
@ -42,10 +38,7 @@ export const repayInstruction = (
|
||||||
authority: PublicKey,
|
authority: PublicKey,
|
||||||
transferAuthority: PublicKey
|
transferAuthority: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||||
BufferLayout.u8("instruction"),
|
|
||||||
Layout.uint64("liquidityAmount"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
|
|
@ -4,82 +4,80 @@ import {
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
} from "@solana/web3.js";
|
} from '@solana/web3.js';
|
||||||
import BN from "bn.js";
|
import BN from 'bn.js';
|
||||||
import * as BufferLayout from "buffer-layout";
|
import * as BufferLayout from 'buffer-layout';
|
||||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
import { wadToLamports } from "../../utils/utils";
|
import { wadToLamports } from 'common/src/utils/utils';
|
||||||
import * as Layout from "./../../utils/layout";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import { LendingInstruction } from "./lending";
|
import { LendingInstruction } from './lending';
|
||||||
|
|
||||||
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||||
[
|
BufferLayout.u8('version'),
|
||||||
BufferLayout.u8("version"),
|
Layout.uint64('lastUpdateSlot'),
|
||||||
Layout.uint64("lastUpdateSlot"),
|
|
||||||
|
|
||||||
Layout.publicKey("lendingMarket"),
|
Layout.publicKey('lendingMarket'),
|
||||||
Layout.publicKey("liquidityMint"),
|
Layout.publicKey('liquidityMint'),
|
||||||
BufferLayout.u8("liquidityMintDecimals"),
|
BufferLayout.u8('liquidityMintDecimals'),
|
||||||
Layout.publicKey("liquiditySupply"),
|
Layout.publicKey('liquiditySupply'),
|
||||||
Layout.publicKey("collateralMint"),
|
Layout.publicKey('collateralMint'),
|
||||||
Layout.publicKey("collateralSupply"),
|
Layout.publicKey('collateralSupply'),
|
||||||
|
|
||||||
Layout.publicKey("collateralFeesReceiver"),
|
Layout.publicKey('collateralFeesReceiver'),
|
||||||
|
|
||||||
// TODO: replace u32 option with generic quivalent
|
// TODO: replace u32 option with generic quivalent
|
||||||
BufferLayout.u32("dexMarketOption"),
|
BufferLayout.u32('dexMarketOption'),
|
||||||
Layout.publicKey("dexMarket"),
|
Layout.publicKey('dexMarket'),
|
||||||
|
|
||||||
BufferLayout.struct(
|
BufferLayout.struct(
|
||||||
[
|
[
|
||||||
/// Optimal utilization rate as a percent
|
/// Optimal utilization rate as a percent
|
||||||
BufferLayout.u8("optimalUtilizationRate"),
|
BufferLayout.u8('optimalUtilizationRate'),
|
||||||
/// The ratio of the loan to the value of the collateral as a percent
|
/// The ratio of the loan to the value of the collateral as a percent
|
||||||
BufferLayout.u8("loanToValueRatio"),
|
BufferLayout.u8('loanToValueRatio'),
|
||||||
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
|
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
|
||||||
BufferLayout.u8("liquidationBonus"),
|
BufferLayout.u8('liquidationBonus'),
|
||||||
/// The percent at which an obligation is considered unhealthy
|
/// The percent at which an obligation is considered unhealthy
|
||||||
BufferLayout.u8("liquidationThreshold"),
|
BufferLayout.u8('liquidationThreshold'),
|
||||||
/// Min borrow APY
|
/// Min borrow APY
|
||||||
BufferLayout.u8("minBorrowRate"),
|
BufferLayout.u8('minBorrowRate'),
|
||||||
/// Optimal (utilization) borrow APY
|
/// Optimal (utilization) borrow APY
|
||||||
BufferLayout.u8("optimalBorrowRate"),
|
BufferLayout.u8('optimalBorrowRate'),
|
||||||
/// Max borrow APY
|
/// Max borrow APY
|
||||||
BufferLayout.u8("maxBorrowRate"),
|
BufferLayout.u8('maxBorrowRate'),
|
||||||
|
|
||||||
BufferLayout.struct(
|
BufferLayout.struct(
|
||||||
[
|
[
|
||||||
/// Fee assessed on `BorrowReserveLiquidity`, expressed as a Wad.
|
/// Fee assessed on `BorrowReserveLiquidity`, expressed as a Wad.
|
||||||
/// Must be between 0 and 10^18, such that 10^18 = 1. A few examples for
|
/// Must be between 0 and 10^18, such that 10^18 = 1. A few examples for
|
||||||
/// clarity:
|
/// clarity:
|
||||||
/// 1% = 10_000_000_000_000_000
|
/// 1% = 10_000_000_000_000_000
|
||||||
/// 0.01% (1 basis point) = 100_000_000_000_000
|
/// 0.01% (1 basis point) = 100_000_000_000_000
|
||||||
/// 0.00001% (Aave borrow fee) = 100_000_000_000
|
/// 0.00001% (Aave borrow fee) = 100_000_000_000
|
||||||
Layout.uint64("borrowFeeWad"),
|
Layout.uint64('borrowFeeWad'),
|
||||||
|
|
||||||
/// Amount of fee going to host account, if provided in liquidate and repay
|
/// Amount of fee going to host account, if provided in liquidate and repay
|
||||||
BufferLayout.u8("hostFeePercentage"),
|
BufferLayout.u8('hostFeePercentage'),
|
||||||
],
|
],
|
||||||
"fees"
|
'fees'
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
"config"
|
'config'
|
||||||
),
|
),
|
||||||
|
|
||||||
BufferLayout.struct(
|
BufferLayout.struct(
|
||||||
[
|
[
|
||||||
Layout.uint128("cumulativeBorrowRateWad"),
|
Layout.uint128('cumulativeBorrowRateWad'),
|
||||||
Layout.uint128("borrowedLiquidityWad"),
|
Layout.uint128('borrowedLiquidityWad'),
|
||||||
Layout.uint64("availableLiquidity"),
|
Layout.uint64('availableLiquidity'),
|
||||||
Layout.uint64("collateralMintSupply"),
|
Layout.uint64('collateralMintSupply'),
|
||||||
],
|
],
|
||||||
"state"
|
'state'
|
||||||
),
|
),
|
||||||
|
|
||||||
// extra space for future contract changes
|
// extra space for future contract changes
|
||||||
BufferLayout.blob(300, "padding"),
|
BufferLayout.blob(300, 'padding'),
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
||||||
return info.data.length === LendingReserveLayout.span;
|
return info.data.length === LendingReserveLayout.span;
|
||||||
|
@ -124,10 +122,7 @@ export interface LendingReserve {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LendingReserveParser = (
|
export const LendingReserveParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
pubKey: PublicKey,
|
|
||||||
info: AccountInfo<Buffer>
|
|
||||||
) => {
|
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
const data = LendingReserveLayout.decode(buffer) as LendingReserve;
|
const data = LendingReserveLayout.decode(buffer) as LendingReserve;
|
||||||
|
|
||||||
|
@ -165,9 +160,9 @@ export const initReserveInstruction = (
|
||||||
dexMarket: PublicKey // TODO: optional
|
dexMarket: PublicKey // TODO: optional
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([
|
||||||
BufferLayout.u8("instruction"),
|
BufferLayout.u8('instruction'),
|
||||||
Layout.uint64("liquidityAmount"),
|
Layout.uint64('liquidityAmount'),
|
||||||
BufferLayout.u8("maxUtilizationRate"),
|
BufferLayout.u8('maxUtilizationRate'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
@ -207,10 +202,8 @@ export const initReserveInstruction = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accrueInterestInstruction = (
|
export const accrueInterestInstruction = (...reserveAccount: PublicKey[]): TransactionInstruction => {
|
||||||
...reserveAccount: PublicKey[]
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||||
): TransactionInstruction => {
|
|
||||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
@ -236,50 +229,30 @@ export const accrueInterestInstruction = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calculateUtilizationRatio = (reserve: LendingReserve) => {
|
export const calculateUtilizationRatio = (reserve: LendingReserve) => {
|
||||||
const totalBorrows = wadToLamports(
|
const totalBorrows = wadToLamports(reserve.state.borrowedLiquidityWad).toNumber();
|
||||||
reserve.state.borrowedLiquidityWad
|
const currentUtilization = totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows);
|
||||||
).toNumber();
|
|
||||||
const currentUtilization =
|
|
||||||
totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows);
|
|
||||||
|
|
||||||
return currentUtilization;
|
return currentUtilization;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reserveMarketCap = (reserve?: LendingReserve) => {
|
export const reserveMarketCap = (reserve?: LendingReserve) => {
|
||||||
const available = reserve?.state.availableLiquidity.toNumber() || 0;
|
const available = reserve?.state.availableLiquidity.toNumber() || 0;
|
||||||
const borrowed = wadToLamports(
|
const borrowed = wadToLamports(reserve?.state.borrowedLiquidityWad).toNumber();
|
||||||
reserve?.state.borrowedLiquidityWad
|
|
||||||
).toNumber();
|
|
||||||
const total = available + borrowed;
|
const total = available + borrowed;
|
||||||
|
|
||||||
return total;
|
return total;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const collateralExchangeRate = (reserve?: LendingReserve) => {
|
export const collateralExchangeRate = (reserve?: LendingReserve) => {
|
||||||
return (
|
return (reserve?.state.collateralMintSupply.toNumber() || 1) / reserveMarketCap(reserve);
|
||||||
(reserve?.state.collateralMintSupply.toNumber() || 1) /
|
|
||||||
reserveMarketCap(reserve)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const collateralToLiquidity = (
|
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
|
||||||
collateralAmount: BN | number,
|
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
|
||||||
reserve?: LendingReserve
|
|
||||||
) => {
|
|
||||||
const amount =
|
|
||||||
typeof collateralAmount === "number"
|
|
||||||
? collateralAmount
|
|
||||||
: collateralAmount.toNumber();
|
|
||||||
return Math.floor(amount / collateralExchangeRate(reserve));
|
return Math.floor(amount / collateralExchangeRate(reserve));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const liquidityToCollateral = (
|
export const liquidityToCollateral = (liquidityAmount: BN | number, reserve?: LendingReserve) => {
|
||||||
liquidityAmount: BN | number,
|
const amount = typeof liquidityAmount === 'number' ? liquidityAmount : liquidityAmount.toNumber();
|
||||||
reserve?: LendingReserve
|
|
||||||
) => {
|
|
||||||
const amount =
|
|
||||||
typeof liquidityAmount === "number"
|
|
||||||
? liquidityAmount
|
|
||||||
: liquidityAmount.toNumber();
|
|
||||||
return Math.floor(amount * collateralExchangeRate(reserve));
|
return Math.floor(amount * collateralExchangeRate(reserve));
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
import {
|
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||||
PublicKey,
|
import BN from 'bn.js';
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
import * as BufferLayout from 'buffer-layout';
|
||||||
TransactionInstruction,
|
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from 'common/src/utils/ids';
|
||||||
} from "@solana/web3.js";
|
import * as Layout from 'common/src/utils/layout';
|
||||||
import BN from "bn.js";
|
import { LendingInstruction } from './lending';
|
||||||
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";
|
|
||||||
|
|
||||||
export const withdrawInstruction = (
|
export const withdrawInstruction = (
|
||||||
collateralAmount: number | BN,
|
collateralAmount: number | BN,
|
||||||
|
@ -20,10 +16,7 @@ export const withdrawInstruction = (
|
||||||
authority: PublicKey,
|
authority: PublicKey,
|
||||||
transferAuthority: PublicKey
|
transferAuthority: PublicKey
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('collateralAmount')]);
|
||||||
BufferLayout.u8("instruction"),
|
|
||||||
Layout.uint64("collateralAmount"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { TokenAccount } from "./account";
|
import { TokenAccount } from 'common/src/models/account';
|
||||||
|
|
||||||
export const DEFAULT_DENOMINATOR = 10_000;
|
export const DEFAULT_DENOMINATOR = 10_000;
|
||||||
|
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
import * as BufferLayout from "buffer-layout";
|
|
||||||
import { publicKey, uint64 } from "../utils/layout";
|
|
||||||
|
|
||||||
export { TokenSwap } from "@solana/spl-token-swap";
|
|
||||||
|
|
||||||
const FEE_LAYOUT = BufferLayout.struct(
|
|
||||||
[
|
|
||||||
BufferLayout.nu64("tradeFeeNumerator"),
|
|
||||||
BufferLayout.nu64("tradeFeeDenominator"),
|
|
||||||
BufferLayout.nu64("ownerTradeFeeNumerator"),
|
|
||||||
BufferLayout.nu64("ownerTradeFeeDenominator"),
|
|
||||||
BufferLayout.nu64("ownerWithdrawFeeNumerator"),
|
|
||||||
BufferLayout.nu64("ownerWithdrawFeeDenominator"),
|
|
||||||
BufferLayout.nu64("hostFeeNumerator"),
|
|
||||||
BufferLayout.nu64("hostFeeDenominator"),
|
|
||||||
],
|
|
||||||
"fees"
|
|
||||||
);
|
|
||||||
|
|
||||||
export const TokenSwapLayoutLegacyV0 = BufferLayout.struct([
|
|
||||||
BufferLayout.u8("isInitialized"),
|
|
||||||
BufferLayout.u8("nonce"),
|
|
||||||
publicKey("tokenAccountA"),
|
|
||||||
publicKey("tokenAccountB"),
|
|
||||||
publicKey("tokenPool"),
|
|
||||||
uint64("feesNumerator"),
|
|
||||||
uint64("feesDenominator"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const TokenSwapLayoutV1: typeof BufferLayout.Structure = BufferLayout.struct(
|
|
||||||
[
|
|
||||||
BufferLayout.u8("isInitialized"),
|
|
||||||
BufferLayout.u8("nonce"),
|
|
||||||
publicKey("tokenProgramId"),
|
|
||||||
publicKey("tokenAccountA"),
|
|
||||||
publicKey("tokenAccountB"),
|
|
||||||
publicKey("tokenPool"),
|
|
||||||
publicKey("mintA"),
|
|
||||||
publicKey("mintB"),
|
|
||||||
publicKey("feeAccount"),
|
|
||||||
BufferLayout.u8("curveType"),
|
|
||||||
uint64("tradeFeeNumerator"),
|
|
||||||
uint64("tradeFeeDenominator"),
|
|
||||||
uint64("ownerTradeFeeNumerator"),
|
|
||||||
uint64("ownerTradeFeeDenominator"),
|
|
||||||
uint64("ownerWithdrawFeeNumerator"),
|
|
||||||
uint64("ownerWithdrawFeeDenominator"),
|
|
||||||
BufferLayout.blob(16, "padding"),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const CURVE_NODE = BufferLayout.union(
|
|
||||||
BufferLayout.u8(),
|
|
||||||
BufferLayout.blob(32),
|
|
||||||
"curve"
|
|
||||||
);
|
|
||||||
CURVE_NODE.addVariant(0, BufferLayout.struct([]), "constantProduct");
|
|
||||||
CURVE_NODE.addVariant(
|
|
||||||
1,
|
|
||||||
BufferLayout.struct([BufferLayout.nu64("token_b_price")]),
|
|
||||||
"constantPrice"
|
|
||||||
);
|
|
||||||
CURVE_NODE.addVariant(2, BufferLayout.struct([]), "stable");
|
|
||||||
CURVE_NODE.addVariant(
|
|
||||||
3,
|
|
||||||
BufferLayout.struct([BufferLayout.nu64("token_b_offset")]),
|
|
||||||
"offset"
|
|
||||||
);
|
|
||||||
|
|
||||||
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
|
||||||
[
|
|
||||||
BufferLayout.u8("isInitialized"),
|
|
||||||
BufferLayout.u8("nonce"),
|
|
||||||
publicKey("tokenProgramId"),
|
|
||||||
publicKey("tokenAccountA"),
|
|
||||||
publicKey("tokenAccountB"),
|
|
||||||
publicKey("tokenPool"),
|
|
||||||
publicKey("mintA"),
|
|
||||||
publicKey("mintB"),
|
|
||||||
publicKey("feeAccount"),
|
|
||||||
FEE_LAYOUT,
|
|
||||||
CURVE_NODE,
|
|
||||||
]
|
|
||||||
);
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { HashRouter, Route, Switch } from "react-router-dom";
|
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { WalletProvider } from "./contexts/wallet";
|
import { WalletProvider } from 'common/src/contexts/wallet';
|
||||||
import { ConnectionProvider } from "./contexts/connection";
|
import { ConnectionProvider } from 'common/src/contexts/connection';
|
||||||
import { AccountsProvider } from "./contexts/accounts";
|
import { AccountsProvider } from 'common/src/contexts/accounts';
|
||||||
import { MarketProvider } from "./contexts/market";
|
import { MarketProvider } from './contexts/market';
|
||||||
import { LendingProvider } from "./contexts/lending";
|
import { LendingProvider } from './contexts/lending';
|
||||||
import { AppLayout } from "./components/Layout";
|
import { AppLayout } from './components/Layout';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BorrowReserveView,
|
BorrowReserveView,
|
||||||
|
@ -21,13 +21,13 @@ import {
|
||||||
LiquidateView,
|
LiquidateView,
|
||||||
LiquidateReserveView,
|
LiquidateReserveView,
|
||||||
MarginTrading,
|
MarginTrading,
|
||||||
} from "./views";
|
} from './views';
|
||||||
import { NewPosition } from "./views/margin/newPosition";
|
import { NewPosition } from './views/margin/newPosition';
|
||||||
|
|
||||||
export function Routes() {
|
export function Routes() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HashRouter basename={"/"}>
|
<HashRouter basename={'/'}>
|
||||||
<ConnectionProvider>
|
<ConnectionProvider>
|
||||||
<WalletProvider>
|
<WalletProvider>
|
||||||
<AccountsProvider>
|
<AccountsProvider>
|
||||||
|
@ -35,53 +35,22 @@ export function Routes() {
|
||||||
<LendingProvider>
|
<LendingProvider>
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={() => <HomeView />} />
|
<Route exact path='/' component={() => <HomeView />} />
|
||||||
<Route
|
<Route exact path='/dashboard' children={<DashboardView />} />
|
||||||
exact
|
<Route path='/reserve/:id' children={<ReserveView />} />
|
||||||
path="/dashboard"
|
<Route exact path='/deposit' component={() => <DepositView />} />
|
||||||
children={<DashboardView />}
|
<Route path='/deposit/:id' children={<DepositReserveView />} />
|
||||||
/>
|
<Route path='/withdraw/:id' children={<WithdrawView />} />
|
||||||
<Route path="/reserve/:id" children={<ReserveView />} />
|
<Route exact path='/borrow' children={<BorrowView />} />
|
||||||
<Route
|
<Route path='/borrow/:id' children={<BorrowReserveView />} />
|
||||||
exact
|
<Route path='/repay/loan/:obligation' children={<RepayReserveView />} />
|
||||||
path="/deposit"
|
<Route path='/repay/:reserve' children={<RepayReserveView />} />
|
||||||
component={() => <DepositView />}
|
<Route exact path='/liquidate' children={<LiquidateView />} />
|
||||||
/>
|
<Route path='/liquidate/:id' children={<LiquidateReserveView />} />
|
||||||
<Route
|
<Route exact path='/margin' children={<MarginTrading />} />
|
||||||
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 path='/margin/:id' children={<NewPosition />} />
|
||||||
<Route exact path="/faucet" children={<FaucetView />} />
|
<Route exact path='/faucet' children={<FaucetView />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</LendingProvider>
|
</LendingProvider>
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import { Connection, PublicKey } from '@solana/web3.js';
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { MintLayout, AccountLayout } from "@solana/spl-token";
|
import { MintLayout, AccountLayout } from '@solana/spl-token';
|
||||||
import { programIds } from "./ids";
|
import { programIds } from 'common/src/utils/ids';
|
||||||
import {
|
import { TokenSwapLayout, TokenSwapLayoutLegacyV0 as TokenSwapLayoutV0, TokenSwapLayoutV1 } from 'common/src/models';
|
||||||
PoolInfo,
|
import { PoolInfo } from '../models';
|
||||||
TokenSwapLayout,
|
import { useConnection } from 'common/src/contexts/connection';
|
||||||
TokenSwapLayoutLegacyV0 as TokenSwapLayoutV0,
|
import { cache, getMultipleAccounts, TokenAccountParser } from 'common/src/contexts/accounts';
|
||||||
TokenSwapLayoutV1,
|
|
||||||
} from "./../models";
|
|
||||||
import { useConnection } from "../contexts/connection";
|
|
||||||
import {
|
|
||||||
cache,
|
|
||||||
getMultipleAccounts,
|
|
||||||
TokenAccountParser,
|
|
||||||
} from "../contexts/accounts";
|
|
||||||
|
|
||||||
export const LIQUIDITY_PROVIDER_FEE = 0.003;
|
export const LIQUIDITY_PROVIDER_FEE = 0.003;
|
||||||
export const SERUM_FEE = 0.0005;
|
export const SERUM_FEE = 0.0005;
|
||||||
|
@ -80,16 +72,10 @@ export const usePools = () => {
|
||||||
// TODO: this is not great
|
// TODO: this is not great
|
||||||
// Ideally SwapLayout stores hash of all the mints to make finding of pool for a pair easier
|
// Ideally SwapLayout stores hash of all the mints to make finding of pool for a pair easier
|
||||||
const holdings = await Promise.all(
|
const holdings = await Promise.all(
|
||||||
getHoldings(connection, [
|
getHoldings(connection, [result.data.tokenAccountA, result.data.tokenAccountB])
|
||||||
result.data.tokenAccountA,
|
|
||||||
result.data.tokenAccountB,
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
pool.pubkeys.holdingMints = [
|
pool.pubkeys.holdingMints = [holdings[0].info.mint, holdings[1].info.mint] as PublicKey[];
|
||||||
holdings[0].info.mint,
|
|
||||||
holdings[1].info.mint,
|
|
||||||
] as PublicKey[];
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
@ -100,10 +86,7 @@ export const usePools = () => {
|
||||||
let pool = toPoolInfo(result, swapId);
|
let pool = toPoolInfo(result, swapId);
|
||||||
pool.legacy = isLegacy;
|
pool.legacy = isLegacy;
|
||||||
pool.pubkeys.feeAccount = result.data.feeAccount;
|
pool.pubkeys.feeAccount = result.data.feeAccount;
|
||||||
pool.pubkeys.holdingMints = [
|
pool.pubkeys.holdingMints = [result.data.mintA, result.data.mintB] as PublicKey[];
|
||||||
result.data.mintA,
|
|
||||||
result.data.mintB,
|
|
||||||
] as PublicKey[];
|
|
||||||
|
|
||||||
poolsArray.push(pool as PoolInfo);
|
poolsArray.push(pool as PoolInfo);
|
||||||
}
|
}
|
||||||
|
@ -125,31 +108,28 @@ export const usePools = () => {
|
||||||
|
|
||||||
// This will pre-cache all accounts used by pools
|
// This will pre-cache all accounts used by pools
|
||||||
// All those accounts are updated whenever there is a change
|
// All those accounts are updated whenever there is a change
|
||||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||||
({ keys, array }) => {
|
return array.map((obj, index) => {
|
||||||
return array.map((obj, index) => {
|
const pubKey = keys[index];
|
||||||
const pubKey = keys[index];
|
if (obj.data.length === AccountLayout.span) {
|
||||||
if (obj.data.length === AccountLayout.span) {
|
return cache.add(pubKey, obj, TokenAccountParser);
|
||||||
return cache.add(pubKey, obj, TokenAccountParser);
|
} else if (obj.data.length === MintLayout.span) {
|
||||||
} else if (obj.data.length === MintLayout.span) {
|
if (!cache.getMint(pubKey)) {
|
||||||
if (!cache.getMint(pubKey)) {
|
return cache.addMint(new PublicKey(pubKey), obj);
|
||||||
return cache.addMint(new PublicKey(pubKey), obj);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}) as any[];
|
}) as any[];
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return poolsArray;
|
return poolsArray;
|
||||||
};
|
};
|
||||||
Promise.all([
|
Promise.all([queryPools(programIds().swap), ...programIds().swap_legacy.map((leg) => queryPools(leg, true))]).then(
|
||||||
queryPools(programIds().swap),
|
(all) => {
|
||||||
...programIds().swap_legacy.map((leg) => queryPools(leg, true)),
|
setPools(all.flat());
|
||||||
]).then((all) => {
|
}
|
||||||
setPools(all.flat());
|
);
|
||||||
});
|
|
||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -165,9 +145,7 @@ export const usePools = () => {
|
||||||
pubkey: new PublicKey(id),
|
pubkey: new PublicKey(id),
|
||||||
};
|
};
|
||||||
|
|
||||||
const index =
|
const index = pools && pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
||||||
pools &&
|
|
||||||
pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
|
||||||
if (index && index >= 0 && pools) {
|
if (index && index >= 0 && pools) {
|
||||||
// TODO: check if account is empty?
|
// TODO: check if account is empty?
|
||||||
|
|
||||||
|
@ -177,16 +155,13 @@ export const usePools = () => {
|
||||||
let pool = toPoolInfo(updated, programIds().swap);
|
let pool = toPoolInfo(updated, programIds().swap);
|
||||||
|
|
||||||
pool.pubkeys.feeAccount = updated.data.feeAccount;
|
pool.pubkeys.feeAccount = updated.data.feeAccount;
|
||||||
pool.pubkeys.holdingMints = [
|
pool.pubkeys.holdingMints = [updated.data.mintA, updated.data.mintB] as PublicKey[];
|
||||||
updated.data.mintA,
|
|
||||||
updated.data.mintB,
|
|
||||||
] as PublicKey[];
|
|
||||||
|
|
||||||
setPools([...pools, pool]);
|
setPools([...pools, pool]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"singleGossip"
|
'singleGossip'
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -218,10 +193,7 @@ export const usePoolForBasket = (mints: (string | undefined)[]) => {
|
||||||
for (let i = 0; i < matchingPool.length; i++) {
|
for (let i = 0; i < matchingPool.length; i++) {
|
||||||
const p = matchingPool[i];
|
const p = matchingPool[i];
|
||||||
|
|
||||||
const account = await cache.query(
|
const account = await cache.query(connection, p.pubkeys.holdingAccounts[0]);
|
||||||
connection,
|
|
||||||
p.pubkeys.holdingAccounts[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!account.info.amount.eqn(0)) {
|
if (!account.info.amount.eqn(0)) {
|
||||||
setPool(p);
|
setPool(p);
|
||||||
|
@ -239,9 +211,7 @@ function estimateProceedsFromInput(
|
||||||
proceedsQuantityInPool: number,
|
proceedsQuantityInPool: number,
|
||||||
inputAmount: number
|
inputAmount: number
|
||||||
): number {
|
): number {
|
||||||
return (
|
return (proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount);
|
||||||
(proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimateInputFromProceeds(
|
function estimateInputFromProceeds(
|
||||||
|
@ -250,13 +220,10 @@ function estimateInputFromProceeds(
|
||||||
proceedsAmount: number
|
proceedsAmount: number
|
||||||
): number | string {
|
): number | string {
|
||||||
if (proceedsAmount >= proceedsQuantityInPool) {
|
if (proceedsAmount >= proceedsQuantityInPool) {
|
||||||
return "Not possible";
|
return 'Not possible';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (inputQuantityInPool * proceedsAmount) / (proceedsQuantityInPool - proceedsAmount);
|
||||||
(inputQuantityInPool * proceedsAmount) /
|
|
||||||
(proceedsQuantityInPool - proceedsAmount)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PoolOperation {
|
export enum PoolOperation {
|
||||||
|
@ -273,20 +240,14 @@ export async function calculateDependentAmount(
|
||||||
op: PoolOperation
|
op: PoolOperation
|
||||||
): Promise<number | string | undefined> {
|
): Promise<number | string | undefined> {
|
||||||
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
|
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
|
||||||
const accountA = await cache.query(
|
const accountA = await cache.query(connection, pool.pubkeys.holdingAccounts[0]);
|
||||||
connection,
|
|
||||||
pool.pubkeys.holdingAccounts[0]
|
|
||||||
);
|
|
||||||
const amountA = accountA.info.amount.toNumber();
|
const amountA = accountA.info.amount.toNumber();
|
||||||
|
|
||||||
const accountB = await cache.query(
|
const accountB = await cache.query(connection, pool.pubkeys.holdingAccounts[1]);
|
||||||
connection,
|
|
||||||
pool.pubkeys.holdingAccounts[1]
|
|
||||||
);
|
|
||||||
let amountB = accountB.info.amount.toNumber();
|
let amountB = accountB.info.amount.toNumber();
|
||||||
|
|
||||||
if (!poolMint.mintAuthority) {
|
if (!poolMint.mintAuthority) {
|
||||||
throw new Error("Mint doesnt have authority");
|
throw new Error('Mint doesnt have authority');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poolMint.supply.eqn(0)) {
|
if (poolMint.supply.eqn(0)) {
|
||||||
|
@ -308,14 +269,8 @@ export async function calculateDependentAmount(
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFirstIndependent = accountA.info.mint.toBase58() === independent;
|
const isFirstIndependent = accountA.info.mint.toBase58() === independent;
|
||||||
const depPrecision = Math.pow(
|
const depPrecision = Math.pow(10, isFirstIndependent ? mintB.decimals : mintA.decimals);
|
||||||
10,
|
const indPrecision = Math.pow(10, isFirstIndependent ? mintA.decimals : mintB.decimals);
|
||||||
isFirstIndependent ? mintB.decimals : mintA.decimals
|
|
||||||
);
|
|
||||||
const indPrecision = Math.pow(
|
|
||||||
10,
|
|
||||||
isFirstIndependent ? mintA.decimals : mintB.decimals
|
|
||||||
);
|
|
||||||
const indAdjustedAmount = amount * indPrecision;
|
const indAdjustedAmount = amount * indPrecision;
|
||||||
|
|
||||||
let indBasketQuantity = isFirstIndependent ? amountA : amountB;
|
let indBasketQuantity = isFirstIndependent ? amountA : amountB;
|
||||||
|
@ -330,27 +285,18 @@ export async function calculateDependentAmount(
|
||||||
} else {
|
} else {
|
||||||
switch (+op) {
|
switch (+op) {
|
||||||
case PoolOperation.Add:
|
case PoolOperation.Add:
|
||||||
depAdjustedAmount =
|
depAdjustedAmount = (depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
||||||
(depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
|
||||||
break;
|
break;
|
||||||
case PoolOperation.SwapGivenProceeds:
|
case PoolOperation.SwapGivenProceeds:
|
||||||
depAdjustedAmount = estimateInputFromProceeds(
|
depAdjustedAmount = estimateInputFromProceeds(depBasketQuantity, indBasketQuantity, indAdjustedAmount);
|
||||||
depBasketQuantity,
|
|
||||||
indBasketQuantity,
|
|
||||||
indAdjustedAmount
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case PoolOperation.SwapGivenInput:
|
case PoolOperation.SwapGivenInput:
|
||||||
depAdjustedAmount = estimateProceedsFromInput(
|
depAdjustedAmount = estimateProceedsFromInput(indBasketQuantity, depBasketQuantity, indAdjustedAmount);
|
||||||
indBasketQuantity,
|
|
||||||
depBasketQuantity,
|
|
||||||
indAdjustedAmount
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof depAdjustedAmount === "string") {
|
if (typeof depAdjustedAmount === 'string') {
|
||||||
return depAdjustedAmount;
|
return depAdjustedAmount;
|
||||||
}
|
}
|
||||||
if (depAdjustedAmount === undefined) {
|
if (depAdjustedAmount === undefined) {
|
||||||
|
|
|
@ -1,246 +1,7 @@
|
||||||
import { useCallback, useState } from "react";
|
import { getTokenName, KnownTokenMap } from 'common/src/utils/utils';
|
||||||
import { MintInfo } from "@solana/spl-token";
|
import { PoolInfo } from '../models';
|
||||||
|
|
||||||
import { PoolInfo, TokenAccount } from "./../models";
|
export function getPoolName(map: KnownTokenMap, pool: PoolInfo, shorten = true) {
|
||||||
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
|
|
||||||
) {
|
|
||||||
const sorted = pool.pubkeys.holdingMints.map((a) => a.toBase58()).sort();
|
const sorted = pool.pubkeys.holdingMints.map((a) => a.toBase58()).sort();
|
||||||
return sorted.map((item) => getTokenName(map, item, shorten)).join("/");
|
return sorted.map((item) => getTokenName(map, item, shorten)).join('/');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useTokenName, useBorrowingPower } from "../../hooks";
|
import { useTokenName } from 'common/src/hooks';
|
||||||
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
|
import { useBorrowingPower } from '../../hooks';
|
||||||
import { TokenIcon } from "../../components/TokenIcon";
|
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
|
||||||
import { formatNumber, formatPct } from "../../utils/utils";
|
import { TokenIcon } from '../../components/TokenIcon';
|
||||||
import { Button } from "antd";
|
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||||
import { Link } from "react-router-dom";
|
import { Button } from 'antd';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { Link } from 'react-router-dom';
|
||||||
import { LABELS } from "../../constants";
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { useMidPriceInUSD } from "../../contexts/market";
|
import { LABELS } from '../../constants';
|
||||||
|
import { useMidPriceInUSD } from '../../contexts/market';
|
||||||
|
|
||||||
export const BorrowItem = (props: {
|
export const BorrowItem = (props: { reserve: LendingReserve; address: PublicKey }) => {
|
||||||
reserve: LendingReserve;
|
|
||||||
address: PublicKey;
|
|
||||||
}) => {
|
|
||||||
const name = useTokenName(props.reserve.liquidityMint);
|
const name = useTokenName(props.reserve.liquidityMint);
|
||||||
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
|
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
|
||||||
|
|
||||||
|
@ -22,8 +20,8 @@ export const BorrowItem = (props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/borrow/${props.address.toBase58()}`}>
|
<Link to={`/borrow/${props.address.toBase58()}`}>
|
||||||
<div className="borrow-item">
|
<div className='borrow-item'>
|
||||||
<span style={{ display: "flex" }}>
|
<span style={{ display: 'flex' }}>
|
||||||
<TokenIcon mintAddress={props.reserve.liquidityMint} />
|
<TokenIcon mintAddress={props.reserve.liquidityMint} />
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
|
@ -33,14 +31,12 @@ export const BorrowItem = (props: {
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(totalInQuote)}</div>
|
||||||
${formatNumber.format(totalInQuote)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{formatPct.format(apr)}</div>
|
<div>{formatPct.format(apr)}</div>
|
||||||
<div>
|
<div>
|
||||||
<Button type="primary">
|
<Button type='primary'>
|
||||||
<span>{LABELS.BORROW_ACTION}</span>
|
<span>{LABELS.BORROW_ACTION}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Card } from "antd";
|
import { Card } from 'antd';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { BarChartStatistic } from "../../../components/BarChartStatistic";
|
import { BarChartStatistic } from '../../../components/BarChartStatistic';
|
||||||
import { LABELS } from "../../../constants";
|
import { LABELS } from '../../../constants';
|
||||||
import { formatNumber } from "../../../utils/utils";
|
import { formatNumber } from 'common/src/utils/utils';
|
||||||
import { useUserDeposits } from "./../../../hooks";
|
import { useUserDeposits } from './../../../hooks';
|
||||||
import { DepositItem } from "./item";
|
import { DepositItem } from './item';
|
||||||
|
|
||||||
export const DashboardDeposits = () => {
|
export const DashboardDeposits = () => {
|
||||||
const { userDeposits, totalInQuote } = useUserDeposits();
|
const { userDeposits, totalInQuote } = useUserDeposits();
|
||||||
|
@ -12,11 +12,10 @@ export const DashboardDeposits = () => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={
|
title={
|
||||||
<div className="dashboard-title">
|
<div className='dashboard-title'>
|
||||||
<div>{LABELS.DASHBOARD_TITLE_DEPOSITS}</div>
|
<div>{LABELS.DASHBOARD_TITLE_DEPOSITS}</div>
|
||||||
<div>
|
<div>
|
||||||
<span>{LABELS.TOTAL_TITLE}: </span>$
|
<span>{LABELS.TOTAL_TITLE}: </span>${formatNumber.format(totalInQuote)}
|
||||||
{formatNumber.format(totalInQuote)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -26,17 +25,14 @@ export const DashboardDeposits = () => {
|
||||||
getPct={(item) => item.info.amountInQuote / totalInQuote}
|
getPct={(item) => item.info.amountInQuote / totalInQuote}
|
||||||
name={(item) => item.info.name}
|
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_ASSET}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_DEPOSIT_BALANCE}</div>
|
<div>{LABELS.TABLE_TITLE_DEPOSIT_BALANCE}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_APY}</div>
|
<div>{LABELS.TABLE_TITLE_APY}</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
{userDeposits.map((deposit) => (
|
{userDeposits.map((deposit) => (
|
||||||
<DepositItem
|
<DepositItem key={deposit.account.pubkey.toBase58()} userDeposit={deposit} />
|
||||||
key={deposit.account.pubkey.toBase58()}
|
|
||||||
userDeposit={deposit}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from 'react';
|
||||||
import { useTokenName, UserDeposit } from "../../../hooks";
|
import { useTokenName } from 'common/src/hooks';
|
||||||
import { calculateDepositAPY } from "../../../models/lending";
|
import { UserDeposit } from '../../../hooks';
|
||||||
import { TokenIcon } from "../../../components/TokenIcon";
|
import { calculateDepositAPY } from '../../../models/lending';
|
||||||
import { formatNumber, formatPct } from "../../../utils/utils";
|
import { TokenIcon } from '../../../components/TokenIcon';
|
||||||
import { Button } from "antd";
|
import { formatNumber, formatPct } from 'common/src/utils/utils';
|
||||||
import { Link } from "react-router-dom";
|
import { Button } from 'antd';
|
||||||
import { LABELS } from "../../../constants";
|
import { Link } from 'react-router-dom';
|
||||||
|
import { LABELS } from '../../../constants';
|
||||||
|
|
||||||
export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
||||||
const { reserve, info } = props.userDeposit;
|
const { reserve, info } = props.userDeposit;
|
||||||
const mintAddress = reserve.info.liquidityMint;
|
const mintAddress = reserve.info.liquidityMint;
|
||||||
const name = useTokenName(mintAddress);
|
const name = useTokenName(mintAddress);
|
||||||
|
|
||||||
const depositAPY = useMemo(() => calculateDepositAPY(reserve.info), [
|
const depositAPY = useMemo(() => calculateDepositAPY(reserve.info), [reserve]);
|
||||||
reserve,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-item">
|
<div className='dashboard-item'>
|
||||||
<span style={{ display: "flex" }}>
|
<span style={{ display: 'flex' }}>
|
||||||
<TokenIcon mintAddress={mintAddress} />
|
<TokenIcon mintAddress={mintAddress} />
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
|
@ -27,20 +26,18 @@ export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
||||||
<div>
|
<div>
|
||||||
<em>{formatNumber.format(info.amount)}</em> {name}
|
<em>{formatNumber.format(info.amount)}</em> {name}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-amount-quote">
|
<div className='dashboard-amount-quote'>${formatNumber.format(info.amountInQuote)}</div>
|
||||||
${formatNumber.format(info.amountInQuote)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{formatPct.format(depositAPY)}</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()}`}>
|
<Link to={`/deposit/${reserve.pubkey.toBase58()}`}>
|
||||||
<Button type="primary">
|
<Button type='primary'>
|
||||||
<span>{LABELS.DEPOSIT_ACTION}</span>
|
<span>{LABELS.DEPOSIT_ACTION}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/withdraw/${reserve.pubkey.toBase58()}`}>
|
<Link to={`/withdraw/${reserve.pubkey.toBase58()}`}>
|
||||||
<Button type="text">
|
<Button type='text'>
|
||||||
<span>{LABELS.WITHDRAW_ACTION}</span>
|
<span>{LABELS.WITHDRAW_ACTION}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue