mirror of https://github.com/certusone/oyster.git
feat: switch to use bbo. resolves #42
This commit is contained in:
parent
01bae0f1c8
commit
f3a5b4bc2a
|
@ -1,8 +1,17 @@
|
|||
import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
|
||||
import { Account, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT } from '../utils/ids';
|
||||
import { LendingObligationLayout, TokenAccount } from '../models';
|
||||
import { cache, TokenAccountParser } from './../contexts/accounts';
|
||||
import { AccountLayout, MintLayout, Token } from "@solana/spl-token";
|
||||
import {
|
||||
Account,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
LENDING_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
WRAPPED_SOL_MINT,
|
||||
} from "../utils/ids";
|
||||
import { LendingObligationLayout, TokenAccount } from "../models";
|
||||
import { cache, TokenAccountParser } from "./../contexts/accounts";
|
||||
|
||||
export function ensureSplAccount(
|
||||
instructions: TransactionInstruction[],
|
||||
|
@ -16,11 +25,31 @@ export function ensureSplAccount(
|
|||
return toCheck.pubkey;
|
||||
}
|
||||
|
||||
const account = createUninitializedAccount(instructions, payer, amount, signers);
|
||||
const account = createUninitializedAccount(
|
||||
instructions,
|
||||
payer,
|
||||
amount,
|
||||
signers
|
||||
);
|
||||
|
||||
instructions.push(Token.createInitAccountInstruction(TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT, account, payer));
|
||||
instructions.push(
|
||||
Token.createInitAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
WRAPPED_SOL_MINT,
|
||||
account,
|
||||
payer
|
||||
)
|
||||
);
|
||||
|
||||
cleanupInstructions.push(Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, account, payer, payer, []));
|
||||
cleanupInstructions.push(
|
||||
Token.createCloseAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
account,
|
||||
payer,
|
||||
payer,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
@ -125,9 +154,16 @@ export function createTokenAccount(
|
|||
owner: PublicKey,
|
||||
signers: Account[]
|
||||
) {
|
||||
const account = createUninitializedAccount(instructions, payer, accountRentExempt, signers);
|
||||
const account = createUninitializedAccount(
|
||||
instructions,
|
||||
payer,
|
||||
accountRentExempt,
|
||||
signers
|
||||
);
|
||||
|
||||
instructions.push(Token.createInitAccountInstruction(TOKEN_PROGRAM_ID, mint, account, owner));
|
||||
instructions.push(
|
||||
Token.createInitAccountInstruction(TOKEN_PROGRAM_ID, mint, account, owner)
|
||||
);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
@ -161,10 +197,25 @@ export function findOrCreateAccountByMint(
|
|||
toAccount = account.pubkey;
|
||||
} else {
|
||||
// creating depositor pool account
|
||||
toAccount = createTokenAccount(instructions, payer, accountRentExempt, mint, owner, signers);
|
||||
toAccount = createTokenAccount(
|
||||
instructions,
|
||||
payer,
|
||||
accountRentExempt,
|
||||
mint,
|
||||
owner,
|
||||
signers
|
||||
);
|
||||
|
||||
if (isWrappedSol) {
|
||||
cleanupInstructions.push(Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, toAccount, payer, payer, []));
|
||||
cleanupInstructions.push(
|
||||
Token.createCloseAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
toAccount,
|
||||
payer,
|
||||
payer,
|
||||
[]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import { accrueInterestInstruction, LendingReserve } from "./../models/lending/reserve";
|
||||
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 {
|
||||
|
@ -16,8 +19,8 @@ import {
|
|||
createUninitializedObligation,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from './account';
|
||||
import { cache, MintParser, ParsedAccount } from '../contexts/accounts';
|
||||
} from "./account";
|
||||
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
||||
import {
|
||||
TokenAccount,
|
||||
LendingObligationLayout,
|
||||
|
@ -47,9 +50,9 @@ export const borrow = async (
|
|||
obligationAccount?: PublicKey
|
||||
) => {
|
||||
notify({
|
||||
message: 'Borrowing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Borrowing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
let signers: Account[] = [];
|
||||
|
@ -61,14 +64,18 @@ export const borrow = async (
|
|||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
|
||||
const obligation = existingObligation
|
||||
? existingObligation.pubkey
|
||||
: createUninitializedObligation(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
await connection.getMinimumBalanceForRentExemption(LendingObligationLayout.span),
|
||||
await connection.getMinimumBalanceForRentExemption(
|
||||
LendingObligationLayout.span
|
||||
),
|
||||
signers
|
||||
);
|
||||
|
||||
|
@ -83,7 +90,12 @@ export const borrow = async (
|
|||
|
||||
const obligationTokenOutput = obligationAccount
|
||||
? obligationAccount
|
||||
: createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||
: createUninitializedAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
signers
|
||||
);
|
||||
|
||||
let toAccount = await findOrCreateAccountByMint(
|
||||
wallet.publicKey,
|
||||
|
@ -105,7 +117,7 @@ export const borrow = async (
|
|||
obligationTokenOutput,
|
||||
wallet.publicKey,
|
||||
depositReserve.info.lendingMarket,
|
||||
authority,
|
||||
authority
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -125,19 +137,21 @@ export const borrow = async (
|
|||
|
||||
if (instructions.length > 0) {
|
||||
// create all accounts in one transaction
|
||||
let tx = await sendTransaction(connection, wallet, instructions, [...signers]);
|
||||
let tx = await sendTransaction(connection, wallet, instructions, [
|
||||
...signers,
|
||||
]);
|
||||
|
||||
notify({
|
||||
message: 'Obligation accounts created',
|
||||
message: "Obligation accounts created",
|
||||
description: `Transaction ${tx}`,
|
||||
type: 'success',
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
|
||||
notify({
|
||||
message: 'Borrowing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Borrowing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
signers = [];
|
||||
|
@ -153,15 +167,19 @@ export const borrow = async (
|
|||
|
||||
fromLamports = approvedAmount - accountRentExempt;
|
||||
|
||||
const mint = (await cache.query(connection, borrowReserve.info.liquidityMint, MintParser)) as ParsedAccount<
|
||||
MintInfo
|
||||
>;
|
||||
const mint = (await cache.query(
|
||||
connection,
|
||||
borrowReserve.info.liquidityMint,
|
||||
MintParser
|
||||
)) as ParsedAccount<MintInfo>;
|
||||
|
||||
amountLamports = toLamports(amount, mint?.info);
|
||||
} else if (amountType === BorrowAmountType.CollateralDepositAmount) {
|
||||
const mint = (await cache.query(connection, depositReserve.info.collateralMint, MintParser)) as ParsedAccount<
|
||||
MintInfo
|
||||
>;
|
||||
const mint = (await cache.query(
|
||||
connection,
|
||||
depositReserve.info.collateralMint,
|
||||
MintParser
|
||||
)) as ParsedAccount<MintInfo>;
|
||||
amountLamports = toLamports(amount, mint?.info);
|
||||
fromLamports = amountLamports;
|
||||
}
|
||||
|
@ -194,18 +212,24 @@ export const borrow = async (
|
|||
throw new Error(`Dex market doesn't exist.`);
|
||||
}
|
||||
|
||||
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(depositReserve.info.liquidityMint)
|
||||
const market = cache.get(depositReserve.info.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
||||
depositReserve.info.liquidityMint
|
||||
)
|
||||
? dexMarket?.info.asks
|
||||
: dexMarket?.info.bids;
|
||||
|
||||
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||
const memory = createTempMemoryAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
signers,
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(
|
||||
depositReserve.pubkey,
|
||||
borrowReserve.pubkey,
|
||||
)
|
||||
accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey)
|
||||
);
|
||||
|
||||
// borrow
|
||||
|
@ -235,19 +259,25 @@ export const borrow = async (
|
|||
|
||||
memory,
|
||||
|
||||
hostFeeReceiver,
|
||||
hostFeeReceiver
|
||||
)
|
||||
);
|
||||
try {
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Funds borrowed.',
|
||||
type: 'success',
|
||||
message: "Funds borrowed.",
|
||||
type: "success",
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
console.error(ex);
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -30,9 +30,9 @@ export const deposit = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: 'Depositing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Depositing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
const isInitalized = true; // TODO: finish reserve init
|
||||
|
@ -42,7 +42,9 @@ export const deposit = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[reserve.lendingMarket.toBuffer()], // which account should be authority
|
||||
|
@ -82,15 +84,16 @@ export const deposit = async (
|
|||
signers
|
||||
);
|
||||
} else {
|
||||
toAccount = createUninitializedAccount(instructions, wallet.publicKey, accountRentExempt, signers);
|
||||
toAccount = createUninitializedAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
signers
|
||||
);
|
||||
}
|
||||
|
||||
if (isInitalized) {
|
||||
instructions.push(
|
||||
accrueInterestInstruction(
|
||||
reserveAddress,
|
||||
)
|
||||
);
|
||||
instructions.push(accrueInterestInstruction(reserveAddress));
|
||||
|
||||
// deposit
|
||||
instructions.push(
|
||||
|
@ -129,11 +132,17 @@ export const deposit = async (
|
|||
}
|
||||
|
||||
try {
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Funds deposited.',
|
||||
type: 'success',
|
||||
message: "Funds deposited.",
|
||||
type: "success",
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch {
|
||||
|
|
|
@ -6,12 +6,24 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import { accrueInterestInstruction, LendingReserve } from "./../models/lending/reserve";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending/reserve";
|
||||
import { liquidateInstruction } from "./../models/lending/liquidate";
|
||||
import { AccountLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
||||
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account";
|
||||
import { approve, LendingMarket, LendingObligation, TokenAccount } from "../models";
|
||||
import {
|
||||
createTempMemoryAccount,
|
||||
ensureSplAccount,
|
||||
findOrCreateAccountByMint,
|
||||
} from "./account";
|
||||
import {
|
||||
approve,
|
||||
LendingMarket,
|
||||
LendingObligation,
|
||||
TokenAccount,
|
||||
} from "../models";
|
||||
import { cache, ParsedAccount } from "../contexts/accounts";
|
||||
|
||||
export const liquidate = async (
|
||||
|
@ -28,9 +40,9 @@ export const liquidate = async (
|
|||
withdrawReserve: ParsedAccount<LendingReserve>
|
||||
) => {
|
||||
notify({
|
||||
message: 'Repaying funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Repaying funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -38,7 +50,9 @@ export const liquidate = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[repayReserve.info.lendingMarket.toBuffer()],
|
||||
|
@ -84,19 +98,25 @@ export const liquidate = async (
|
|||
throw new Error(`Dex market doesn't exist.`);
|
||||
}
|
||||
|
||||
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
const market = cache.get(withdrawReserve.info.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(repayReserve.info.liquidityMint)
|
||||
const dexOrderBookSide = market.info.quoteMint.equals(
|
||||
repayReserve.info.liquidityMint
|
||||
)
|
||||
? dexMarket?.info.asks
|
||||
: dexMarket?.info.bids;
|
||||
|
||||
const memory = createTempMemoryAccount(instructions, wallet.publicKey, signers, LENDING_PROGRAM_ID);
|
||||
const memory = createTempMemoryAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
signers,
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(
|
||||
repayReserve.pubkey,
|
||||
withdrawReserve.pubkey,
|
||||
)
|
||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
|
@ -118,11 +138,17 @@ export const liquidate = async (
|
|||
)
|
||||
);
|
||||
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Funds liquidated.',
|
||||
type: 'success',
|
||||
message: "Funds liquidated.",
|
||||
type: "success",
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,7 +6,10 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import { accrueInterestInstruction, LendingReserve } from "./../models/lending/reserve";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
} from "./../models/lending/reserve";
|
||||
import { repayInstruction } from "./../models/lending/repay";
|
||||
import { AccountLayout, Token, NATIVE_MINT } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../utils/ids";
|
||||
|
@ -31,9 +34,9 @@ export const repay = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: 'Repaying funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Repaying funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -41,7 +44,9 @@ export const repay = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[repayReserve.info.lendingMarket.toBuffer()],
|
||||
|
@ -49,9 +54,27 @@ export const repay = async (
|
|||
);
|
||||
|
||||
let fromAccount = from.pubkey;
|
||||
if (wallet.publicKey.equals(fromAccount) && repayReserve.info.liquidityMint.equals(NATIVE_MINT)) {
|
||||
fromAccount = createTokenAccount(instructions, wallet.publicKey, accountRentExempt + repayAmount, NATIVE_MINT, wallet.publicKey, signers);
|
||||
cleanupInstructions.push(Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, fromAccount, wallet.publicKey, wallet.publicKey, []));
|
||||
if (
|
||||
wallet.publicKey.equals(fromAccount) &&
|
||||
repayReserve.info.liquidityMint.equals(NATIVE_MINT)
|
||||
) {
|
||||
fromAccount = createTokenAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt + repayAmount,
|
||||
NATIVE_MINT,
|
||||
wallet.publicKey,
|
||||
signers
|
||||
);
|
||||
cleanupInstructions.push(
|
||||
Token.createCloseAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
fromAccount,
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// create approval for transfer transactions
|
||||
|
@ -84,14 +107,11 @@ export const repay = async (
|
|||
obligationToken.info.amount.toNumber(),
|
||||
|
||||
// reuse transfer authority
|
||||
transferAuthority.publicKey,
|
||||
transferAuthority.publicKey
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(
|
||||
repayReserve.pubkey,
|
||||
withdrawReserve.pubkey,
|
||||
)
|
||||
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey)
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
|
@ -108,15 +128,21 @@ export const repay = async (
|
|||
obligationToken.pubkey,
|
||||
repayReserve.info.lendingMarket,
|
||||
authority,
|
||||
transferAuthority.publicKey,
|
||||
transferAuthority.publicKey
|
||||
)
|
||||
);
|
||||
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Funds repaid.',
|
||||
type: 'success',
|
||||
message: "Funds repaid.",
|
||||
type: "success",
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,7 +6,11 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { sendTransaction } from "../contexts/connection";
|
||||
import { notify } from "../utils/notifications";
|
||||
import { accrueInterestInstruction, LendingReserve, withdrawInstruction } from "./../models/lending";
|
||||
import {
|
||||
accrueInterestInstruction,
|
||||
LendingReserve,
|
||||
withdrawInstruction,
|
||||
} from "./../models/lending";
|
||||
import { AccountLayout } from "@solana/spl-token";
|
||||
import { LENDING_PROGRAM_ID } from "../utils/ids";
|
||||
import { findOrCreateAccountByMint } from "./account";
|
||||
|
@ -21,9 +25,9 @@ export const withdraw = async (
|
|||
wallet: any
|
||||
) => {
|
||||
notify({
|
||||
message: 'Withdrawing funds...',
|
||||
description: 'Please review transactions to approve.',
|
||||
type: 'warn',
|
||||
message: "Withdrawing funds...",
|
||||
description: "Please review transactions to approve.",
|
||||
type: "warn",
|
||||
});
|
||||
|
||||
// user from account
|
||||
|
@ -31,9 +35,14 @@ export const withdraw = async (
|
|||
const instructions: TransactionInstruction[] = [];
|
||||
const cleanupInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
|
||||
const [authority] = await PublicKey.findProgramAddress([reserve.lendingMarket.toBuffer()], LENDING_PROGRAM_ID);
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[reserve.lendingMarket.toBuffer()],
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const fromAccount = from.pubkey;
|
||||
|
||||
|
@ -59,11 +68,7 @@ export const withdraw = async (
|
|||
signers
|
||||
);
|
||||
|
||||
instructions.push(
|
||||
accrueInterestInstruction(
|
||||
reserveAddress
|
||||
)
|
||||
);
|
||||
instructions.push(accrueInterestInstruction(reserveAddress));
|
||||
|
||||
instructions.push(
|
||||
withdrawInstruction(
|
||||
|
@ -75,16 +80,22 @@ export const withdraw = async (
|
|||
reserve.liquiditySupply,
|
||||
reserve.lendingMarket,
|
||||
authority,
|
||||
transferAuthority.publicKey,
|
||||
transferAuthority.publicKey
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
let tx = await sendTransaction(connection, wallet, instructions.concat(cleanupInstructions), signers, true);
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions.concat(cleanupInstructions),
|
||||
signers,
|
||||
true
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Funds deposited.',
|
||||
type: 'success',
|
||||
message: "Funds deposited.",
|
||||
type: "success",
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
} catch {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Statistic } from "antd";
|
||||
import React, { } from "react";
|
||||
import React from "react";
|
||||
|
||||
export const BarChartStatistic = <T, >(props: {
|
||||
export const BarChartStatistic = <T,>(props: {
|
||||
items: T[];
|
||||
title?: string;
|
||||
name: (item: T) => string;
|
||||
|
@ -9,34 +9,47 @@ export const BarChartStatistic = <T, >(props: {
|
|||
getPct: (item: T) => number;
|
||||
}) => {
|
||||
const colors = [
|
||||
'#003f5c',
|
||||
'#2f4b7c',
|
||||
'#665191',
|
||||
'#a05195',
|
||||
'#d45087',
|
||||
'#f95d6a',
|
||||
'#ff7c43',
|
||||
'#ffa600',
|
||||
"#003f5c",
|
||||
"#2f4b7c",
|
||||
"#665191",
|
||||
"#a05195",
|
||||
"#d45087",
|
||||
"#f95d6a",
|
||||
"#ff7c43",
|
||||
"#ffa600",
|
||||
].reverse();
|
||||
|
||||
return (
|
||||
<Statistic
|
||||
title={props.title}
|
||||
valueRender={() =>
|
||||
<div style={{ width: '100%', height: 37, display: 'flex', backgroundColor: 'lightgrey',
|
||||
valueRender={() => (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 37,
|
||||
display: "flex",
|
||||
backgroundColor: "lightgrey",
|
||||
fontSize: 12,
|
||||
lineHeight: '37px', }}>
|
||||
{props.items.map((item, i) =>
|
||||
<div key={props.name(item)}
|
||||
lineHeight: "37px",
|
||||
}}
|
||||
>
|
||||
{props.items.map((item, i) => (
|
||||
<div
|
||||
key={props.name(item)}
|
||||
title={props.name(item)}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
width: `${100 * props.getPct(item)}%` ,
|
||||
backgroundColor: (props.color && props.color(item)) || colors[i % props.items.length] }} >
|
||||
{props.name(item)}
|
||||
</div>)}
|
||||
</div>}
|
||||
width: `${100 * props.getPct(item)}%`,
|
||||
backgroundColor:
|
||||
(props.color && props.color(item)) ||
|
||||
colors[i % props.items.length],
|
||||
}}
|
||||
>
|
||||
</Statistic>
|
||||
{props.name(item)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
></Statistic>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -157,7 +157,9 @@ export const BorrowInput = (props: {
|
|||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
>
|
||||
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.BORROW_ACTION}
|
||||
{fromAccounts.length === 0
|
||||
? LABELS.NO_DEPOSITS
|
||||
: LABELS.BORROW_ACTION}
|
||||
</ConnectButton>
|
||||
<BackButton />
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { cache, ParsedAccount } from '../../contexts/accounts';
|
||||
import { useConnectionConfig } from '../../contexts/connection';
|
||||
import { useLendingReserves, useUserDeposits } from '../../hooks';
|
||||
import { LendingReserve, LendingMarket, LendingReserveParser } from '../../models';
|
||||
import { getTokenName } from '../../utils/utils';
|
||||
import { Card, Select } from 'antd';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
import { NumericInput } from '../Input/numeric';
|
||||
import './style.less';
|
||||
import { TokenDisplay } from '../TokenDisplay';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import { useLendingReserves, useUserDeposits } from "../../hooks";
|
||||
import {
|
||||
LendingReserve,
|
||||
LendingMarket,
|
||||
LendingReserveParser,
|
||||
} from "../../models";
|
||||
import { getTokenName } from "../../utils/utils";
|
||||
import { Card, Select } from "antd";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
import { NumericInput } from "../Input/numeric";
|
||||
import "./style.less";
|
||||
import { TokenDisplay } from "../TokenDisplay";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -30,36 +34,49 @@ export default function CollateralInput(props: {
|
|||
const { tokenMap } = useConnectionConfig();
|
||||
const [collateralReserve, setCollateralReserve] = useState<string>();
|
||||
const [balance, setBalance] = useState<number>(0);
|
||||
const [lastAmount, setLastAmount] = useState<string>('');
|
||||
const [lastAmount, setLastAmount] = useState<string>("");
|
||||
const userDeposits = useUserDeposits();
|
||||
|
||||
useEffect(() => {
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserve) || '';
|
||||
const id: string =
|
||||
cache
|
||||
.byParser(LendingReserveParser)
|
||||
.find((acc) => acc === collateralReserve) || "";
|
||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
if (parser) {
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() === parser.info.liquidityMint.toBase58()
|
||||
(u) =>
|
||||
u.reserve.info.liquidityMint.toBase58() ===
|
||||
parser.info.liquidityMint.toBase58()
|
||||
);
|
||||
if (collateralDeposit) setBalance(collateralDeposit.info.amount);
|
||||
else setBalance(0);
|
||||
}
|
||||
}, [collateralReserve, userDeposits]);
|
||||
|
||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
if (!market) return null;
|
||||
|
||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
|
||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(
|
||||
market?.info?.quoteMint
|
||||
);
|
||||
|
||||
const renderReserveAccounts = reserveAccounts
|
||||
.filter((reserve) => reserve.info !== props.reserve)
|
||||
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint))
|
||||
.filter(
|
||||
(reserve) =>
|
||||
!onlyQuoteAllowed ||
|
||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
||||
)
|
||||
.map((reserve) => {
|
||||
const mint = reserve.info.liquidityMint.toBase58();
|
||||
const address = reserve.pubkey.toBase58();
|
||||
const name = getTokenName(tokenMap, mint);
|
||||
return (
|
||||
<Option key={address} value={address} name={name} title={address}>
|
||||
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div key={address} style={{ display: "flex", alignItems: "center" }}>
|
||||
<TokenIcon mintAddress={mint} />
|
||||
{name}
|
||||
</div>
|
||||
|
@ -68,19 +85,30 @@ export default function CollateralInput(props: {
|
|||
});
|
||||
|
||||
return (
|
||||
<Card className='ccy-input' style={{ borderRadius: 20 }} bodyStyle={{ padding: 0 }}>
|
||||
<div className='ccy-input-header'>
|
||||
<div className='ccy-input-header-left'>{props.title}</div>
|
||||
<Card
|
||||
className="ccy-input"
|
||||
style={{ borderRadius: 20 }}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<div className="ccy-input-header">
|
||||
<div className="ccy-input-header-left">{props.title}</div>
|
||||
|
||||
{!props.hideBalance && (
|
||||
<div className='ccy-input-header-right' onClick={(e) => props.onInputChange && props.onInputChange(balance)}>
|
||||
<div
|
||||
className="ccy-input-header-right"
|
||||
onClick={(e) => props.onInputChange && props.onInputChange(balance)}
|
||||
>
|
||||
Balance: {balance.toFixed(6)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='ccy-input-header' style={{ padding: '0px 10px 5px 7px' }}>
|
||||
<div className="ccy-input-header" style={{ padding: "0px 10px 5px 7px" }}>
|
||||
<NumericInput
|
||||
value={parseFloat(lastAmount || '0.00') === props.amount ? lastAmount : props.amount?.toFixed(6)?.toString()}
|
||||
value={
|
||||
parseFloat(lastAmount || "0.00") === props.amount
|
||||
? lastAmount
|
||||
: props.amount?.toFixed(6)?.toString()
|
||||
}
|
||||
onChange={(val: string) => {
|
||||
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||
|
@ -90,19 +118,19 @@ export default function CollateralInput(props: {
|
|||
}}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
boxShadow: 'none',
|
||||
borderColor: 'transparent',
|
||||
outline: 'transparent',
|
||||
boxShadow: "none",
|
||||
borderColor: "transparent",
|
||||
outline: "transparent",
|
||||
}}
|
||||
placeholder='0.00'
|
||||
placeholder="0.00"
|
||||
/>
|
||||
<div className='ccy-input-header-right' style={{ display: 'flex' }}>
|
||||
<div className="ccy-input-header-right" style={{ display: "flex" }}>
|
||||
{props.showLeverageSelector && (
|
||||
<Select
|
||||
size='large'
|
||||
size="large"
|
||||
showSearch
|
||||
style={{ width: 80 }}
|
||||
placeholder='CCY'
|
||||
placeholder="CCY"
|
||||
value={props.leverage}
|
||||
onChange={(item: number) => {
|
||||
if (props.onLeverage) props.onLeverage(item);
|
||||
|
@ -113,12 +141,22 @@ export default function CollateralInput(props: {
|
|||
props.onLeverage(parseFloat(item));
|
||||
}
|
||||
}}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
>
|
||||
{[1, 2, 3, 4, 5].map((val) => (
|
||||
<Option key={val} value={val} name={val + 'x'} title={val + 'x'}>
|
||||
<div key={val} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{val + 'x'}
|
||||
<Option
|
||||
key={val}
|
||||
value={val}
|
||||
name={val + "x"}
|
||||
title={val + "x"}
|
||||
>
|
||||
<div
|
||||
key={val}
|
||||
style={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
{val + "x"}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
|
@ -126,23 +164,28 @@ export default function CollateralInput(props: {
|
|||
)}
|
||||
{!props.disabled ? (
|
||||
<Select
|
||||
size='large'
|
||||
size="large"
|
||||
showSearch
|
||||
style={{ minWidth: 150 }}
|
||||
placeholder='CCY'
|
||||
placeholder="CCY"
|
||||
value={collateralReserve}
|
||||
onChange={(item) => {
|
||||
if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||
setCollateralReserve(item);
|
||||
}}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
>
|
||||
{renderReserveAccounts}
|
||||
</Select>
|
||||
) : (
|
||||
<TokenDisplay
|
||||
key={props.reserve.liquidityMint.toBase58()}
|
||||
name={getTokenName(tokenMap, props.reserve.liquidityMint.toBase58())}
|
||||
name={getTokenName(
|
||||
tokenMap,
|
||||
props.reserve.liquidityMint.toBase58()
|
||||
)}
|
||||
mintAddress={props.reserve.liquidityMint.toBase58()}
|
||||
showBalance={false}
|
||||
/>
|
||||
|
|
|
@ -15,29 +15,20 @@ export const CollateralItem = (props: {
|
|||
userDeposit?: UserDeposit;
|
||||
name: string;
|
||||
}) => {
|
||||
const {
|
||||
mint,
|
||||
name,
|
||||
userDeposit,
|
||||
} = props;
|
||||
const { mint, name, userDeposit } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TokenIcon mintAddress={mint} />
|
||||
{name}
|
||||
<span
|
||||
className="token-balance"
|
||||
>
|
||||
{" "}
|
||||
{userDeposit ? formatAmount(userDeposit.info.amount) : '--'}
|
||||
<span className="token-balance">
|
||||
{userDeposit ? formatAmount(userDeposit.info.amount) : "--"}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const CollateralSelector = (props: {
|
||||
reserve: LendingReserve;
|
||||
|
@ -56,15 +47,15 @@ export const CollateralSelector = (props: {
|
|||
|
||||
const quoteMintAddress = market?.info?.quoteMint?.toBase58();
|
||||
|
||||
const onlyQuoteAllowed = props.reserve?.liquidityMint?.toBase58() !==
|
||||
quoteMintAddress;
|
||||
const onlyQuoteAllowed =
|
||||
props.reserve?.liquidityMint?.toBase58() !== quoteMintAddress;
|
||||
|
||||
return (
|
||||
<Select
|
||||
size='large'
|
||||
size="large"
|
||||
showSearch
|
||||
style={{ minWidth: 300, margin: '5px 0px' }}
|
||||
placeholder='Collateral'
|
||||
style={{ minWidth: 300, margin: "5px 0px" }}
|
||||
placeholder="Collateral"
|
||||
value={props.collateralReserve}
|
||||
disabled={props.disabled}
|
||||
defaultValue={props.collateralReserve}
|
||||
|
@ -73,23 +64,34 @@ export const CollateralSelector = (props: {
|
|||
props.onCollateralReserve(item);
|
||||
}
|
||||
}}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
>
|
||||
{reserveAccounts
|
||||
.filter((reserve) => reserve.info !== props.reserve)
|
||||
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint))
|
||||
.filter(
|
||||
(reserve) =>
|
||||
!onlyQuoteAllowed ||
|
||||
reserve.info.liquidityMint.equals(market.info.quoteMint)
|
||||
)
|
||||
.map((reserve) => {
|
||||
const mint = reserve.info.liquidityMint.toBase58();
|
||||
const address = reserve.pubkey.toBase58();
|
||||
const name = getTokenName(tokenMap, mint);
|
||||
|
||||
return <Option key={address} value={address} name={name} title={address}>
|
||||
return (
|
||||
<Option key={address} value={address} name={name} title={address}>
|
||||
<CollateralItem
|
||||
reserve={reserve}
|
||||
userDeposit={userDeposits.find(dep => dep.reserve.pubkey.toBase58() === address)}
|
||||
userDeposit={userDeposits.find(
|
||||
(dep) => dep.reserve.pubkey.toBase58() === address
|
||||
)}
|
||||
mint={mint}
|
||||
name={name} />
|
||||
name={name}
|
||||
/>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,11 @@ export const ConnectButton = (
|
|||
const { wallet, connected } = useWallet();
|
||||
const { onClick, children, disabled, ...rest } = props;
|
||||
return (
|
||||
<Button {...rest} onClick={connected ? onClick : wallet.connect} disabled={connected && disabled}>
|
||||
<Button
|
||||
{...rest}
|
||||
onClick={connected ? onClick : wallet.connect}
|
||||
disabled={connected && disabled}
|
||||
>
|
||||
{connected ? props.children : LABELS.CONNECT_LABEL}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'antd';
|
||||
import React from "react";
|
||||
import { Input } from "antd";
|
||||
|
||||
export class NumericInput extends React.Component<any, any> {
|
||||
onChange = (e: any) => {
|
||||
const { value } = e.target;
|
||||
const reg = /^-?\d*(\.\d*)?$/;
|
||||
if (reg.test(value) || value === '' || value === '-') {
|
||||
if (reg.test(value) || value === "" || value === "-") {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
};
|
||||
|
@ -15,19 +15,29 @@ export class NumericInput extends React.Component<any, any> {
|
|||
const { value, onBlur, onChange } = this.props;
|
||||
let valueTemp = value;
|
||||
if (value === undefined || value === null) return;
|
||||
if (value.charAt && (value.charAt(value.length - 1) === '.' || value === '-')) {
|
||||
if (
|
||||
value.charAt &&
|
||||
(value.charAt(value.length - 1) === "." || value === "-")
|
||||
) {
|
||||
valueTemp = value.slice(0, -1);
|
||||
}
|
||||
if (value.startsWith && (value.startsWith('.') || value.startsWith('-.'))) {
|
||||
valueTemp = valueTemp.replace('.', '0.');
|
||||
if (value.startsWith && (value.startsWith(".") || value.startsWith("-."))) {
|
||||
valueTemp = valueTemp.replace(".", "0.");
|
||||
}
|
||||
if (valueTemp.replace) onChange?.(valueTemp.replace(/0*(\d+)/, '$1'));
|
||||
if (valueTemp.replace) onChange?.(valueTemp.replace(/0*(\d+)/, "$1"));
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Input {...this.props} onChange={this.onChange} onBlur={this.onBlur} maxLength={25} />;
|
||||
return (
|
||||
<Input
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
maxLength={25}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
ShoppingOutlined,
|
||||
HomeOutlined,
|
||||
RocketOutlined,
|
||||
ForkOutlined
|
||||
ForkOutlined,
|
||||
// LineChartOutlined
|
||||
} from "@ant-design/icons";
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { AppBar } from "./../AppBar";
|
|||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useConnectionConfig } from "../../contexts/connection";
|
||||
import { LABELS } from "../../constants";
|
||||
import config from './../../../package.json';
|
||||
import config from "./../../../package.json";
|
||||
|
||||
export const AppLayout = React.memo((props: any) => {
|
||||
const { env } = useConnectionConfig();
|
||||
|
@ -37,8 +37,7 @@ export const AppLayout = React.memo((props: any) => {
|
|||
[...Object.keys(paths)].find((key) => location.pathname.startsWith(key)) ||
|
||||
"";
|
||||
const defaultKey = paths[current] || "1";
|
||||
const theme = 'light';
|
||||
|
||||
const theme = "light";
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
|
@ -47,7 +46,11 @@ export const AppLayout = React.memo((props: any) => {
|
|||
</div>
|
||||
<BasicLayout
|
||||
title={LABELS.APP_TITLE}
|
||||
footerRender={() => <div className="footer" title={LABELS.FOOTER}>{LABELS.FOOTER}</div>}
|
||||
footerRender={() => (
|
||||
<div className="footer" title={LABELS.FOOTER}>
|
||||
{LABELS.FOOTER}
|
||||
</div>
|
||||
)}
|
||||
navTheme={theme}
|
||||
headerTheme={theme}
|
||||
theme={theme}
|
||||
|
@ -56,12 +59,15 @@ export const AppLayout = React.memo((props: any) => {
|
|||
primaryColor="#d83aeb"
|
||||
logo={<div className="App-logo" />}
|
||||
rightContentRender={() => <AppBar />}
|
||||
links={[
|
||||
|
||||
]}
|
||||
links={[]}
|
||||
menuContentRender={() => {
|
||||
return (<div className="links">
|
||||
<Menu theme={theme} defaultSelectedKeys={[defaultKey]} mode="inline">
|
||||
return (
|
||||
<div className="links">
|
||||
<Menu
|
||||
theme={theme}
|
||||
defaultSelectedKeys={[defaultKey]}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item key="1" icon={<HomeOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
|
@ -129,14 +135,31 @@ export const AppLayout = React.memo((props: any) => {
|
|||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
<Menu theme={theme} defaultSelectedKeys={[defaultKey]} selectable={false} mode="inline" className="bottom-links">
|
||||
<Menu
|
||||
theme={theme}
|
||||
defaultSelectedKeys={[defaultKey]}
|
||||
selectable={false}
|
||||
mode="inline"
|
||||
className="bottom-links"
|
||||
>
|
||||
<Menu.Item key="16" icon={<ForkOutlined />}>
|
||||
<a title="Fork" href={`${config.repository.url}/fork`} target="_blank" rel="noopener noreferrer" >
|
||||
<a
|
||||
title="Fork"
|
||||
href={`${config.repository.url}/fork`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Fork
|
||||
</a>
|
||||
</Menu.Item>,
|
||||
</Menu.Item>
|
||||
,
|
||||
<Menu.Item key="15" icon={<GithubOutlined />}>
|
||||
<a title="Gtihub" href={config.repository.url} target="_blank" rel="noopener noreferrer" >
|
||||
<a
|
||||
title="Gtihub"
|
||||
href={config.repository.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
</Menu.Item>
|
||||
|
|
|
@ -48,7 +48,7 @@ export const LiquidateInput = (props: {
|
|||
wadToLamports(obligation.info.borrowAmountWad).toNumber(),
|
||||
obligation.account,
|
||||
repayReserve,
|
||||
withdrawReserve,
|
||||
withdrawReserve
|
||||
);
|
||||
|
||||
setShowConfirmation(true);
|
||||
|
@ -58,7 +58,14 @@ export const LiquidateInput = (props: {
|
|||
setPendingTx(false);
|
||||
}
|
||||
})();
|
||||
}, [withdrawReserve, fromAccounts, obligation, repayReserve, wallet, connection]);
|
||||
}, [
|
||||
withdrawReserve,
|
||||
fromAccounts,
|
||||
obligation,
|
||||
repayReserve,
|
||||
wallet,
|
||||
connection,
|
||||
]);
|
||||
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
|
@ -80,20 +87,25 @@ export const LiquidateInput = (props: {
|
|||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
<div className="liquidate-input-title">{LABELS.SELECT_COLLATERAL}</div>
|
||||
<div className="liquidate-input-title">
|
||||
{LABELS.SELECT_COLLATERAL}
|
||||
</div>
|
||||
<CollateralSelector
|
||||
reserve={repayReserve.info}
|
||||
collateralReserve={withdrawReserve?.pubkey.toBase58()}
|
||||
disabled={true}
|
||||
/>
|
||||
<Button type="primary"
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onLiquidate}
|
||||
disabled={fromAccounts.length === 0}
|
||||
loading={pendingTx}>
|
||||
loading={pendingTx}
|
||||
>
|
||||
{LABELS.LIQUIDATE_ACTION}
|
||||
</Button>
|
||||
<BackButton />
|
||||
</div>)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { Card, Row, Col } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMint } from '../../contexts/accounts';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
import { useUserAccounts } from '../../hooks';
|
||||
import { PoolInfo } from '../../models';
|
||||
import { formatPriceNumber } from '../../utils/utils';
|
||||
import { Card, Row, Col } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useMint } from "../../contexts/accounts";
|
||||
import { useEnrichedPools } from "../../contexts/market";
|
||||
import { useUserAccounts } from "../../hooks";
|
||||
import { PoolInfo } from "../../models";
|
||||
import { formatPriceNumber } from "../../utils/utils";
|
||||
|
||||
export const PoolPrice = (props: { pool: PoolInfo }) => {
|
||||
const pool = props.pool;
|
||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [props.pool]);
|
||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [
|
||||
props.pool,
|
||||
]);
|
||||
const enriched = useEnrichedPools(pools)[0];
|
||||
|
||||
const { userAccounts } = useUserAccounts();
|
||||
|
@ -17,32 +19,37 @@ export const PoolPrice = (props: { pool: PoolInfo }) => {
|
|||
const ratio =
|
||||
userAccounts
|
||||
.filter((f) => pool.pubkeys.mint.equals(f.info.mint))
|
||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) / (lpMint?.supply.toNumber() || 0);
|
||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) /
|
||||
(lpMint?.supply.toNumber() || 0);
|
||||
|
||||
if (!enriched) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
className='ccy-input'
|
||||
style={{ borderRadius: 20, width: '100%' }}
|
||||
bodyStyle={{ padding: '7px' }}
|
||||
size='small'
|
||||
title='Prices and pool share'
|
||||
className="ccy-input"
|
||||
style={{ borderRadius: 20, width: "100%" }}
|
||||
bodyStyle={{ padding: "7px" }}
|
||||
size="small"
|
||||
title="Prices and pool share"
|
||||
>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Row style={{ width: "100%" }}>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB))}
|
||||
{formatPriceNumber.format(
|
||||
parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB)
|
||||
)}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA))}
|
||||
{formatPriceNumber.format(
|
||||
parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA)
|
||||
)}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{ratio * 100 < 0.001 && ratio > 0 ? '<' : ''}
|
||||
{ratio * 100 < 0.001 && ratio > 0 ? "<" : ""}
|
||||
{formatPriceNumber.format(ratio * 100)}%
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Row style={{ width: "100%" }}>
|
||||
<Col span={8}>
|
||||
{enriched.names[0]} per {enriched.names[1]}
|
||||
</Col>
|
||||
|
|
|
@ -44,7 +44,7 @@ export const ReserveStatus = (props: {
|
|||
const totalBorrowsInUSD = price * totalBorrows;
|
||||
|
||||
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
|
||||
props.reserve
|
||||
props.reserve,
|
||||
]);
|
||||
|
||||
const liquidationThreshold = props.reserve.config.liquidationThreshold;
|
||||
|
@ -54,10 +54,21 @@ export const ReserveStatus = (props: {
|
|||
return (
|
||||
<Card
|
||||
className={props.className}
|
||||
title={<>
|
||||
<TokenIcon style={{ marginRight: 0, marginTop: 0, position: 'absolute', left: 15 }}
|
||||
mintAddress={mintAddress} size={30} />
|
||||
{LABELS.RESERVE_STATUS_TITLE}</>}
|
||||
title={
|
||||
<>
|
||||
<TokenIcon
|
||||
style={{
|
||||
marginRight: 0,
|
||||
marginTop: 0,
|
||||
position: "absolute",
|
||||
left: 15,
|
||||
}}
|
||||
mintAddress={mintAddress}
|
||||
size={30}
|
||||
/>
|
||||
{LABELS.RESERVE_STATUS_TITLE}
|
||||
</>
|
||||
}
|
||||
bodyStyle={bodyStyle}
|
||||
>
|
||||
<div className="flexColumn">
|
||||
|
@ -66,21 +77,31 @@ export const ReserveStatus = (props: {
|
|||
<Statistic
|
||||
title="Available Liquidity"
|
||||
value={availableLiquidity}
|
||||
valueRender={(node) => <div>
|
||||
valueRender={(node) => (
|
||||
<div>
|
||||
{node}
|
||||
<div className="dashboard-amount-quote-stat">${formatNumber.format(availableLiquidityInUSD)}</div>
|
||||
</div>}
|
||||
precision={2} />
|
||||
<div className="dashboard-amount-quote-stat">
|
||||
${formatNumber.format(availableLiquidityInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
precision={2}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="Total Borrowed"
|
||||
value={totalBorrows}
|
||||
valueRender={(node) => <div>
|
||||
valueRender={(node) => (
|
||||
<div>
|
||||
{node}
|
||||
<div className="dashboard-amount-quote-stat">${formatNumber.format(totalBorrowsInUSD)}</div>
|
||||
</div>}
|
||||
precision={2} />
|
||||
<div className="dashboard-amount-quote-stat">
|
||||
${formatNumber.format(totalBorrowsInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
precision={2}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={GUTTER}>
|
||||
|
@ -102,7 +123,8 @@ export const ReserveStatus = (props: {
|
|||
className="small-statisitc"
|
||||
value={maxLTV}
|
||||
precision={2}
|
||||
suffix="%" />
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
|
@ -110,7 +132,8 @@ export const ReserveStatus = (props: {
|
|||
className="small-statisitc"
|
||||
value={liquidationThreshold}
|
||||
precision={2}
|
||||
suffix="%" />
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
|
@ -118,7 +141,8 @@ export const ReserveStatus = (props: {
|
|||
className="small-statisitc"
|
||||
value={liquidationPenalty}
|
||||
precision={2}
|
||||
suffix="%" />
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
|
@ -126,7 +150,8 @@ export const ReserveStatus = (props: {
|
|||
className="small-statisitc"
|
||||
value={depositAPY * 100}
|
||||
precision={2}
|
||||
suffix="%" />
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
|||
);
|
||||
|
||||
const totalSupply = availableLiquidity + totalBorrows;
|
||||
const percent = 100 * totalBorrows / totalSupply;
|
||||
const percent = (100 * totalBorrows) / totalSupply;
|
||||
|
||||
return (
|
||||
<WaterWave
|
||||
|
@ -34,7 +34,8 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
|||
title="Utilization"
|
||||
value={percent}
|
||||
suffix="%"
|
||||
precision={2} />
|
||||
precision={2}
|
||||
/>
|
||||
}
|
||||
percent={percent}
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { PoolInfo } from '../../models';
|
||||
import echarts from 'echarts';
|
||||
import { formatNumber, formatUSD } from '../../utils/utils';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { PoolInfo } from "../../models";
|
||||
import echarts from "echarts";
|
||||
import { formatNumber, formatUSD } from "../../utils/utils";
|
||||
import { useEnrichedPools } from "../../contexts/market";
|
||||
|
||||
export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||
const { pool } = props;
|
||||
|
@ -44,7 +44,7 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
|
||||
instance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
trigger: "item",
|
||||
formatter: function (params: any) {
|
||||
var val = formatUSD.format(params.value);
|
||||
var tokenAmount = formatNumber.format(params.data.tokens);
|
||||
|
@ -53,8 +53,8 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Liquidity',
|
||||
type: 'pie',
|
||||
name: "Liquidity",
|
||||
type: "pie",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
|
@ -70,20 +70,20 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
},
|
||||
rich: {
|
||||
c: {
|
||||
color: 'black',
|
||||
color: "black",
|
||||
lineHeight: 22,
|
||||
align: 'center',
|
||||
align: "center",
|
||||
},
|
||||
r: {
|
||||
color: 'black',
|
||||
align: 'right',
|
||||
color: "black",
|
||||
align: "right",
|
||||
},
|
||||
},
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: '#000',
|
||||
borderColor: "#000",
|
||||
},
|
||||
},
|
||||
data,
|
||||
|
@ -96,5 +96,5 @@ export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
return <div ref={chartDiv} style={{ height: 150, width: '100%' }} />;
|
||||
return <div ref={chartDiv} style={{ height: 150, width: "100%" }} />;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { useMint, useAccountByMint } from '../../contexts/accounts';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
import React from "react";
|
||||
import { useMint, useAccountByMint } from "../../contexts/accounts";
|
||||
import { TokenIcon } from "../TokenIcon";
|
||||
|
||||
export const TokenDisplay = (props: {
|
||||
name: string;
|
||||
|
@ -16,7 +16,8 @@ export const TokenDisplay = (props: {
|
|||
let hasBalance: boolean = false;
|
||||
if (showBalance) {
|
||||
if (tokenAccount && tokenMint) {
|
||||
balance = tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||
balance =
|
||||
tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||
hasBalance = balance > 0;
|
||||
}
|
||||
}
|
||||
|
@ -27,18 +28,27 @@ export const TokenDisplay = (props: {
|
|||
title={mintAddress}
|
||||
key={mintAddress}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{icon || <TokenIcon mintAddress={mintAddress} />}
|
||||
{name}
|
||||
</div>
|
||||
{showBalance ? (
|
||||
<span title={balance.toString()} key={mintAddress} className='token-balance'>
|
||||
{hasBalance ? (balance < 0.001 ? '<0.001' : balance.toFixed(3)) : '-'}
|
||||
<span
|
||||
title={balance.toString()}
|
||||
key={mintAddress}
|
||||
className="token-balance"
|
||||
>
|
||||
{" "}
|
||||
{hasBalance
|
||||
? balance < 0.001
|
||||
? "<0.001"
|
||||
: balance.toFixed(3)
|
||||
: "-"}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -25,13 +25,25 @@ export const UserLendingCard = (props: {
|
|||
|
||||
const name = useTokenName(reserve?.liquidityMint);
|
||||
|
||||
const { balance: tokenBalance, balanceInUSD: tokenBalanceInUSD } = useUserBalance(props.reserve.liquidityMint);
|
||||
const { balance: collateralBalance, balanceInUSD: collateralBalanceInUSD } = useUserCollateralBalance(
|
||||
props.reserve
|
||||
);
|
||||
const {
|
||||
balance: tokenBalance,
|
||||
balanceInUSD: tokenBalanceInUSD,
|
||||
} = useUserBalance(props.reserve.liquidityMint);
|
||||
const {
|
||||
balance: collateralBalance,
|
||||
balanceInUSD: collateralBalanceInUSD,
|
||||
} = useUserCollateralBalance(props.reserve);
|
||||
|
||||
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
|
||||
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
|
||||
const {
|
||||
borrowed: totalBorrowed,
|
||||
borrowedInUSD,
|
||||
ltv,
|
||||
health,
|
||||
} = useBorrowedAmount(address);
|
||||
const {
|
||||
totalInQuote: borrowingPowerInUSD,
|
||||
borrowingPower,
|
||||
} = useBorrowingPower(address);
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
@ -57,8 +69,12 @@ export const UserLendingCard = (props: {
|
|||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div>
|
||||
<div><em>{formatNumber.format(totalBorrowed)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(borrowedInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(totalBorrowed)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(borrowedInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,8 +99,12 @@ export const UserLendingCard = (props: {
|
|||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div>
|
||||
<div><em>{formatNumber.format(borrowingPower)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(borrowingPowerInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(borrowingPowerInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -97,8 +117,12 @@ export const UserLendingCard = (props: {
|
|||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div>
|
||||
<div><em>{formatNumber.format(tokenBalance)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(tokenBalanceInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(tokenBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(tokenBalanceInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -109,8 +133,12 @@ export const UserLendingCard = (props: {
|
|||
</Text>
|
||||
<div className="card-cell ">
|
||||
<div>
|
||||
<div><em>{formatNumber.format(collateralBalance)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(collateralBalanceInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateralBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(collateralBalanceInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -59,12 +59,15 @@ export const WithdrawInput = (props: {
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
const withdrawAmount = Math.min(type === InputType.Percent
|
||||
const withdrawAmount = Math.min(
|
||||
type === InputType.Percent
|
||||
? (pct * collateralBalanceLamports) / 100
|
||||
: Math.ceil(
|
||||
collateralBalanceLamports *
|
||||
(parseFloat(value) / collateralBalanceInLiquidity)
|
||||
), collateralBalanceLamports);
|
||||
),
|
||||
collateralBalanceLamports
|
||||
);
|
||||
await withdraw(
|
||||
fromAccounts[0],
|
||||
withdrawAmount,
|
||||
|
@ -142,7 +145,9 @@ export const WithdrawInput = (props: {
|
|||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
>
|
||||
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.WITHDRAW_ACTION}
|
||||
{fromAccounts.length === 0
|
||||
? LABELS.NO_DEPOSITS
|
||||
: LABELS.WITHDRAW_ACTION}
|
||||
</ConnectButton>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,8 @@ export const LABELS = {
|
|||
BORROWING_POWER_VALUE: "Borrowing Power",
|
||||
BORROWED_VALUE: "You borrowed",
|
||||
GIVE_SOL: "Give me SOL",
|
||||
LIQUIDATION_INFO: "This view displays all loans that can be liquidated. A liquidation is a process where borrower collateral does not cover value of the loan. It is represented by health factor falling below 1.0. When a loan is liquidated, an liquidator can purchase collateral at a discount by repaing the portio of the loan. ",
|
||||
LIQUIDATION_INFO:
|
||||
"This view displays all loans that can be liquidated. A liquidation is a process where borrower collateral does not cover value of the loan. It is represented by health factor falling below 1.0. When a loan is liquidated, an liquidator can purchase collateral at a discount by repaing the portio of the loan. ",
|
||||
FAUCET_INFO:
|
||||
"This faucet will help you fund your accounts outside of Solana main network.",
|
||||
ACCOUNT_FUNDED: "Account funded.",
|
||||
|
@ -12,7 +13,7 @@ export const LABELS = {
|
|||
REPAY_ACTION: "Repay",
|
||||
RESERVE_STATUS_TITLE: "Reserve Status & Configuration",
|
||||
AUDIT_WARNING:
|
||||
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
|
||||
"Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.",
|
||||
FOOTER:
|
||||
'This page was produced by the Solana Foundation ("SF") for internal educational and inspiration purposes only. SF does not encourage, induce or sanction the deployment, integration or use of Oyster or any similar application (including its code) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. Anyone using this code or a derivation thereof must comply with applicable laws and regulations when releasing related software.',
|
||||
MENU_HOME: "Home",
|
||||
|
@ -26,7 +27,7 @@ export const LABELS = {
|
|||
MENU_BORROW: "Borrow",
|
||||
MENU_LIQUIDATE: "Liquidate",
|
||||
MENU_FAUCET: "Faucet",
|
||||
MARGIN_TRADING: 'Margin Trading',
|
||||
MARGIN_TRADING: "Margin Trading",
|
||||
APP_TITLE: "Oyster Lending",
|
||||
CONNECT_BUTTON: "Connect",
|
||||
WALLET_TOOLTIP: "Wallet public key",
|
||||
|
@ -65,21 +66,26 @@ export const LABELS = {
|
|||
GO_BACK_ACTION: "Go back",
|
||||
DEPOSIT_ACTION: "Deposit",
|
||||
TOTAL_TITLE: "Total",
|
||||
TRADING_TABLE_TITLE_MY_COLLATERAL: 'Chosen Collateral',
|
||||
TRADING_TABLE_TITLE_DESIRED_ASSET: 'Desired Asset',
|
||||
TRADING_TABLE_TITLE_MULTIPLIER: 'Leverage',
|
||||
TRADING_TABLE_TITLE_ASSET_PRICE: 'Asset Price',
|
||||
TRADING_TABLE_TITLE_LIQUIDATION_PRICE: 'Liquidation Price',
|
||||
TRADING_TABLE_TITLE_APY: 'APY',
|
||||
TRADING_TABLE_TITLE_ACTIONS: 'Action',
|
||||
TRADING_ADD_POSITION: 'Add Position',
|
||||
MARGIN_TRADE_ACTION: 'Margin Trade',
|
||||
MARGIN_TRADE_CHOOSE_COLLATERAL_AND_LEVERAGE: 'Please choose your collateral and leverage.',
|
||||
MARGIN_TRADE_QUESTION: 'Please choose how much of this asset you wish to purchase.',
|
||||
TABLE_TITLE_BUYING_POWER: 'Total Buying Power',
|
||||
NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.',
|
||||
SET_MORE_MARGIN_MESSAGE: 'You need more margin to match this leverage amount to make this trade.',
|
||||
LEVERAGE_LIMIT_MESSAGE: 'You will need more margin to make this trade.',
|
||||
NO_DEPOSIT_MESSAGE: 'You need to deposit coin of this type into oyster before trading with it on margin.',
|
||||
NO_COLL_TYPE_MESSAGE: 'Choose Collateral CCY',
|
||||
TRADING_TABLE_TITLE_MY_COLLATERAL: "Chosen Collateral",
|
||||
TRADING_TABLE_TITLE_DESIRED_ASSET: "Desired Asset",
|
||||
TRADING_TABLE_TITLE_MULTIPLIER: "Leverage",
|
||||
TRADING_TABLE_TITLE_ASSET_PRICE: "Asset Price",
|
||||
TRADING_TABLE_TITLE_LIQUIDATION_PRICE: "Liquidation Price",
|
||||
TRADING_TABLE_TITLE_APY: "APY",
|
||||
TRADING_TABLE_TITLE_ACTIONS: "Action",
|
||||
TRADING_ADD_POSITION: "Add Position",
|
||||
MARGIN_TRADE_ACTION: "Margin Trade",
|
||||
MARGIN_TRADE_CHOOSE_COLLATERAL_AND_LEVERAGE:
|
||||
"Please choose your collateral and leverage.",
|
||||
MARGIN_TRADE_QUESTION:
|
||||
"Please choose how much of this asset you wish to purchase.",
|
||||
TABLE_TITLE_BUYING_POWER: "Total Buying Power",
|
||||
NOT_ENOUGH_MARGIN_MESSAGE:
|
||||
"Not enough buying power in oyster to make this trade at this leverage.",
|
||||
SET_MORE_MARGIN_MESSAGE:
|
||||
"You need more margin to match this leverage amount to make this trade.",
|
||||
LEVERAGE_LIMIT_MESSAGE: "You will need more margin to make this trade.",
|
||||
NO_DEPOSIT_MESSAGE:
|
||||
"You need to deposit coin of this type into oyster before trading with it on margin.",
|
||||
NO_COLL_TYPE_MESSAGE: "Choose Collateral CCY",
|
||||
};
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useConnection } from './connection';
|
||||
import { useWallet } from './wallet';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { AccountLayout, u64, MintInfo, MintLayout } from '@solana/spl-token';
|
||||
import { PoolInfo, TokenAccount } from './../models';
|
||||
import { chunks } from './../utils/utils';
|
||||
import { EventEmitter } from './../utils/eventEmitter';
|
||||
import { useUserAccounts } from '../hooks/useUserAccounts';
|
||||
import { WRAPPED_SOL_MINT, programIds, LEND_HOST_FEE_ADDRESS } from '../utils/ids';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useConnection } from "./connection";
|
||||
import { useWallet } from "./wallet";
|
||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||
import { AccountLayout, u64, MintInfo, MintLayout } from "@solana/spl-token";
|
||||
import { PoolInfo, TokenAccount } from "./../models";
|
||||
import { chunks } from "./../utils/utils";
|
||||
import { EventEmitter } from "./../utils/eventEmitter";
|
||||
import { useUserAccounts } from "../hooks/useUserAccounts";
|
||||
import {
|
||||
WRAPPED_SOL_MINT,
|
||||
programIds,
|
||||
LEND_HOST_FEE_ADDRESS,
|
||||
} from "../utils/ids";
|
||||
|
||||
const AccountsContext = React.createContext<any>(null);
|
||||
|
||||
|
@ -22,7 +32,10 @@ export interface ParsedAccountBase {
|
|||
info: any; // TODO: change to unkown
|
||||
}
|
||||
|
||||
export type AccountParser = (pubkey: PublicKey, data: AccountInfo<Buffer>) => ParsedAccountBase | undefined;
|
||||
export type AccountParser = (
|
||||
pubkey: PublicKey,
|
||||
data: AccountInfo<Buffer>
|
||||
) => ParsedAccountBase | undefined;
|
||||
|
||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||
info: T;
|
||||
|
@ -31,7 +44,7 @@ export interface ParsedAccount<T> extends ParsedAccountBase {
|
|||
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
||||
const info = await connection.getAccountInfo(pubKey);
|
||||
if (info === null) {
|
||||
throw new Error('Failed to find mint account');
|
||||
throw new Error("Failed to find mint account");
|
||||
}
|
||||
|
||||
const data = Buffer.from(info.data);
|
||||
|
@ -55,7 +68,10 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
|||
return details;
|
||||
};
|
||||
|
||||
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
export const TokenAccountParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = deserializeAccount(buffer);
|
||||
|
||||
|
@ -70,7 +86,10 @@ export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>)
|
|||
return details;
|
||||
};
|
||||
|
||||
export const GenericAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
export const GenericAccountParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const details = {
|
||||
|
@ -88,9 +107,13 @@ export const keyToAccountParser = new Map<string, AccountParser>();
|
|||
|
||||
export const cache = {
|
||||
emitter: new EventEmitter(),
|
||||
query: async (connection: Connection, pubKey: string | PublicKey, parser?: AccountParser) => {
|
||||
query: async (
|
||||
connection: Connection,
|
||||
pubKey: string | PublicKey,
|
||||
parser?: AccountParser
|
||||
) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
if (typeof pubKey === "string") {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
|
@ -111,7 +134,7 @@ export const cache = {
|
|||
// TODO: refactor to use multiple accounts query with flush like behavior
|
||||
query = connection.getAccountInfo(id).then((data) => {
|
||||
if (!data) {
|
||||
throw new Error('Account not found');
|
||||
throw new Error("Account not found");
|
||||
}
|
||||
|
||||
return cache.add(id, data, parser);
|
||||
|
@ -120,11 +143,17 @@ export const cache = {
|
|||
|
||||
return query;
|
||||
},
|
||||
add: (id: PublicKey | string, obj: AccountInfo<Buffer>, parser?: AccountParser) => {
|
||||
const address = typeof id === 'string' ? id : id?.toBase58();
|
||||
add: (
|
||||
id: PublicKey | string,
|
||||
obj: AccountInfo<Buffer>,
|
||||
parser?: AccountParser
|
||||
) => {
|
||||
const address = typeof id === "string" ? id : id?.toBase58();
|
||||
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
||||
if (!deserialize) {
|
||||
throw new Error('Deserializer needs to be registered or passed as a parameter');
|
||||
throw new Error(
|
||||
"Deserializer needs to be registered or passed as a parameter"
|
||||
);
|
||||
}
|
||||
|
||||
cache.registerParser(id, deserialize);
|
||||
|
@ -142,7 +171,7 @@ export const cache = {
|
|||
},
|
||||
get: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
if (typeof pubKey !== "string") {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -152,7 +181,7 @@ export const cache = {
|
|||
},
|
||||
delete: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
if (typeof pubKey !== "string") {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -178,7 +207,7 @@ export const cache = {
|
|||
},
|
||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||
if (pubkey) {
|
||||
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||
const address = typeof pubkey === "string" ? pubkey : pubkey?.toBase58();
|
||||
keyToAccountParser.set(address, parser);
|
||||
}
|
||||
|
||||
|
@ -186,7 +215,7 @@ export const cache = {
|
|||
},
|
||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
if (typeof pubKey === "string") {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
|
@ -215,7 +244,7 @@ export const cache = {
|
|||
},
|
||||
getMint: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
if (typeof pubKey !== "string") {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
|
@ -237,7 +266,10 @@ export const useAccountsContext = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
function wrapNativeAccount(pubkey: PublicKey, account?: AccountInfo<Buffer>): TokenAccount | undefined {
|
||||
function wrapNativeAccount(
|
||||
pubkey: PublicKey,
|
||||
account?: AccountInfo<Buffer>
|
||||
): TokenAccount | undefined {
|
||||
if (!account) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -273,7 +305,9 @@ export function useCachedPool(legacy = false) {
|
|||
};
|
||||
}
|
||||
|
||||
export const getCachedAccount = (predicate: (account: TokenAccount) => boolean) => {
|
||||
export const getCachedAccount = (
|
||||
predicate: (account: TokenAccount) => boolean
|
||||
) => {
|
||||
for (const account of genericCache.values()) {
|
||||
if (predicate(account)) {
|
||||
return account as TokenAccount;
|
||||
|
@ -323,7 +357,10 @@ const UseNativeAccount = () => {
|
|||
};
|
||||
|
||||
const PRECACHED_OWNERS = new Set<string>();
|
||||
const precacheUserTokenAccounts = async (connection: Connection, owner?: PublicKey) => {
|
||||
const precacheUserTokenAccounts = async (
|
||||
connection: Connection,
|
||||
owner?: PublicKey
|
||||
) => {
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
@ -351,12 +388,16 @@ export function AccountsProvider({ children = null as any }) {
|
|||
return cache
|
||||
.byParser(TokenAccountParser)
|
||||
.map((id) => cache.get(id))
|
||||
.filter((a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58())
|
||||
.filter(
|
||||
(a) => a && a.info.owner.toBase58() === wallet.publicKey?.toBase58()
|
||||
)
|
||||
.map((a) => a as TokenAccount);
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = selectUserAccounts().filter((a) => a !== undefined) as TokenAccount[];
|
||||
const accounts = selectUserAccounts().filter(
|
||||
(a) => a !== undefined
|
||||
) as TokenAccount[];
|
||||
setUserAccounts(accounts);
|
||||
}, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
|
||||
|
||||
|
@ -406,7 +447,7 @@ export function AccountsProvider({ children = null as any }) {
|
|||
}
|
||||
}
|
||||
},
|
||||
'singleGossip'
|
||||
"singleGossip"
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
@ -434,9 +475,15 @@ export function useNativeAccount() {
|
|||
};
|
||||
}
|
||||
|
||||
export const getMultipleAccounts = async (connection: any, keys: string[], commitment: string) => {
|
||||
export const getMultipleAccounts = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string
|
||||
) => {
|
||||
const result = await Promise.all(
|
||||
chunks(keys, 99).map((chunk) => getMultipleAccountsCore(connection, chunk, commitment))
|
||||
chunks(keys, 99).map((chunk) =>
|
||||
getMultipleAccountsCore(connection, chunk, commitment)
|
||||
)
|
||||
);
|
||||
|
||||
const array = result
|
||||
|
@ -451,7 +498,7 @@ export const getMultipleAccounts = async (connection: any, keys: string[], commi
|
|||
const { data, ...rest } = acc;
|
||||
const obj = {
|
||||
...rest,
|
||||
data: Buffer.from(data[0], 'base64'),
|
||||
data: Buffer.from(data[0], "base64"),
|
||||
} as AccountInfo<Buffer>;
|
||||
return obj;
|
||||
})
|
||||
|
@ -461,12 +508,18 @@ export const getMultipleAccounts = async (connection: any, keys: string[], commi
|
|||
return { keys, array };
|
||||
};
|
||||
|
||||
const getMultipleAccountsCore = async (connection: any, keys: string[], commitment: string) => {
|
||||
const args = connection._buildArgs([keys], commitment, 'base64');
|
||||
const getMultipleAccountsCore = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string
|
||||
) => {
|
||||
const args = connection._buildArgs([keys], commitment, "base64");
|
||||
|
||||
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
|
||||
const unsafeRes = await connection._rpcRequest("getMultipleAccounts", args);
|
||||
if (unsafeRes.error) {
|
||||
throw new Error('failed to get info about account ' + unsafeRes.error.message);
|
||||
throw new Error(
|
||||
"failed to get info about account " + unsafeRes.error.message
|
||||
);
|
||||
}
|
||||
|
||||
if (unsafeRes.result.value) {
|
||||
|
@ -482,7 +535,7 @@ export function useMint(key?: string | PublicKey) {
|
|||
const connection = useConnection();
|
||||
const [mint, setMint] = useState<MintInfo>();
|
||||
|
||||
const id = typeof key === 'string' ? key : key?.toBase58();
|
||||
const id = typeof key === "string" ? key : key?.toBase58();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
|
@ -497,7 +550,9 @@ export function useMint(key?: string | PublicKey) {
|
|||
const dispose = cache.emitter.onCache((e) => {
|
||||
const event = e;
|
||||
if (event.id === id) {
|
||||
cache.query(connection, id, MintParser).then((mint) => setMint(mint.info as any));
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then((mint) => setMint(mint.info as any));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
|
@ -510,7 +565,9 @@ export function useMint(key?: string | PublicKey) {
|
|||
|
||||
export const useAccountByMint = (mint: string) => {
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const index = userAccounts.findIndex((acc) => acc.info.mint.toBase58() === mint);
|
||||
const index = userAccounts.findIndex(
|
||||
(acc) => acc.info.mint.toBase58() === mint
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
return userAccounts[index];
|
||||
|
@ -531,7 +588,9 @@ export function useAccount(pubKey?: PublicKey) {
|
|||
return;
|
||||
}
|
||||
|
||||
const acc = await cache.query(connection, key, TokenAccountParser).catch((err) => console.log(err));
|
||||
const acc = await cache
|
||||
.query(connection, key, TokenAccountParser)
|
||||
.catch((err) => console.log(err));
|
||||
if (acc) {
|
||||
setAccount(acc);
|
||||
}
|
||||
|
@ -594,7 +653,7 @@ const deserializeAccount = (data: Buffer) => {
|
|||
// TODO: expose in spl package
|
||||
const deserializeMint = (data: Buffer) => {
|
||||
if (data.length !== MintLayout.span) {
|
||||
throw new Error('Not a valid Mint');
|
||||
throw new Error("Not a valid Mint");
|
||||
}
|
||||
|
||||
const mintInfo = MintLayout.decode(data);
|
||||
|
|
|
@ -1,29 +1,40 @@
|
|||
import { KnownToken, useLocalStorageState } from './../utils/utils';
|
||||
import { Account, clusterApiUrl, Connection, Transaction, TransactionInstruction } from '@solana/web3.js';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { notify } from './../utils/notifications';
|
||||
import { ExplorerLink } from '../components/ExplorerLink';
|
||||
import LocalTokens from '../config/tokens.json';
|
||||
import { setProgramIds } from '../utils/ids';
|
||||
import { KnownToken, useLocalStorageState } from "./../utils/utils";
|
||||
import {
|
||||
Account,
|
||||
clusterApiUrl,
|
||||
Connection,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { notify } from "./../utils/notifications";
|
||||
import { ExplorerLink } from "../components/ExplorerLink";
|
||||
import LocalTokens from "../config/tokens.json";
|
||||
import { setProgramIds } from "../utils/ids";
|
||||
|
||||
export type ENV = 'mainnet-beta' | 'testnet' | 'devnet' | 'localnet' | 'lending';
|
||||
export type ENV =
|
||||
| "mainnet-beta"
|
||||
| "testnet"
|
||||
| "devnet"
|
||||
| "localnet"
|
||||
| "lending";
|
||||
|
||||
export const ENDPOINTS = [
|
||||
{
|
||||
name: 'mainnet-beta' as ENV,
|
||||
endpoint: 'https://solana-api.projectserum.com/',
|
||||
name: "mainnet-beta" as ENV,
|
||||
endpoint: "https://solana-api.projectserum.com/",
|
||||
},
|
||||
{
|
||||
name: 'Oyster Dev' as ENV,
|
||||
endpoint: 'http://oyster-dev.solana.com/',
|
||||
name: "Oyster Dev" as ENV,
|
||||
endpoint: "http://oyster-dev.solana.com/",
|
||||
},
|
||||
{
|
||||
name: 'Lending' as ENV,
|
||||
endpoint: 'http://tln.solana.com/',
|
||||
name: "Lending" as ENV,
|
||||
endpoint: "http://tln.solana.com/",
|
||||
},
|
||||
{ name: 'testnet' as ENV, endpoint: clusterApiUrl('testnet') },
|
||||
{ name: 'devnet' as ENV, endpoint: clusterApiUrl('devnet') },
|
||||
{ name: 'localnet' as ENV, endpoint: 'http://127.0.0.1:8899' },
|
||||
{ name: "testnet" as ENV, endpoint: clusterApiUrl("testnet") },
|
||||
{ name: "devnet" as ENV, endpoint: clusterApiUrl("devnet") },
|
||||
{ name: "localnet" as ENV, endpoint: "http://127.0.0.1:8899" },
|
||||
];
|
||||
|
||||
const DEFAULT = ENDPOINTS[0].endpoint;
|
||||
|
@ -46,29 +57,43 @@ const ConnectionContext = React.createContext<ConnectionConfig>({
|
|||
setEndpoint: () => {},
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
setSlippage: (val: number) => {},
|
||||
connection: new Connection(DEFAULT, 'recent'),
|
||||
sendConnection: new Connection(DEFAULT, 'recent'),
|
||||
connection: new Connection(DEFAULT, "recent"),
|
||||
sendConnection: new Connection(DEFAULT, "recent"),
|
||||
env: ENDPOINTS[0].name,
|
||||
tokens: [],
|
||||
tokenMap: new Map<string, KnownToken>(),
|
||||
});
|
||||
|
||||
export function ConnectionProvider({ children = undefined as any }) {
|
||||
const [endpoint, setEndpoint] = useLocalStorageState('connectionEndpts', ENDPOINTS[0].endpoint);
|
||||
const [endpoint, setEndpoint] = useLocalStorageState(
|
||||
"connectionEndpts",
|
||||
ENDPOINTS[0].endpoint
|
||||
);
|
||||
|
||||
const [slippage, setSlippage] = useLocalStorageState('slippage', DEFAULT_SLIPPAGE.toString());
|
||||
const [slippage, setSlippage] = useLocalStorageState(
|
||||
"slippage",
|
||||
DEFAULT_SLIPPAGE.toString()
|
||||
);
|
||||
|
||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||
const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||
const connection = useMemo(() => new Connection(endpoint, "recent"), [
|
||||
endpoint,
|
||||
]);
|
||||
const sendConnection = useMemo(() => new Connection(endpoint, "recent"), [
|
||||
endpoint,
|
||||
]);
|
||||
|
||||
const env = ENDPOINTS.find((end) => end.endpoint === endpoint)?.name || ENDPOINTS[0].name;
|
||||
const env =
|
||||
ENDPOINTS.find((end) => end.endpoint === endpoint)?.name ||
|
||||
ENDPOINTS[0].name;
|
||||
|
||||
const [tokens, setTokens] = useState<KnownToken[]>([]);
|
||||
const [tokenMap, setTokenMap] = useState<Map<string, KnownToken>>(new Map());
|
||||
useEffect(() => {
|
||||
// fetch token files
|
||||
window
|
||||
.fetch(`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`)
|
||||
.fetch(
|
||||
`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`
|
||||
)
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
|
@ -104,7 +129,10 @@ export function ConnectionProvider({ children = undefined as any }) {
|
|||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
const id = sendConnection.onAccountChange(new Account().publicKey, () => {});
|
||||
const id = sendConnection.onAccountChange(
|
||||
new Account().publicKey,
|
||||
() => {}
|
||||
);
|
||||
return () => {
|
||||
sendConnection.removeAccountChangeListener(id);
|
||||
};
|
||||
|
@ -162,7 +190,7 @@ export function useSlippageConfig() {
|
|||
|
||||
const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
||||
// wait for all confirmation before geting transaction
|
||||
await connection.confirmTransaction(txid, 'max');
|
||||
await connection.confirmTransaction(txid, "max");
|
||||
|
||||
const tx = await connection.getParsedConfirmedTransaction(txid);
|
||||
|
||||
|
@ -196,7 +224,9 @@ export const sendTransaction = async (
|
|||
) => {
|
||||
let transaction = new Transaction();
|
||||
instructions.forEach((instruction) => transaction.add(instruction));
|
||||
transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash;
|
||||
transaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash("max")
|
||||
).blockhash;
|
||||
transaction.setSigners(
|
||||
// fee payied by the wallet owner
|
||||
wallet.publicKey,
|
||||
|
@ -209,30 +239,37 @@ export const sendTransaction = async (
|
|||
const rawTransaction = transaction.serialize();
|
||||
let options = {
|
||||
skipPreflight: true,
|
||||
commitment: 'singleGossip',
|
||||
commitment: "singleGossip",
|
||||
};
|
||||
|
||||
const txid = await connection.sendRawTransaction(rawTransaction, options);
|
||||
|
||||
if (awaitConfirmation) {
|
||||
const status = (await connection.confirmTransaction(txid, options && (options.commitment as any))).value;
|
||||
const status = (
|
||||
await connection.confirmTransaction(
|
||||
txid,
|
||||
options && (options.commitment as any)
|
||||
)
|
||||
).value;
|
||||
|
||||
if (status?.err) {
|
||||
const errors = await getErrorForTransaction(connection, txid);
|
||||
notify({
|
||||
message: 'Transaction failed...',
|
||||
message: "Transaction failed...",
|
||||
description: (
|
||||
<>
|
||||
{errors.map((err) => (
|
||||
<div>{err}</div>
|
||||
))}
|
||||
<ExplorerLink address={txid} type='transaction' />
|
||||
<ExplorerLink address={txid} type="transaction" />
|
||||
</>
|
||||
),
|
||||
type: 'error',
|
||||
type: "error",
|
||||
});
|
||||
|
||||
throw new Error(`Raw transaction ${txid} failed (${JSON.stringify(status)})`);
|
||||
throw new Error(
|
||||
`Raw transaction ${txid} failed (${JSON.stringify(status)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useConnection } from './connection';
|
||||
import { LENDING_PROGRAM_ID } from './../utils/ids';
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useConnection } from "./connection";
|
||||
import { LENDING_PROGRAM_ID } from "./../utils/ids";
|
||||
import {
|
||||
LendingMarketParser,
|
||||
isLendingReserve,
|
||||
|
@ -9,12 +9,17 @@ import {
|
|||
LendingReserve,
|
||||
isLendingObligation,
|
||||
LendingObligationParser,
|
||||
} from './../models/lending';
|
||||
import { cache, getMultipleAccounts, MintParser, ParsedAccount } from './accounts';
|
||||
import { PublicKey, AccountInfo } from '@solana/web3.js';
|
||||
import { DexMarketParser } from '../models/dex';
|
||||
import { usePrecacheMarket } from './market';
|
||||
import { useLendingReserves } from '../hooks';
|
||||
} from "./../models/lending";
|
||||
import {
|
||||
cache,
|
||||
getMultipleAccounts,
|
||||
MintParser,
|
||||
ParsedAccount,
|
||||
} from "./accounts";
|
||||
import { PublicKey, AccountInfo } from "@solana/web3.js";
|
||||
import { DexMarketParser } from "../models/dex";
|
||||
import { usePrecacheMarket } from "./market";
|
||||
import { useLendingReserves } from "../hooks";
|
||||
|
||||
export interface LendingContextState {}
|
||||
|
||||
|
@ -41,21 +46,38 @@ export const useLending = () => {
|
|||
|
||||
// TODO: query for all the dex from reserves
|
||||
|
||||
const processAccount = useCallback((item : { pubkey: PublicKey, account: AccountInfo<Buffer> }) => {
|
||||
const processAccount = useCallback(
|
||||
(item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
|
||||
if (isLendingReserve(item.account)) {
|
||||
const reserve = cache.add(item.pubkey.toBase58(), item.account, LendingReserveParser);
|
||||
const reserve = cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingReserveParser
|
||||
);
|
||||
|
||||
return reserve;
|
||||
} else if (isLendingMarket(item.account)) {
|
||||
return cache.add(item.pubkey.toBase58(), item.account, LendingMarketParser);
|
||||
return cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingMarketParser
|
||||
);
|
||||
} else if (isLendingObligation(item.account)) {
|
||||
return cache.add(item.pubkey.toBase58(), item.account, LendingObligationParser);
|
||||
return cache.add(
|
||||
item.pubkey.toBase58(),
|
||||
item.account,
|
||||
LendingObligationParser
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (reserveAccounts.length > 0) {
|
||||
precacheMarkets(reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58()));
|
||||
precacheMarkets(
|
||||
reserveAccounts.map((reserve) => reserve.info.liquidityMint.toBase58())
|
||||
);
|
||||
}
|
||||
}, [reserveAccounts, precacheMarkets]);
|
||||
|
||||
|
@ -64,21 +86,36 @@ export const useLending = () => {
|
|||
setLendingAccounts([]);
|
||||
|
||||
const queryLendingAccounts = async () => {
|
||||
const programAccounts = await connection.getProgramAccounts(LENDING_PROGRAM_ID);
|
||||
const programAccounts = await connection.getProgramAccounts(
|
||||
LENDING_PROGRAM_ID
|
||||
);
|
||||
|
||||
const accounts = programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||
const accounts = programAccounts
|
||||
.map(processAccount)
|
||||
.filter((item) => item !== undefined);
|
||||
|
||||
const lendingReserves = accounts
|
||||
.filter((acc) => (acc?.info as LendingReserve).lendingMarket !== undefined)
|
||||
.filter(
|
||||
(acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
|
||||
)
|
||||
.map((acc) => acc as ParsedAccount<LendingReserve>);
|
||||
|
||||
const toQuery = [
|
||||
...lendingReserves.map((acc) => {
|
||||
const result = [
|
||||
cache.registerParser(acc?.info.collateralMint.toBase58(), MintParser),
|
||||
cache.registerParser(acc?.info.liquidityMint.toBase58(), MintParser),
|
||||
cache.registerParser(
|
||||
acc?.info.collateralMint.toBase58(),
|
||||
MintParser
|
||||
),
|
||||
cache.registerParser(
|
||||
acc?.info.liquidityMint.toBase58(),
|
||||
MintParser
|
||||
),
|
||||
// ignore dex if its not set
|
||||
cache.registerParser(acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : '', DexMarketParser),
|
||||
cache.registerParser(
|
||||
acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : "",
|
||||
DexMarketParser
|
||||
),
|
||||
].filter((_) => _);
|
||||
return result;
|
||||
}),
|
||||
|
@ -86,13 +123,15 @@ export const useLending = () => {
|
|||
|
||||
// This will pre-cache all accounts used by pools
|
||||
// All those accounts are updated whenever there is a change
|
||||
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||
({ keys, array }) => {
|
||||
return array.map((obj, index) => {
|
||||
const address = keys[index];
|
||||
cache.add(address, obj);
|
||||
return obj;
|
||||
}) as any[];
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// HACK: fix, force account refresh
|
||||
programAccounts.map(processAccount).filter((item) => item !== undefined);
|
||||
|
@ -116,7 +155,7 @@ export const useLending = () => {
|
|||
};
|
||||
processAccount(item);
|
||||
},
|
||||
'singleGossip'
|
||||
"singleGossip"
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { MINT_TO_MARKET } from './../models/marketOverrides';
|
||||
import { POOLS_WITH_AIRDROP } from './../models/airdrops';
|
||||
import { convert, fromLamports, getPoolName, getTokenName, KnownTokenMap, STABLE_COINS } from './../utils/utils';
|
||||
import { useConnectionConfig } from './connection';
|
||||
import { cache, getMultipleAccounts, ParsedAccount } from './accounts';
|
||||
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { useMemo } from 'react';
|
||||
import { EventEmitter } from './../utils/eventEmitter';
|
||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { MINT_TO_MARKET } from "./../models/marketOverrides";
|
||||
import { POOLS_WITH_AIRDROP } from "./../models/airdrops";
|
||||
import {
|
||||
convert,
|
||||
fromLamports,
|
||||
getPoolName,
|
||||
getTokenName,
|
||||
KnownTokenMap,
|
||||
STABLE_COINS,
|
||||
} from "./../utils/utils";
|
||||
import { useConnectionConfig } from "./connection";
|
||||
import { cache, getMultipleAccounts, ParsedAccount } from "./accounts";
|
||||
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum";
|
||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||
import { useMemo } from "react";
|
||||
import { EventEmitter } from "./../utils/eventEmitter";
|
||||
|
||||
import { DexMarketParser } from './../models/dex';
|
||||
import { LendingMarket, LendingReserve, PoolInfo } from '../models';
|
||||
import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from '../utils/pools';
|
||||
import { DexMarketParser } from "./../models/dex";
|
||||
import { LendingMarket, LendingReserve, PoolInfo } from "../models";
|
||||
import { LIQUIDITY_PROVIDER_FEE, SERUM_FEE } from "../utils/pools";
|
||||
|
||||
const INITAL_LIQUIDITY_DATE = new Date('2020-10-27');
|
||||
const INITAL_LIQUIDITY_DATE = new Date("2020-10-27");
|
||||
export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min
|
||||
|
||||
interface RecentPoolData {
|
||||
|
@ -43,19 +50,27 @@ export function MarketProvider({ children = null as any }) {
|
|||
const { endpoint } = useConnectionConfig();
|
||||
const accountsToObserve = useMemo(() => new Map<string, number>(), []);
|
||||
const [marketMints, setMarketMints] = useState<string[]>([]);
|
||||
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(new Map());
|
||||
const [dailyVolume, setDailyVolume] = useState<Map<string, RecentPoolData>>(
|
||||
new Map()
|
||||
);
|
||||
|
||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
||||
const connection = useMemo(() => new Connection(endpoint, "recent"), [
|
||||
endpoint,
|
||||
]);
|
||||
|
||||
const marketByMint = useMemo(() => {
|
||||
return [...new Set(marketMints).values()].reduce((acc, key) => {
|
||||
const mintAddress = key;
|
||||
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
||||
(a) => a.address.toBase58() === mintAddress
|
||||
);
|
||||
|
||||
const marketAddress = MINT_TO_MARKET[mintAddress];
|
||||
const marketName = `${SERUM_TOKEN?.name}/USDC`;
|
||||
const marketInfo = MARKETS.find((m) => m.name === marketName || m.address.toBase58() === marketAddress);
|
||||
const marketInfo = MARKETS.find(
|
||||
(m) => m.name === marketName || m.address.toBase58() === marketAddress
|
||||
);
|
||||
|
||||
if (marketInfo) {
|
||||
acc.set(mintAddress, {
|
||||
|
@ -94,7 +109,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
connection,
|
||||
// only query for markets that are not in cahce
|
||||
allMarkets.filter((a) => cache.get(a) === undefined),
|
||||
'single'
|
||||
"single"
|
||||
).then(({ keys, array }) => {
|
||||
allMarkets.forEach(() => {});
|
||||
|
||||
|
@ -152,7 +167,10 @@ export function MarketProvider({ children = null as any }) {
|
|||
|
||||
const midPriceInUSD = useCallback(
|
||||
(mintAddress: string) => {
|
||||
return getMidPrice(marketByMint.get(mintAddress)?.marketInfo.address.toBase58(), mintAddress);
|
||||
return getMidPrice(
|
||||
marketByMint.get(mintAddress)?.marketInfo.address.toBase58(),
|
||||
mintAddress
|
||||
);
|
||||
},
|
||||
[marketByMint]
|
||||
);
|
||||
|
@ -160,7 +178,7 @@ export function MarketProvider({ children = null as any }) {
|
|||
const subscribeToMarket = useCallback(
|
||||
(mintAddress: string) => {
|
||||
const info = marketByMint.get(mintAddress);
|
||||
const market = cache.get(info?.marketInfo.address.toBase58() || '');
|
||||
const market = cache.get(info?.marketInfo.address.toBase58() || "");
|
||||
if (!market) {
|
||||
return () => {};
|
||||
}
|
||||
|
@ -230,7 +248,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
const marketEmitter = context?.marketEmitter;
|
||||
const marketsByMint = context?.marketByMint;
|
||||
const dailyVolume = context?.dailyVolume;
|
||||
const poolKeys = pools.map((p) => p.pubkeys.account.toBase58()).join(',');
|
||||
const poolKeys = pools.map((p) => p.pubkeys.account.toBase58()).join(",");
|
||||
|
||||
useEffect(() => {
|
||||
if (!marketEmitter || !subscribeToMarket || pools.length === 0) {
|
||||
|
@ -242,7 +260,9 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
const subscriptions = mints.map((m) => subscribeToMarket(m));
|
||||
|
||||
const update = () => {
|
||||
setEnriched(createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap));
|
||||
setEnriched(
|
||||
createEnrichedPools(pools, marketsByMint, dailyVolume, tokenMap)
|
||||
);
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket(update);
|
||||
|
@ -254,7 +274,14 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
subscriptions.forEach((dispose) => dispose && dispose());
|
||||
};
|
||||
// Do not add pools here, causes a really bad infinite rendering loop. Use poolKeys instead.
|
||||
}, [tokenMap, dailyVolume, poolKeys, subscribeToMarket, marketEmitter, marketsByMint]);
|
||||
}, [
|
||||
tokenMap,
|
||||
dailyVolume,
|
||||
poolKeys,
|
||||
subscribeToMarket,
|
||||
marketEmitter,
|
||||
marketsByMint,
|
||||
]);
|
||||
|
||||
return enriched;
|
||||
};
|
||||
|
@ -279,20 +306,30 @@ function createEnrichedPools(
|
|||
const result = pools
|
||||
.filter((p) => p.pubkeys.holdingMints && p.pubkeys.holdingMints.length > 1)
|
||||
.map((p, index) => {
|
||||
const mints = (p.pubkeys.holdingMints || []).map((a) => a.toBase58()).sort();
|
||||
const mints = (p.pubkeys.holdingMints || [])
|
||||
.map((a) => a.toBase58())
|
||||
.sort();
|
||||
const mintA = cache.getMint(mints[0]);
|
||||
const mintB = cache.getMint(mints[1]);
|
||||
|
||||
const account0 = cache.get(p.pubkeys.holdingAccounts[0]);
|
||||
const account1 = cache.get(p.pubkeys.holdingAccounts[1]);
|
||||
|
||||
const accountA = account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
||||
const accountB = account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
||||
const accountA =
|
||||
account0?.info.mint.toBase58() === mints[0] ? account0 : account1;
|
||||
const accountB =
|
||||
account1?.info.mint.toBase58() === mints[1] ? account1 : account0;
|
||||
|
||||
const baseMid = getMidPrice(marketByMint.get(mints[0])?.marketInfo.address.toBase58() || '', mints[0]);
|
||||
const baseMid = getMidPrice(
|
||||
marketByMint.get(mints[0])?.marketInfo.address.toBase58() || "",
|
||||
mints[0]
|
||||
);
|
||||
const baseReserveUSD = baseMid * convert(accountA, mintA);
|
||||
|
||||
const quote = getMidPrice(marketByMint.get(mints[1])?.marketInfo.address.toBase58() || '', mints[1]);
|
||||
const quote = getMidPrice(
|
||||
marketByMint.get(mints[1])?.marketInfo.address.toBase58() || "",
|
||||
mints[1]
|
||||
);
|
||||
const quoteReserveUSD = quote * convert(accountB, mintB);
|
||||
|
||||
const poolMint = cache.getMint(p.pubkeys.mint);
|
||||
|
@ -300,10 +337,16 @@ function createEnrichedPools(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let airdropYield = calculateAirdropYield(p, marketByMint, baseReserveUSD, quoteReserveUSD);
|
||||
let airdropYield = calculateAirdropYield(
|
||||
p,
|
||||
marketByMint,
|
||||
baseReserveUSD,
|
||||
quoteReserveUSD
|
||||
);
|
||||
|
||||
let volume = 0;
|
||||
let volume24h = baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
||||
let volume24h =
|
||||
baseMid * (poolData?.get(p.pubkeys.mint.toBase58())?.volume24hA || 0);
|
||||
let fees24h = volume24h * (LIQUIDITY_PROVIDER_FEE - SERUM_FEE);
|
||||
let fees = 0;
|
||||
let apy = airdropYield;
|
||||
|
@ -311,13 +354,18 @@ function createEnrichedPools(
|
|||
if (p.pubkeys.feeAccount) {
|
||||
const feeAccount = cache.get(p.pubkeys.feeAccount);
|
||||
|
||||
if (poolMint && feeAccount && feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()) {
|
||||
if (
|
||||
poolMint &&
|
||||
feeAccount &&
|
||||
feeAccount.info.mint.toBase58() === p.pubkeys.mint.toBase58()
|
||||
) {
|
||||
const feeBalance = feeAccount?.info.amount.toNumber();
|
||||
const supply = poolMint?.supply.toNumber();
|
||||
|
||||
const ownedPct = feeBalance / supply;
|
||||
|
||||
const poolOwnerFees = ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
||||
const poolOwnerFees =
|
||||
ownedPct * baseReserveUSD + ownedPct * quoteReserveUSD;
|
||||
volume = poolOwnerFees / 0.0004;
|
||||
fees = volume * LIQUIDITY_PROVIDER_FEE;
|
||||
|
||||
|
@ -327,16 +375,27 @@ function createEnrichedPools(
|
|||
|
||||
// Aproximation not true for all pools we need to fine a better way
|
||||
const daysSinceInception = Math.floor(
|
||||
(TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) / (24 * 3600 * 1000)
|
||||
(TODAY.getTime() - INITAL_LIQUIDITY_DATE.getTime()) /
|
||||
(24 * 3600 * 1000)
|
||||
);
|
||||
const apy0 =
|
||||
parseFloat(((baseVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||
parseFloat(
|
||||
((baseVolume / daysSinceInception) *
|
||||
LIQUIDITY_PROVIDER_FEE *
|
||||
356) as any
|
||||
) / baseReserveUSD;
|
||||
const apy1 =
|
||||
parseFloat(((quoteVolume / daysSinceInception) * LIQUIDITY_PROVIDER_FEE * 356) as any) / quoteReserveUSD;
|
||||
parseFloat(
|
||||
((quoteVolume / daysSinceInception) *
|
||||
LIQUIDITY_PROVIDER_FEE *
|
||||
356) as any
|
||||
) / quoteReserveUSD;
|
||||
|
||||
apy = apy + Math.max(apy0, apy1);
|
||||
|
||||
const apy24h0 = parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) / baseReserveUSD;
|
||||
const apy24h0 =
|
||||
parseFloat((volume24h * LIQUIDITY_PROVIDER_FEE * 356) as any) /
|
||||
baseReserveUSD;
|
||||
apy24h = apy24h + apy24h0;
|
||||
}
|
||||
}
|
||||
|
@ -345,7 +404,10 @@ function createEnrichedPools(
|
|||
const lpMint = cache.getMint(p.pubkeys.mint);
|
||||
|
||||
const name = getPoolName(tokenMap, p);
|
||||
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace('/', '-')}`;
|
||||
const link = `#/?pair=${getPoolName(tokenMap, p, false).replace(
|
||||
"/",
|
||||
"-"
|
||||
)}`;
|
||||
|
||||
return {
|
||||
key: p.pubkeys.account.toBase58(),
|
||||
|
@ -360,7 +422,11 @@ function createEnrichedPools(
|
|||
liquidityAinUsd: baseReserveUSD,
|
||||
liquidityB: convert(accountB, mintB),
|
||||
liquidityBinUsd: quoteReserveUSD,
|
||||
supply: lpMint && (lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)).toFixed(9),
|
||||
supply:
|
||||
lpMint &&
|
||||
(
|
||||
lpMint?.supply.toNumber() / Math.pow(10, lpMint?.decimals || 0)
|
||||
).toFixed(9),
|
||||
fees,
|
||||
fees24h,
|
||||
liquidity: baseReserveUSD + quoteReserveUSD,
|
||||
|
@ -384,7 +450,9 @@ function calculateAirdropYield(
|
|||
quoteReserveUSD: number
|
||||
) {
|
||||
let airdropYield = 0;
|
||||
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) => drop.pool.equals(p.pubkeys.mint));
|
||||
let poolWithAirdrop = POOLS_WITH_AIRDROP.find((drop) =>
|
||||
drop.pool.equals(p.pubkeys.mint)
|
||||
);
|
||||
if (poolWithAirdrop) {
|
||||
airdropYield = poolWithAirdrop.airdrops.reduce((acc, item) => {
|
||||
const market = marketByMint.get(item.mint.toBase58())?.marketInfo.address;
|
||||
|
@ -394,7 +462,8 @@ function calculateAirdropYield(
|
|||
acc =
|
||||
acc +
|
||||
// airdrop yield
|
||||
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) * (365 / 30);
|
||||
((item.amount * midPrice) / (baseReserveUSD + quoteReserveUSD)) *
|
||||
(365 / 30);
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
@ -404,7 +473,9 @@ function calculateAirdropYield(
|
|||
}
|
||||
|
||||
export const useMidPriceInUSD = (mint: string) => {
|
||||
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(MarketsContext) as MarketsContextState;
|
||||
const { midPriceInUSD, subscribeToMarket, marketEmitter } = useContext(
|
||||
MarketsContext
|
||||
) as MarketsContextState;
|
||||
const [price, setPrice] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -432,7 +503,12 @@ export const usePrecacheMarket = () => {
|
|||
return context.precacheMarkets;
|
||||
};
|
||||
|
||||
export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve, dex: PublicKey) => {
|
||||
export const simulateMarketOrderFill = (
|
||||
amount: number,
|
||||
reserve: LendingReserve,
|
||||
dex: PublicKey,
|
||||
useBBO = false
|
||||
) => {
|
||||
const liquidityMint = cache.get(reserve.liquidityMint);
|
||||
const collateralMint = cache.get(reserve.collateralMint);
|
||||
if (!liquidityMint || !collateralMint) {
|
||||
|
@ -445,35 +521,51 @@ export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve,
|
|||
}
|
||||
const decodedMarket = marketInfo.info;
|
||||
|
||||
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
const baseMintDecimals =
|
||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals =
|
||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
|
||||
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<
|
||||
LendingMarket
|
||||
>;
|
||||
|
||||
const dexMarket = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||
const dexMarket = new Market(
|
||||
decodedMarket,
|
||||
baseMintDecimals,
|
||||
quoteMintDecimals,
|
||||
undefined,
|
||||
decodedMarket.programId
|
||||
);
|
||||
|
||||
const bookAccount = lendingMarket.info.quoteMint.equals(reserve.liquidityMint)
|
||||
? decodedMarket?.bids
|
||||
: decodedMarket?.asks;
|
||||
|
||||
const bookInfo = cache.get(bookAccount)?.info;
|
||||
if (!bookInfo) {
|
||||
const bidInfo = cache.get(decodedMarket?.bids)?.info;
|
||||
const askInfo = cache.get(decodedMarket?.asks)?.info;
|
||||
if (!bidInfo || !askInfo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const book = new Orderbook(dexMarket, bookInfo.accountFlags, bookInfo.slab);
|
||||
const bids = new Orderbook(dexMarket, bidInfo.accountFlags, bidInfo.slab);
|
||||
const asks = new Orderbook(dexMarket, askInfo.accountFlags, askInfo.slab);
|
||||
|
||||
const book = lendingMarket.info.quoteMint.equals(reserve.liquidityMint)
|
||||
? bids
|
||||
: asks;
|
||||
|
||||
let cost = 0;
|
||||
let remaining = fromLamports(amount, liquidityMint.info);
|
||||
|
||||
if (book) {
|
||||
const depth = book.getL2(1000);
|
||||
let price, sizeAtLevel: number;
|
||||
|
||||
const op = book.isBids
|
||||
? (price: number, size: number) => size / price
|
||||
: (price: number, size: number) => size * price;
|
||||
|
||||
if (useBBO) {
|
||||
const price = bbo(bids, asks);
|
||||
|
||||
return op(price, remaining);
|
||||
} else {
|
||||
const depth = book.getL2(1000);
|
||||
let price, sizeAtLevel: number;
|
||||
|
||||
for ([price, sizeAtLevel] of depth) {
|
||||
let filled = remaining > sizeAtLevel ? sizeAtLevel : remaining;
|
||||
cost = cost + op(price, filled);
|
||||
|
@ -488,10 +580,23 @@ export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve,
|
|||
return cost;
|
||||
};
|
||||
|
||||
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find((a) => a.address.toBase58() === mintAddress);
|
||||
const bbo = (bidsBook: Orderbook, asksBook: Orderbook) => {
|
||||
const bestBid = bidsBook.getL2(1);
|
||||
const bestAsk = asksBook.getL2(1);
|
||||
|
||||
if (STABLE_COINS.has(SERUM_TOKEN?.name || '')) {
|
||||
if (bestBid.length > 0 && bestAsk.length > 0) {
|
||||
return (bestBid[0][0] + bestAsk[0][0]) / 2.0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
||||
(a) => a.address.toBase58() === mintAddress
|
||||
);
|
||||
|
||||
if (STABLE_COINS.has(SERUM_TOKEN?.name || "")) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
|
@ -506,10 +611,18 @@ const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
|||
|
||||
const decodedMarket = marketInfo.info;
|
||||
|
||||
const baseMintDecimals = cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals = cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
const baseMintDecimals =
|
||||
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
|
||||
const quoteMintDecimals =
|
||||
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
|
||||
|
||||
const market = new Market(decodedMarket, baseMintDecimals, quoteMintDecimals, undefined, decodedMarket.programId);
|
||||
const market = new Market(
|
||||
decodedMarket,
|
||||
baseMintDecimals,
|
||||
quoteMintDecimals,
|
||||
undefined,
|
||||
decodedMarket.programId
|
||||
);
|
||||
|
||||
const bids = cache.get(decodedMarket.bids)?.info;
|
||||
const asks = cache.get(decodedMarket.asks)?.info;
|
||||
|
@ -518,12 +631,7 @@ const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
|||
const bidsBook = new Orderbook(market, bids.accountFlags, bids.slab);
|
||||
const asksBook = new Orderbook(market, asks.accountFlags, asks.slab);
|
||||
|
||||
const bestBid = bidsBook.getL2(1);
|
||||
const bestAsk = asksBook.getL2(1);
|
||||
|
||||
if (bestBid.length > 0 && bestAsk.length > 0) {
|
||||
return (bestBid[0][0] + bestAsk[0][0]) / 2.0;
|
||||
}
|
||||
return bbo(bidsBook, asksBook);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -534,12 +642,14 @@ const refreshAccounts = async (connection: Connection, keys: string[]) => {
|
|||
return [];
|
||||
}
|
||||
|
||||
return getMultipleAccounts(connection, keys, 'single').then(({ keys, array }) => {
|
||||
return getMultipleAccounts(connection, keys, "single").then(
|
||||
({ keys, array }) => {
|
||||
return array.map((item, index) => {
|
||||
const address = keys[index];
|
||||
return cache.add(new PublicKey(address), item);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
interface SerumMarket {
|
||||
|
|
|
@ -73,7 +73,8 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
|||
item.obligation.info.tokenMint
|
||||
) as ParsedAccount<MintInfo>;
|
||||
|
||||
result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber());
|
||||
result.borrowedLamports +=
|
||||
borrowed * (owned / obligationMint?.info.supply.toNumber());
|
||||
result.borrowedInUSD += item.obligation.info.borrowedInQuote;
|
||||
result.colateralInUSD += item.obligation.info.collateralInQuote;
|
||||
liquidationThreshold = item.obligation.info.liquidationThreshold;
|
||||
|
@ -83,8 +84,11 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
|||
result.ltv = userObligationsByReserve[0].obligation.info.ltv;
|
||||
result.health = userObligationsByReserve[0].obligation.info.health;
|
||||
} else {
|
||||
result.ltv = 100 * result.borrowedInUSD / result.colateralInUSD;
|
||||
result.health = result.colateralInUSD * liquidationThreshold / 100 / result.borrowedInUSD;
|
||||
result.ltv = (100 * result.borrowedInUSD) / result.colateralInUSD;
|
||||
result.health =
|
||||
(result.colateralInUSD * liquidationThreshold) /
|
||||
100 /
|
||||
result.borrowedInUSD;
|
||||
result.health = Number.isFinite(result.health) ? result.health : 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,18 @@ import { useUserObligations } from "./useUserObligations";
|
|||
|
||||
// TODO: add option to decrease buying power by overcollateralization factor
|
||||
// TODO: add support for balance in the wallet
|
||||
export function useBorrowingPower(reserveAddress: string | PublicKey | undefined, includeWallet = false, overcollateralize = true) {
|
||||
const key = useMemo(() => typeof reserveAddress === 'string' ? reserveAddress : reserveAddress?.toBase58() || '', [reserveAddress]);
|
||||
export function useBorrowingPower(
|
||||
reserveAddress: string | PublicKey | undefined,
|
||||
includeWallet = false,
|
||||
overcollateralize = true
|
||||
) {
|
||||
const key = useMemo(
|
||||
() =>
|
||||
typeof reserveAddress === "string"
|
||||
? reserveAddress
|
||||
: reserveAddress?.toBase58() || "",
|
||||
[reserveAddress]
|
||||
);
|
||||
|
||||
const reserve = useLendingReserve(key);
|
||||
|
||||
|
@ -20,21 +30,19 @@ export function useBorrowingPower(reserveAddress: string | PublicKey | undefined
|
|||
const quoteMintAddess = market?.info?.quoteMint?.toBase58();
|
||||
|
||||
// TODO: remove once cross-collateral is supported
|
||||
const onlyQuoteAllowed = liquidityMintAddress !==
|
||||
quoteMintAddess;
|
||||
const onlyQuoteAllowed = liquidityMintAddress !== quoteMintAddess;
|
||||
|
||||
const exclude = useMemo(() => new Set(
|
||||
[key]),
|
||||
[key]);
|
||||
const exclude = useMemo(() => new Set([key]), [key]);
|
||||
const inlcude = useMemo(() => {
|
||||
const quoteReserve = getLendingReserves()
|
||||
.find(r => r.info.liquidityMint.toBase58() === quoteMintAddess);
|
||||
return onlyQuoteAllowed && quoteReserve ?
|
||||
new Set([quoteReserve.pubkey.toBase58()]) :
|
||||
undefined;
|
||||
const quoteReserve = getLendingReserves().find(
|
||||
(r) => r.info.liquidityMint.toBase58() === quoteMintAddess
|
||||
);
|
||||
return onlyQuoteAllowed && quoteReserve
|
||||
? new Set([quoteReserve.pubkey.toBase58()])
|
||||
: undefined;
|
||||
}, [onlyQuoteAllowed, quoteMintAddess]);
|
||||
|
||||
const { totalInQuote } = useUserDeposits(exclude, inlcude)
|
||||
const { totalInQuote } = useUserDeposits(exclude, inlcude);
|
||||
|
||||
const price = useMidPriceInUSD(liquidityMintAddress).price;
|
||||
|
||||
|
@ -56,6 +64,6 @@ export function useBorrowingPower(reserveAddress: string | PublicKey | undefined
|
|||
return {
|
||||
borrowingPower: totalInQuote / price,
|
||||
totalInQuote,
|
||||
utilization
|
||||
utilization,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,20 +19,25 @@ export function useUserCollateralBalance(
|
|||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||
|
||||
const balanceLamports = useMemo(() => reserve &&
|
||||
calculateCollateralBalance(reserve, userBalance),
|
||||
[userBalance, reserve]);
|
||||
const balanceLamports = useMemo(
|
||||
() => reserve && calculateCollateralBalance(reserve, userBalance),
|
||||
[userBalance, reserve]
|
||||
);
|
||||
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mint),
|
||||
[balanceLamports, mint]);
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mint), [
|
||||
balanceLamports,
|
||||
mint,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateBalance = () => {
|
||||
setBalanceInUSD(balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || ''));
|
||||
}
|
||||
setBalanceInUSD(
|
||||
balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || "")
|
||||
);
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket((args) => {
|
||||
if(args.ids.has(reserve?.dexMarket.toBase58() || '')) {
|
||||
if (args.ids.has(reserve?.dexMarket.toBase58() || "")) {
|
||||
updateBalance();
|
||||
}
|
||||
});
|
||||
|
@ -55,8 +60,10 @@ export function useUserCollateralBalance(
|
|||
}
|
||||
export function calculateCollateralBalance(
|
||||
reserve: LendingReserve,
|
||||
balanceLamports: number) {
|
||||
return reserveMarketCap(reserve) *
|
||||
(balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1));
|
||||
balanceLamports: number
|
||||
) {
|
||||
return (
|
||||
reserveMarketCap(reserve) *
|
||||
(balanceLamports / (reserve?.state.collateralMintSupply.toNumber() || 1))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -77,14 +77,16 @@ export function useEnrichedLendingObligations() {
|
|||
let collateralInQuote = 0;
|
||||
|
||||
if (liquidityMint) {
|
||||
const collateralMint = cache.get(item.collateralReserve.info.liquidityMint);
|
||||
const collateralMint = cache.get(
|
||||
item.collateralReserve.info.liquidityMint
|
||||
);
|
||||
|
||||
const collateral = fromLamports(
|
||||
collateralToLiquidity(
|
||||
obligation.info.depositedCollateral,
|
||||
item.reserve.info
|
||||
),
|
||||
collateralMint?.info,
|
||||
collateralMint?.info
|
||||
);
|
||||
|
||||
const borrowed = wadToLamports(
|
||||
|
@ -96,19 +98,26 @@ export function useEnrichedLendingObligations() {
|
|||
item.reserve.info,
|
||||
item.reserve.info.dexMarketOption
|
||||
? item.reserve.info.dexMarket
|
||||
: item.collateralReserve.info.dexMarket
|
||||
: item.collateralReserve.info.dexMarket,
|
||||
true
|
||||
);
|
||||
|
||||
const liquidityMintAddress = item.reserve.info.liquidityMint.toBase58();
|
||||
const liquidityMint = cache.get(liquidityMintAddress) as ParsedAccount<MintInfo>;
|
||||
borrowedInQuote = fromLamports(borrowed, liquidityMint.info) * midPriceInUSD(liquidityMintAddress);;
|
||||
collateralInQuote = collateral * midPriceInUSD(collateralMint?.pubkey.toBase58() || '');
|
||||
const liquidityMint = cache.get(
|
||||
liquidityMintAddress
|
||||
) as ParsedAccount<MintInfo>;
|
||||
borrowedInQuote =
|
||||
fromLamports(borrowed, liquidityMint.info) *
|
||||
midPriceInUSD(liquidityMintAddress);
|
||||
collateralInQuote =
|
||||
collateral *
|
||||
midPriceInUSD(collateralMint?.pubkey.toBase58() || "");
|
||||
|
||||
ltv = (100 * borrowedAmount) / collateral;
|
||||
|
||||
const liquidationThreshold =
|
||||
item.reserve.info.config.liquidationThreshold;
|
||||
health = collateral * liquidationThreshold / 100 / borrowedAmount;
|
||||
health = (collateral * liquidationThreshold) / 100 / borrowedAmount;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -119,9 +128,13 @@ export function useEnrichedLendingObligations() {
|
|||
health,
|
||||
borrowedInQuote,
|
||||
collateralInQuote,
|
||||
liquidationThreshold: item.reserve.info.config.liquidationThreshold,
|
||||
liquidationThreshold:
|
||||
item.reserve.info.config.liquidationThreshold,
|
||||
repayName: getTokenName(tokenMap, reserve.liquidityMint),
|
||||
collateralName: getTokenName(tokenMap, collateralReserve.liquidityMint)
|
||||
collateralName: getTokenName(
|
||||
tokenMap,
|
||||
collateralReserve.liquidityMint
|
||||
),
|
||||
},
|
||||
} as EnrichedLendingObligation;
|
||||
})
|
||||
|
|
|
@ -43,12 +43,11 @@ export function useLendingReserve(address?: string | PublicKey) {
|
|||
if (token) {
|
||||
const account = reserveAccounts.filter(
|
||||
(acc) => acc.info.liquidityMint.toBase58() === token.mintAddress
|
||||
)[0]
|
||||
)[0];
|
||||
if (account) {
|
||||
addressName = account.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const id = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -5,8 +5,15 @@ import { useMarkets } from "../contexts/market";
|
|||
import { fromLamports } from "../utils/utils";
|
||||
import { useUserAccounts } from "./useUserAccounts";
|
||||
|
||||
export function useUserBalance(mintAddress?: PublicKey | string, account?: PublicKey) {
|
||||
const mint = useMemo(() => typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58(), [mintAddress]);
|
||||
export function useUserBalance(
|
||||
mintAddress?: PublicKey | string,
|
||||
account?: PublicKey
|
||||
) {
|
||||
const mint = useMemo(
|
||||
() =>
|
||||
typeof mintAddress === "string" ? mintAddress : mintAddress?.toBase58(),
|
||||
[mintAddress]
|
||||
);
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||
|
@ -29,12 +36,15 @@ export function useUserBalance(mintAddress?: PublicKey | string, account?: Publi
|
|||
);
|
||||
}, [accounts]);
|
||||
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [mintInfo, balanceLamports]);
|
||||
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [
|
||||
mintInfo,
|
||||
balanceLamports,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateBalance = () => {
|
||||
setBalanceInUSD(balance * midPriceInUSD(mint || ''));
|
||||
}
|
||||
setBalanceInUSD(balance * midPriceInUSD(mint || ""));
|
||||
};
|
||||
|
||||
const dispose = marketEmitter.onMarket((args) => {
|
||||
updateBalance();
|
||||
|
|
|
@ -45,19 +45,28 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
|||
}, [reserveAccounts, exclude, include]);
|
||||
|
||||
useEffect(() => {
|
||||
const activeMarkets = new Set(reserveAccounts.map(r => r.info.dexMarket.toBase58()));
|
||||
const activeMarkets = new Set(
|
||||
reserveAccounts.map((r) => r.info.dexMarket.toBase58())
|
||||
);
|
||||
|
||||
const userDepositsFactory = () => {
|
||||
return userAccounts
|
||||
.filter((acc) => reservesByCollateralMint.has(acc?.info.mint.toBase58()))
|
||||
.filter((acc) =>
|
||||
reservesByCollateralMint.has(acc?.info.mint.toBase58())
|
||||
)
|
||||
.map((item) => {
|
||||
const reserve = reservesByCollateralMint.get(
|
||||
item?.info.mint.toBase58()
|
||||
) as ParsedAccount<LendingReserve>;
|
||||
|
||||
let collateralMint = cache.get(reserve.info.collateralMint) as ParsedAccount<MintInfo>;
|
||||
let collateralMint = cache.get(
|
||||
reserve.info.collateralMint
|
||||
) as ParsedAccount<MintInfo>;
|
||||
|
||||
const amountLamports = calculateCollateralBalance(reserve.info, item?.info.amount.toNumber());
|
||||
const amountLamports = calculateCollateralBalance(
|
||||
reserve.info,
|
||||
item?.info.amount.toNumber()
|
||||
);
|
||||
const amount = fromLamports(amountLamports, collateralMint?.info);
|
||||
const price = midPriceInUSD(reserve.info.liquidityMint.toBase58());
|
||||
const amountInQuote = price * amount;
|
||||
|
@ -78,7 +87,7 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
|||
|
||||
const dispose = marketEmitter.onMarket((args) => {
|
||||
// ignore if none of the markets is used by the reserve
|
||||
if ([...args.ids.values()].every(id => !activeMarkets.has(id))) {
|
||||
if ([...args.ids.values()].every((id) => !activeMarkets.has(id))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -90,10 +99,20 @@ export function useUserDeposits(exclude?: Set<string>, include?: Set<string>) {
|
|||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [userAccounts, reserveAccounts, reservesByCollateralMint, tokenMap, midPriceInUSD, marketEmitter]);
|
||||
}, [
|
||||
userAccounts,
|
||||
reserveAccounts,
|
||||
reservesByCollateralMint,
|
||||
tokenMap,
|
||||
midPriceInUSD,
|
||||
marketEmitter,
|
||||
]);
|
||||
|
||||
return {
|
||||
userDeposits,
|
||||
totalInQuote: userDeposits.reduce((res, item) => res + item.info.amountInQuote, 0),
|
||||
totalInQuote: userDeposits.reduce(
|
||||
(res, item) => res + item.info.amountInQuote,
|
||||
0
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,13 +17,14 @@ export function useUserObligationByReserve(
|
|||
typeof collateralReserve === "string"
|
||||
? collateralReserve
|
||||
: collateralReserve?.toBase58();
|
||||
return userObligations.filter(
|
||||
(item) =>
|
||||
borrowId && collateralId ?
|
||||
item.obligation.info.borrowReserve.toBase58() === borrowId &&
|
||||
item.obligation.info.collateralReserve.toBase58() === collateralId :
|
||||
(borrowId && item.obligation.info.borrowReserve.toBase58() === borrowId) ||
|
||||
(collateralId && item.obligation.info.collateralReserve.toBase58() === collateralId)
|
||||
return userObligations.filter((item) =>
|
||||
borrowId && collateralId
|
||||
? item.obligation.info.borrowReserve.toBase58() === borrowId &&
|
||||
item.obligation.info.collateralReserve.toBase58() === collateralId
|
||||
: (borrowId &&
|
||||
item.obligation.info.borrowReserve.toBase58() === borrowId) ||
|
||||
(collateralId &&
|
||||
item.obligation.info.collateralReserve.toBase58() === collateralId)
|
||||
);
|
||||
}, [borrowReserve, collateralReserve, userObligations]);
|
||||
|
||||
|
|
|
@ -30,11 +30,17 @@ export function useUserObligations() {
|
|||
userAccounts: [...accountsByMint.get(ob.info.tokenMint.toBase58())],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote);
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.obligation.info.borrowedInQuote - a.obligation.info.borrowedInQuote
|
||||
);
|
||||
}, [accountsByMint, obligations]);
|
||||
|
||||
return {
|
||||
userObligations,
|
||||
totalInQuote: userObligations.reduce((result, item) => result + item.obligation.info.borrowedInQuote, 0),
|
||||
totalInQuote: userObligations.reduce(
|
||||
(result, item) => result + item.obligation.info.borrowedInQuote,
|
||||
0
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import './wdyr';
|
||||
import "./wdyr";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
{
|
||||
"name": "Oyster Lending",
|
||||
"short_name": "Oyster Lending",
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { Account, AccountInfo, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||
import {
|
||||
Account,
|
||||
AccountInfo,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { AccountInfo as TokenAccountInfo, Token } from "@solana/spl-token";
|
||||
import { TOKEN_PROGRAM_ID } from "../utils/ids";
|
||||
|
@ -17,7 +22,7 @@ export function approve(
|
|||
amount: number,
|
||||
|
||||
// if delegate is not passed ephemeral transfer authority is used
|
||||
delegate?: PublicKey,
|
||||
delegate?: PublicKey
|
||||
): Account {
|
||||
const tokenProgram = TOKEN_PROGRAM_ID;
|
||||
const transferAuthority = new Account();
|
||||
|
@ -34,11 +39,7 @@ export function approve(
|
|||
);
|
||||
|
||||
cleanupInstructions.push(
|
||||
Token.createRevokeInstruction(
|
||||
tokenProgram,
|
||||
account,
|
||||
owner,
|
||||
[]),
|
||||
Token.createRevokeInstruction(tokenProgram, account, owner, [])
|
||||
);
|
||||
|
||||
return transferAuthority;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
interface PoolAirdrop {
|
||||
pool: PublicKey;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export * from './account';
|
||||
export * from './lending';
|
||||
export * from './tokenSwap';
|
||||
export * from './pool';
|
||||
export * from './totals';
|
||||
export * from "./account";
|
||||
export * from "./lending";
|
||||
export * from "./tokenSwap";
|
||||
export * from "./pool";
|
||||
export * from "./totals";
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import { calculateUtilizationRatio, LendingReserve } from "./reserve";
|
||||
|
||||
export enum BorrowAmountType {
|
||||
LiquidityBorrowAmount = 0,
|
||||
|
@ -57,12 +61,12 @@ export const borrowInstruction = (
|
|||
|
||||
memory: PublicKey,
|
||||
|
||||
hostFeeReceiver?: PublicKey,
|
||||
hostFeeReceiver?: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('amount'),
|
||||
BufferLayout.u8('amountType'),
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("amount"),
|
||||
BufferLayout.u8("amountType"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
@ -107,8 +111,8 @@ export const borrowInstruction = (
|
|||
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
||||
];
|
||||
|
||||
if(hostFeeReceiver) {
|
||||
keys.push({ pubkey: hostFeeReceiver, isSigner: false, isWritable: true })
|
||||
if (hostFeeReceiver) {
|
||||
keys.push({ pubkey: hostFeeReceiver, isSigner: false, isWritable: true });
|
||||
}
|
||||
|
||||
return new TransactionInstruction({
|
||||
|
@ -129,12 +133,16 @@ export const calculateBorrowAPY = (reserve: LendingReserve) => {
|
|||
const normalizedFactor = currentUtilization / optimalUtilization;
|
||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||
const minBorrowRate = reserve.config.minBorrowRate / 100;
|
||||
borrowAPY = normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
||||
borrowAPY =
|
||||
normalizedFactor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
|
||||
} else {
|
||||
const normalizedFactor = (currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
||||
const normalizedFactor =
|
||||
(currentUtilization - optimalUtilization) / (1 - optimalUtilization);
|
||||
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
|
||||
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
|
||||
borrowAPY = normalizedFactor * (maxBorrowRate - optimalBorrowRate) + optimalBorrowRate;
|
||||
borrowAPY =
|
||||
normalizedFactor * (maxBorrowRate - optimalBorrowRate) +
|
||||
optimalBorrowRate;
|
||||
}
|
||||
|
||||
return borrowAPY;
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { calculateBorrowAPY } from './borrow';
|
||||
import { LendingInstruction } from './lending';
|
||||
import { calculateUtilizationRatio, LendingReserve } from './reserve';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
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
|
||||
/// of the reserve liquidity pool.
|
||||
|
@ -32,8 +36,8 @@ export const depositInstruction = (
|
|||
collateralMint: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('liquidityAmount')
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import { LendingInstruction } from './lending';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import * as Layout from "./../../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.
|
||||
///
|
||||
|
@ -39,7 +43,10 @@ export const liquidateInstruction = (
|
|||
dexOrderBookSide: PublicKey,
|
||||
memory: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -4,15 +4,15 @@ import * as Layout from "./../../utils/layout";
|
|||
|
||||
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8('version'),
|
||||
BufferLayout.u8('bumpSeed'),
|
||||
BufferLayout.u8("version"),
|
||||
BufferLayout.u8("bumpSeed"),
|
||||
Layout.publicKey("owner"),
|
||||
Layout.publicKey("quoteMint"),
|
||||
Layout.publicKey("tokenProgramId"),
|
||||
|
||||
// extra space for future contract changes
|
||||
BufferLayout.blob(62, "padding"),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
export interface LendingMarket {
|
||||
|
@ -20,7 +20,7 @@ export interface LendingMarket {
|
|||
|
||||
isInitialized: boolean;
|
||||
quoteMint: PublicKey;
|
||||
tokenProgramId: PublicKey,
|
||||
tokenProgramId: PublicKey;
|
||||
}
|
||||
|
||||
export const isLendingMarket = (info: AccountInfo<Buffer>) => {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { AccountInfo, PublicKey, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, TransactionInstruction } from "@solana/web3.js";
|
||||
import {
|
||||
AccountInfo,
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { LendingInstruction } from ".";
|
||||
|
@ -7,7 +13,7 @@ import * as Layout from "./../../utils/layout";
|
|||
|
||||
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8('version'),
|
||||
BufferLayout.u8("version"),
|
||||
/// Amount of collateral tokens deposited for this obligation
|
||||
Layout.uint64("depositedCollateral"),
|
||||
/// Reserve which collateral tokens were deposited into
|
||||
|
@ -60,8 +66,8 @@ export const LendingObligationParser = (
|
|||
};
|
||||
|
||||
export const healthFactorToRiskColor = (health: number) => {
|
||||
return '';
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
/// Initializes a new loan obligation.
|
||||
/// ///
|
||||
|
@ -84,14 +90,9 @@ export const initObligationInstruction = (
|
|||
obligationTokenOutput: PublicKey,
|
||||
obligationTokenOwner: PublicKey,
|
||||
lendingMarket: PublicKey,
|
||||
lendingMarketAuthority: PublicKey,
|
||||
|
||||
|
||||
|
||||
lendingMarketAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import { LendingInstruction } from './lending';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { LendingInstruction } from "./lending";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import * as Layout from "./../../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
|
||||
/// will be recalculated for interest.
|
||||
|
@ -36,9 +40,12 @@ export const repayInstruction = (
|
|||
obligationInput: PublicKey,
|
||||
lendingMarket: PublicKey,
|
||||
authority: PublicKey,
|
||||
transferAuthority: PublicKey,
|
||||
transferAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('liquidityAmount')]);
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -4,47 +4,48 @@ import {
|
|||
SYSVAR_CLOCK_PUBKEY,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import { wadToLamports } from '../../utils/utils';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import { wadToLamports } from "../../utils/utils";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
|
||||
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct([
|
||||
BufferLayout.u8('version'),
|
||||
Layout.uint64('lastUpdateSlot'),
|
||||
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("version"),
|
||||
Layout.uint64("lastUpdateSlot"),
|
||||
|
||||
Layout.publicKey('lendingMarket'),
|
||||
Layout.publicKey('liquidityMint'),
|
||||
BufferLayout.u8('liquidityMintDecimals'),
|
||||
Layout.publicKey('liquiditySupply'),
|
||||
Layout.publicKey('collateralMint'),
|
||||
Layout.publicKey('collateralSupply'),
|
||||
Layout.publicKey("lendingMarket"),
|
||||
Layout.publicKey("liquidityMint"),
|
||||
BufferLayout.u8("liquidityMintDecimals"),
|
||||
Layout.publicKey("liquiditySupply"),
|
||||
Layout.publicKey("collateralMint"),
|
||||
Layout.publicKey("collateralSupply"),
|
||||
|
||||
Layout.publicKey('collateralFeesReceiver'),
|
||||
Layout.publicKey("collateralFeesReceiver"),
|
||||
|
||||
// TODO: replace u32 option with generic quivalent
|
||||
BufferLayout.u32('dexMarketOption'),
|
||||
Layout.publicKey('dexMarket'),
|
||||
BufferLayout.u32("dexMarketOption"),
|
||||
Layout.publicKey("dexMarket"),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
/// Optimal utilization rate as a percent
|
||||
BufferLayout.u8('optimalUtilizationRate'),
|
||||
BufferLayout.u8("optimalUtilizationRate"),
|
||||
/// The ratio of the loan to the value of the collateral as a percent
|
||||
BufferLayout.u8('loanToValueRatio'),
|
||||
BufferLayout.u8("loanToValueRatio"),
|
||||
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
|
||||
BufferLayout.u8('liquidationBonus'),
|
||||
BufferLayout.u8("liquidationBonus"),
|
||||
/// The percent at which an obligation is considered unhealthy
|
||||
BufferLayout.u8('liquidationThreshold'),
|
||||
BufferLayout.u8("liquidationThreshold"),
|
||||
/// Min borrow APY
|
||||
BufferLayout.u8('minBorrowRate'),
|
||||
BufferLayout.u8("minBorrowRate"),
|
||||
/// Optimal (utilization) borrow APY
|
||||
BufferLayout.u8('optimalBorrowRate'),
|
||||
BufferLayout.u8("optimalBorrowRate"),
|
||||
/// Max borrow APY
|
||||
BufferLayout.u8('maxBorrowRate'),
|
||||
BufferLayout.u8("maxBorrowRate"),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
|
@ -54,30 +55,31 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
|
|||
/// 1% = 10_000_000_000_000_000
|
||||
/// 0.01% (1 basis point) = 100_000_000_000_000
|
||||
/// 0.00001% (Aave borrow fee) = 100_000_000_000
|
||||
Layout.uint64('borrowFeeWad'),
|
||||
Layout.uint64("borrowFeeWad"),
|
||||
|
||||
/// Amount of fee going to host account, if provided in liquidate and repay
|
||||
BufferLayout.u8('hostFeePercentage'),
|
||||
BufferLayout.u8("hostFeePercentage"),
|
||||
],
|
||||
'fees'
|
||||
"fees"
|
||||
),
|
||||
],
|
||||
'config'
|
||||
"config"
|
||||
),
|
||||
|
||||
BufferLayout.struct(
|
||||
[
|
||||
Layout.uint128('cumulativeBorrowRateWad'),
|
||||
Layout.uint128('borrowedLiquidityWad'),
|
||||
Layout.uint64('availableLiquidity'),
|
||||
Layout.uint64('collateralMintSupply'),
|
||||
Layout.uint128("cumulativeBorrowRateWad"),
|
||||
Layout.uint128("borrowedLiquidityWad"),
|
||||
Layout.uint64("availableLiquidity"),
|
||||
Layout.uint64("collateralMintSupply"),
|
||||
],
|
||||
'state'
|
||||
"state"
|
||||
),
|
||||
|
||||
// extra space for future contract changes
|
||||
BufferLayout.blob(300, "padding"),
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
||||
return info.data.length === LendingReserveLayout.span;
|
||||
|
@ -122,7 +124,10 @@ export interface LendingReserve {
|
|||
};
|
||||
}
|
||||
|
||||
export const LendingReserveParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||
export const LendingReserveParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = LendingReserveLayout.decode(buffer) as LendingReserve;
|
||||
|
||||
|
@ -138,8 +143,6 @@ export const LendingReserveParser = (pubKey: PublicKey, info: AccountInfo<Buffer
|
|||
info: data,
|
||||
};
|
||||
|
||||
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
|
@ -162,9 +165,9 @@ export const initReserveInstruction = (
|
|||
dexMarket: PublicKey // TODO: optional
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('liquidityAmount'),
|
||||
BufferLayout.u8('maxUtilizationRate'),
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("liquidityAmount"),
|
||||
BufferLayout.u8("maxUtilizationRate"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
@ -207,9 +210,7 @@ export const initReserveInstruction = (
|
|||
export const accrueInterestInstruction = (
|
||||
...reserveAccount: PublicKey[]
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
]);
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8("instruction")]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
@ -221,7 +222,11 @@ export const accrueInterestInstruction = (
|
|||
|
||||
const keys = [
|
||||
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
||||
...reserveAccount.map(reserve => ({ pubkey: reserve, isSigner: false, isWritable: true }))
|
||||
...reserveAccount.map((reserve) => ({
|
||||
pubkey: reserve,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
})),
|
||||
];
|
||||
return new TransactionInstruction({
|
||||
keys,
|
||||
|
@ -231,30 +236,50 @@ export const accrueInterestInstruction = (
|
|||
};
|
||||
|
||||
export const calculateUtilizationRatio = (reserve: LendingReserve) => {
|
||||
const totalBorrows = wadToLamports(reserve.state.borrowedLiquidityWad).toNumber();
|
||||
const currentUtilization = totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows);
|
||||
const totalBorrows = wadToLamports(
|
||||
reserve.state.borrowedLiquidityWad
|
||||
).toNumber();
|
||||
const currentUtilization =
|
||||
totalBorrows / (reserve.state.availableLiquidity.toNumber() + totalBorrows);
|
||||
|
||||
return currentUtilization;
|
||||
};
|
||||
|
||||
export const reserveMarketCap = (reserve?: LendingReserve) => {
|
||||
const available = reserve?.state.availableLiquidity.toNumber() || 0;
|
||||
const borrowed = wadToLamports(reserve?.state.borrowedLiquidityWad).toNumber();
|
||||
const borrowed = wadToLamports(
|
||||
reserve?.state.borrowedLiquidityWad
|
||||
).toNumber();
|
||||
const total = available + borrowed;
|
||||
|
||||
return total;
|
||||
};
|
||||
|
||||
export const collateralExchangeRate = (reserve?: LendingReserve) => {
|
||||
return (reserve?.state.collateralMintSupply.toNumber() || 1) / reserveMarketCap(reserve);
|
||||
return (
|
||||
(reserve?.state.collateralMintSupply.toNumber() || 1) /
|
||||
reserveMarketCap(reserve)
|
||||
);
|
||||
};
|
||||
|
||||
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
|
||||
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
|
||||
export const collateralToLiquidity = (
|
||||
collateralAmount: BN | number,
|
||||
reserve?: LendingReserve
|
||||
) => {
|
||||
const amount =
|
||||
typeof collateralAmount === "number"
|
||||
? collateralAmount
|
||||
: collateralAmount.toNumber();
|
||||
return Math.floor(amount / collateralExchangeRate(reserve));
|
||||
};
|
||||
|
||||
export const liquidityToCollateral = (liquidityAmount: BN | number, reserve?: LendingReserve) => {
|
||||
const amount = typeof liquidityAmount === 'number' ? liquidityAmount : liquidityAmount.toNumber();
|
||||
export const liquidityToCollateral = (
|
||||
liquidityAmount: BN | number,
|
||||
reserve?: LendingReserve
|
||||
) => {
|
||||
const amount =
|
||||
typeof liquidityAmount === "number"
|
||||
? liquidityAmount
|
||||
: liquidityAmount.toNumber();
|
||||
return Math.floor(amount * collateralExchangeRate(reserve));
|
||||
};
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from '../../utils/ids';
|
||||
import * as Layout from './../../utils/layout';
|
||||
import { LendingInstruction } from './lending';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { TOKEN_PROGRAM_ID, LENDING_PROGRAM_ID } from "../../utils/ids";
|
||||
import * as Layout from "./../../utils/layout";
|
||||
import { LendingInstruction } from "./lending";
|
||||
|
||||
export const withdrawInstruction = (
|
||||
collateralAmount: number | BN,
|
||||
|
@ -14,9 +18,12 @@ export const withdrawInstruction = (
|
|||
reserveSupply: PublicKey,
|
||||
lendingMarket: PublicKey,
|
||||
authority: PublicKey,
|
||||
transferAuthority: PublicKey,
|
||||
transferAuthority: PublicKey
|
||||
): TransactionInstruction => {
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), Layout.uint64('collateralAmount')]);
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
Layout.uint64("collateralAmount"),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
dataLayout.encode(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { TokenAccount } from './account';
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { TokenAccount } from "./account";
|
||||
|
||||
export const DEFAULT_DENOMINATOR = 10_000;
|
||||
|
||||
|
|
|
@ -1,68 +1,84 @@
|
|||
import * as BufferLayout from 'buffer-layout';
|
||||
import { publicKey, uint64 } from '../utils/layout';
|
||||
import * as BufferLayout from "buffer-layout";
|
||||
import { publicKey, uint64 } from "../utils/layout";
|
||||
|
||||
export { TokenSwap } from '@solana/spl-token-swap';
|
||||
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'),
|
||||
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'
|
||||
"fees"
|
||||
);
|
||||
|
||||
export const TokenSwapLayoutLegacyV0 = BufferLayout.struct([
|
||||
BufferLayout.u8('isInitialized'),
|
||||
BufferLayout.u8('nonce'),
|
||||
publicKey('tokenAccountA'),
|
||||
publicKey('tokenAccountB'),
|
||||
publicKey('tokenPool'),
|
||||
uint64('feesNumerator'),
|
||||
uint64('feesDenominator'),
|
||||
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'),
|
||||
]);
|
||||
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');
|
||||
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'),
|
||||
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8("isInitialized"),
|
||||
BufferLayout.u8("nonce"),
|
||||
publicKey("tokenProgramId"),
|
||||
publicKey("tokenAccountA"),
|
||||
publicKey("tokenAccountB"),
|
||||
publicKey("tokenPool"),
|
||||
publicKey("mintA"),
|
||||
publicKey("mintB"),
|
||||
publicKey("feeAccount"),
|
||||
FEE_LAYOUT,
|
||||
CURVE_NODE,
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { WalletProvider } from './contexts/wallet';
|
||||
import { ConnectionProvider } from './contexts/connection';
|
||||
import { AccountsProvider } from './contexts/accounts';
|
||||
import { MarketProvider } from './contexts/market';
|
||||
import { LendingProvider } from './contexts/lending';
|
||||
import { AppLayout } from './components/Layout';
|
||||
import { HashRouter, Route, Switch } from "react-router-dom";
|
||||
import React from "react";
|
||||
import { WalletProvider } from "./contexts/wallet";
|
||||
import { ConnectionProvider } from "./contexts/connection";
|
||||
import { AccountsProvider } from "./contexts/accounts";
|
||||
import { MarketProvider } from "./contexts/market";
|
||||
import { LendingProvider } from "./contexts/lending";
|
||||
import { AppLayout } from "./components/Layout";
|
||||
|
||||
import {
|
||||
BorrowReserveView,
|
||||
|
@ -21,13 +21,13 @@ import {
|
|||
LiquidateView,
|
||||
LiquidateReserveView,
|
||||
MarginTrading,
|
||||
} from './views';
|
||||
import { NewPosition } from './views/margin/newPosition';
|
||||
} from "./views";
|
||||
import { NewPosition } from "./views/margin/newPosition";
|
||||
|
||||
export function Routes() {
|
||||
return (
|
||||
<>
|
||||
<HashRouter basename={'/'}>
|
||||
<HashRouter basename={"/"}>
|
||||
<ConnectionProvider>
|
||||
<WalletProvider>
|
||||
<AccountsProvider>
|
||||
|
@ -35,22 +35,53 @@ export function Routes() {
|
|||
<LendingProvider>
|
||||
<AppLayout>
|
||||
<Switch>
|
||||
<Route exact path='/' component={() => <HomeView />} />
|
||||
<Route exact path='/dashboard' children={<DashboardView />} />
|
||||
<Route path='/reserve/:id' children={<ReserveView />} />
|
||||
<Route exact path='/deposit' component={() => <DepositView />} />
|
||||
<Route path='/deposit/:id' children={<DepositReserveView />} />
|
||||
<Route path='/withdraw/:id' children={<WithdrawView />} />
|
||||
<Route exact path='/borrow' children={<BorrowView />} />
|
||||
<Route path='/borrow/:id' children={<BorrowReserveView />} />
|
||||
<Route path='/repay/loan/:obligation' children={<RepayReserveView />} />
|
||||
<Route path='/repay/:reserve' children={<RepayReserveView />} />
|
||||
<Route exact path='/liquidate' children={<LiquidateView />} />
|
||||
<Route path='/liquidate/:id' children={<LiquidateReserveView />} />
|
||||
<Route exact path='/margin' children={<MarginTrading />} />
|
||||
<Route exact path="/" component={() => <HomeView />} />
|
||||
<Route
|
||||
exact
|
||||
path="/dashboard"
|
||||
children={<DashboardView />}
|
||||
/>
|
||||
<Route path="/reserve/:id" children={<ReserveView />} />
|
||||
<Route
|
||||
exact
|
||||
path="/deposit"
|
||||
component={() => <DepositView />}
|
||||
/>
|
||||
<Route
|
||||
path="/deposit/:id"
|
||||
children={<DepositReserveView />}
|
||||
/>
|
||||
<Route path="/withdraw/:id" children={<WithdrawView />} />
|
||||
<Route exact path="/borrow" children={<BorrowView />} />
|
||||
<Route
|
||||
path="/borrow/:id"
|
||||
children={<BorrowReserveView />}
|
||||
/>
|
||||
<Route
|
||||
path="/repay/loan/:obligation"
|
||||
children={<RepayReserveView />}
|
||||
/>
|
||||
<Route
|
||||
path="/repay/:reserve"
|
||||
children={<RepayReserveView />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/liquidate"
|
||||
children={<LiquidateView />}
|
||||
/>
|
||||
<Route
|
||||
path="/liquidate/:id"
|
||||
children={<LiquidateReserveView />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/margin"
|
||||
children={<MarginTrading />}
|
||||
/>
|
||||
|
||||
<Route path='/margin/:id' children={<NewPosition />} />
|
||||
<Route exact path='/faucet' children={<FaucetView />} />
|
||||
<Route path="/margin/:id" children={<NewPosition />} />
|
||||
<Route exact path="/faucet" children={<FaucetView />} />
|
||||
</Switch>
|
||||
</AppLayout>
|
||||
</LendingProvider>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { EventEmitter as Emitter } from 'eventemitter3';
|
||||
import { EventEmitter as Emitter } from "eventemitter3";
|
||||
|
||||
export class CacheUpdateEvent {
|
||||
static type = 'CacheUpdate';
|
||||
static type = "CacheUpdate";
|
||||
id: string;
|
||||
parser: any;
|
||||
isNew: boolean;
|
||||
|
@ -13,7 +13,7 @@ export class CacheUpdateEvent {
|
|||
}
|
||||
|
||||
export class CacheDeleteEvent {
|
||||
static type = 'CacheUpdate';
|
||||
static type = "CacheUpdate";
|
||||
id: string;
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
|
@ -21,7 +21,7 @@ export class CacheDeleteEvent {
|
|||
}
|
||||
|
||||
export class MarketUpdateEvent {
|
||||
static type = 'MarketUpdate';
|
||||
static type = "MarketUpdate";
|
||||
ids: Set<string>;
|
||||
constructor(ids: Set<string>) {
|
||||
this.ids = ids;
|
||||
|
@ -48,7 +48,10 @@ export class EventEmitter {
|
|||
}
|
||||
|
||||
raiseCacheUpdated(id: string, isNew: boolean, parser: any) {
|
||||
this.emitter.emit(CacheUpdateEvent.type, new CacheUpdateEvent(id, isNew, parser));
|
||||
this.emitter.emit(
|
||||
CacheUpdateEvent.type,
|
||||
new CacheUpdateEvent(id, isNew, parser)
|
||||
);
|
||||
}
|
||||
|
||||
raiseCacheDeleted(id: string) {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { TokenSwapLayout, TokenSwapLayoutV1 } from '../models';
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { TokenSwapLayout, TokenSwapLayoutV1 } from "../models";
|
||||
|
||||
export const WRAPPED_SOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
|
||||
export let TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
||||
export const WRAPPED_SOL_MINT = new PublicKey(
|
||||
"So11111111111111111111111111111111111111112"
|
||||
);
|
||||
export let TOKEN_PROGRAM_ID = new PublicKey(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
);
|
||||
|
||||
export let LENDING_PROGRAM_ID = new PublicKey('TokenLending1111111111111111111111111111111');
|
||||
export let LENDING_PROGRAM_ID = new PublicKey(
|
||||
"TokenLending1111111111111111111111111111111"
|
||||
);
|
||||
|
||||
let SWAP_PROGRAM_ID: PublicKey;
|
||||
let SWAP_PROGRAM_LEGACY_IDS: PublicKey[];
|
||||
|
@ -14,17 +20,17 @@ export const LEND_HOST_FEE_ADDRESS = process.env.REACT_APP_LEND_HOST_FEE_ADDRESS
|
|||
? new PublicKey(`${process.env.REACT_APP_LEND_HOST_FEE_ADDRESS}`)
|
||||
: undefined;
|
||||
|
||||
console.debug(`Lend host fee address: ${LEND_HOST_FEE_ADDRESS?.toBase58()}`);
|
||||
console.debug(`Lend host fee address: ${LEND_HOST_FEE_ADDRESS?.toBase58()}`);
|
||||
|
||||
export const ENABLE_FEES_INPUT = false;
|
||||
|
||||
// legacy pools are used to show users contributions in those pools to allow for withdrawals of funds
|
||||
export const PROGRAM_IDS = [
|
||||
{
|
||||
name: 'mainnet-beta',
|
||||
name: "mainnet-beta",
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey('9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL'),
|
||||
pubkey: new PublicKey("9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL"),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [
|
||||
|
@ -34,30 +40,30 @@ export const PROGRAM_IDS = [
|
|||
}),
|
||||
},
|
||||
{
|
||||
name: 'testnet',
|
||||
name: "testnet",
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey('2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg'),
|
||||
pubkey: new PublicKey("2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg"),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'devnet',
|
||||
name: "devnet",
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey('6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj'),
|
||||
pubkey: new PublicKey("6Cust2JhvweKLh4CVo1dt21s2PJ86uNGkziudpkNPaCj"),
|
||||
layout: TokenSwapLayout,
|
||||
},
|
||||
legacy: [new PublicKey('BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ')],
|
||||
legacy: [new PublicKey("BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ")],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'localnet',
|
||||
name: "localnet",
|
||||
swap: () => ({
|
||||
current: {
|
||||
pubkey: new PublicKey('369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7'),
|
||||
pubkey: new PublicKey("369YmCWHGxznT7GGBhcLZDRcRoGWmGKFWdmtiPy78yj7"),
|
||||
layout: TokenSwapLayoutV1,
|
||||
},
|
||||
legacy: [],
|
||||
|
@ -77,8 +83,10 @@ export const setProgramIds = (envName: string) => {
|
|||
SWAP_PROGRAM_LAYOUT = swap.current.layout;
|
||||
SWAP_PROGRAM_LEGACY_IDS = swap.legacy;
|
||||
|
||||
if (envName === 'mainnet-beta') {
|
||||
LENDING_PROGRAM_ID = new PublicKey('2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW');
|
||||
if (envName === "mainnet-beta") {
|
||||
LENDING_PROGRAM_ID = new PublicKey(
|
||||
"2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import { Connection, PublicKey } from '@solana/web3.js';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { MintLayout, AccountLayout } from '@solana/spl-token';
|
||||
import { programIds } from './ids';
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { MintLayout, AccountLayout } from "@solana/spl-token";
|
||||
import { programIds } from "./ids";
|
||||
import {
|
||||
PoolInfo,
|
||||
TokenSwapLayout,
|
||||
TokenSwapLayoutLegacyV0 as TokenSwapLayoutV0,
|
||||
TokenSwapLayoutV1,
|
||||
} from './../models';
|
||||
import { useConnection } from '../contexts/connection';
|
||||
import { cache, getMultipleAccounts, TokenAccountParser } from '../contexts/accounts';
|
||||
} from "./../models";
|
||||
import { useConnection } from "../contexts/connection";
|
||||
import {
|
||||
cache,
|
||||
getMultipleAccounts,
|
||||
TokenAccountParser,
|
||||
} from "../contexts/accounts";
|
||||
|
||||
export const LIQUIDITY_PROVIDER_FEE = 0.003;
|
||||
export const SERUM_FEE = 0.0005;
|
||||
|
||||
|
||||
const getHoldings = (connection: Connection, accounts: string[]) => {
|
||||
return accounts.map((acc) => cache.query(connection, new PublicKey(acc)));
|
||||
};
|
||||
|
@ -77,10 +80,16 @@ export const usePools = () => {
|
|||
// TODO: this is not great
|
||||
// Ideally SwapLayout stores hash of all the mints to make finding of pool for a pair easier
|
||||
const holdings = await Promise.all(
|
||||
getHoldings(connection, [result.data.tokenAccountA, result.data.tokenAccountB])
|
||||
getHoldings(connection, [
|
||||
result.data.tokenAccountA,
|
||||
result.data.tokenAccountB,
|
||||
])
|
||||
);
|
||||
|
||||
pool.pubkeys.holdingMints = [holdings[0].info.mint, holdings[1].info.mint] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [
|
||||
holdings[0].info.mint,
|
||||
holdings[1].info.mint,
|
||||
] as PublicKey[];
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
@ -91,7 +100,10 @@ export const usePools = () => {
|
|||
let pool = toPoolInfo(result, swapId);
|
||||
pool.legacy = isLegacy;
|
||||
pool.pubkeys.feeAccount = result.data.feeAccount;
|
||||
pool.pubkeys.holdingMints = [result.data.mintA, result.data.mintB] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [
|
||||
result.data.mintA,
|
||||
result.data.mintB,
|
||||
] as PublicKey[];
|
||||
|
||||
poolsArray.push(pool as PoolInfo);
|
||||
}
|
||||
|
@ -113,7 +125,8 @@ export const usePools = () => {
|
|||
|
||||
// This will pre-cache all accounts used by pools
|
||||
// All those accounts are updated whenever there is a change
|
||||
await getMultipleAccounts(connection, toQuery, 'single').then(({ keys, array }) => {
|
||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||
({ keys, array }) => {
|
||||
return array.map((obj, index) => {
|
||||
const pubKey = keys[index];
|
||||
if (obj.data.length === AccountLayout.span) {
|
||||
|
@ -126,15 +139,17 @@ export const usePools = () => {
|
|||
|
||||
return obj;
|
||||
}) as any[];
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return poolsArray;
|
||||
};
|
||||
Promise.all([queryPools(programIds().swap), ...programIds().swap_legacy.map((leg) => queryPools(leg, true))]).then(
|
||||
(all) => {
|
||||
Promise.all([
|
||||
queryPools(programIds().swap),
|
||||
...programIds().swap_legacy.map((leg) => queryPools(leg, true)),
|
||||
]).then((all) => {
|
||||
setPools(all.flat());
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -150,7 +165,9 @@ export const usePools = () => {
|
|||
pubkey: new PublicKey(id),
|
||||
};
|
||||
|
||||
const index = pools && pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
||||
const index =
|
||||
pools &&
|
||||
pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
|
||||
if (index && index >= 0 && pools) {
|
||||
// TODO: check if account is empty?
|
||||
|
||||
|
@ -160,13 +177,16 @@ export const usePools = () => {
|
|||
let pool = toPoolInfo(updated, programIds().swap);
|
||||
|
||||
pool.pubkeys.feeAccount = updated.data.feeAccount;
|
||||
pool.pubkeys.holdingMints = [updated.data.mintA, updated.data.mintB] as PublicKey[];
|
||||
pool.pubkeys.holdingMints = [
|
||||
updated.data.mintA,
|
||||
updated.data.mintB,
|
||||
] as PublicKey[];
|
||||
|
||||
setPools([...pools, pool]);
|
||||
}
|
||||
}
|
||||
},
|
||||
'singleGossip'
|
||||
"singleGossip"
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
@ -198,7 +218,10 @@ export const usePoolForBasket = (mints: (string | undefined)[]) => {
|
|||
for (let i = 0; i < matchingPool.length; i++) {
|
||||
const p = matchingPool[i];
|
||||
|
||||
const account = await cache.query(connection, p.pubkeys.holdingAccounts[0]);
|
||||
const account = await cache.query(
|
||||
connection,
|
||||
p.pubkeys.holdingAccounts[0]
|
||||
);
|
||||
|
||||
if (!account.info.amount.eqn(0)) {
|
||||
setPool(p);
|
||||
|
@ -216,7 +239,9 @@ function estimateProceedsFromInput(
|
|||
proceedsQuantityInPool: number,
|
||||
inputAmount: number
|
||||
): number {
|
||||
return (proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount);
|
||||
return (
|
||||
(proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount)
|
||||
);
|
||||
}
|
||||
|
||||
function estimateInputFromProceeds(
|
||||
|
@ -225,10 +250,13 @@ function estimateInputFromProceeds(
|
|||
proceedsAmount: number
|
||||
): number | string {
|
||||
if (proceedsAmount >= proceedsQuantityInPool) {
|
||||
return 'Not possible';
|
||||
return "Not possible";
|
||||
}
|
||||
|
||||
return (inputQuantityInPool * proceedsAmount) / (proceedsQuantityInPool - proceedsAmount);
|
||||
return (
|
||||
(inputQuantityInPool * proceedsAmount) /
|
||||
(proceedsQuantityInPool - proceedsAmount)
|
||||
);
|
||||
}
|
||||
|
||||
export enum PoolOperation {
|
||||
|
@ -245,14 +273,20 @@ export async function calculateDependentAmount(
|
|||
op: PoolOperation
|
||||
): Promise<number | string | undefined> {
|
||||
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
|
||||
const accountA = await cache.query(connection, pool.pubkeys.holdingAccounts[0]);
|
||||
const accountA = await cache.query(
|
||||
connection,
|
||||
pool.pubkeys.holdingAccounts[0]
|
||||
);
|
||||
const amountA = accountA.info.amount.toNumber();
|
||||
|
||||
const accountB = await cache.query(connection, pool.pubkeys.holdingAccounts[1]);
|
||||
const accountB = await cache.query(
|
||||
connection,
|
||||
pool.pubkeys.holdingAccounts[1]
|
||||
);
|
||||
let amountB = accountB.info.amount.toNumber();
|
||||
|
||||
if (!poolMint.mintAuthority) {
|
||||
throw new Error('Mint doesnt have authority');
|
||||
throw new Error("Mint doesnt have authority");
|
||||
}
|
||||
|
||||
if (poolMint.supply.eqn(0)) {
|
||||
|
@ -274,8 +308,14 @@ export async function calculateDependentAmount(
|
|||
}
|
||||
|
||||
const isFirstIndependent = accountA.info.mint.toBase58() === independent;
|
||||
const depPrecision = Math.pow(10, isFirstIndependent ? mintB.decimals : mintA.decimals);
|
||||
const indPrecision = Math.pow(10, isFirstIndependent ? mintA.decimals : mintB.decimals);
|
||||
const depPrecision = Math.pow(
|
||||
10,
|
||||
isFirstIndependent ? mintB.decimals : mintA.decimals
|
||||
);
|
||||
const indPrecision = Math.pow(
|
||||
10,
|
||||
isFirstIndependent ? mintA.decimals : mintB.decimals
|
||||
);
|
||||
const indAdjustedAmount = amount * indPrecision;
|
||||
|
||||
let indBasketQuantity = isFirstIndependent ? amountA : amountB;
|
||||
|
@ -290,18 +330,27 @@ export async function calculateDependentAmount(
|
|||
} else {
|
||||
switch (+op) {
|
||||
case PoolOperation.Add:
|
||||
depAdjustedAmount = (depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
||||
depAdjustedAmount =
|
||||
(depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
|
||||
break;
|
||||
case PoolOperation.SwapGivenProceeds:
|
||||
depAdjustedAmount = estimateInputFromProceeds(depBasketQuantity, indBasketQuantity, indAdjustedAmount);
|
||||
depAdjustedAmount = estimateInputFromProceeds(
|
||||
depBasketQuantity,
|
||||
indBasketQuantity,
|
||||
indAdjustedAmount
|
||||
);
|
||||
break;
|
||||
case PoolOperation.SwapGivenInput:
|
||||
depAdjustedAmount = estimateProceedsFromInput(indBasketQuantity, depBasketQuantity, indAdjustedAmount);
|
||||
depAdjustedAmount = estimateProceedsFromInput(
|
||||
indBasketQuantity,
|
||||
depBasketQuantity,
|
||||
indAdjustedAmount
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof depAdjustedAmount === 'string') {
|
||||
if (typeof depAdjustedAmount === "string") {
|
||||
return depAdjustedAmount;
|
||||
}
|
||||
if (depAdjustedAmount === undefined) {
|
||||
|
|
|
@ -9,7 +9,7 @@ export const BorrowView = () => {
|
|||
const { reserveAccounts } = useLendingReserves();
|
||||
return (
|
||||
<div className="flexColumn">
|
||||
<Card >
|
||||
<Card>
|
||||
<div className="borrow-item deposit-header">
|
||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||
<div>Serum Dex Price</div>
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import React from 'react';
|
||||
import { useTokenName, useBorrowingPower } from '../../hooks';
|
||||
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
|
||||
import { TokenIcon } from '../../components/TokenIcon';
|
||||
import { formatNumber, formatPct } from '../../utils/utils';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
import React from "react";
|
||||
import { useTokenName, useBorrowingPower } from "../../hooks";
|
||||
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../../components/TokenIcon";
|
||||
import { formatNumber, formatPct } from "../../utils/utils";
|
||||
import { Button } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { LABELS } from "../../constants";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
|
||||
export const BorrowItem = (props: { reserve: LendingReserve; address: PublicKey }) => {
|
||||
export const BorrowItem = (props: {
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
const name = useTokenName(props.reserve.liquidityMint);
|
||||
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
|
||||
|
||||
|
@ -19,8 +22,8 @@ export const BorrowItem = (props: { reserve: LendingReserve; address: PublicKey
|
|||
|
||||
return (
|
||||
<Link to={`/borrow/${props.address.toBase58()}`}>
|
||||
<div className='borrow-item'>
|
||||
<span style={{ display: 'flex' }}>
|
||||
<div className="borrow-item">
|
||||
<span style={{ display: "flex" }}>
|
||||
<TokenIcon mintAddress={props.reserve.liquidityMint} />
|
||||
{name}
|
||||
</span>
|
||||
|
@ -30,12 +33,14 @@ export const BorrowItem = (props: { reserve: LendingReserve; address: PublicKey
|
|||
<div>
|
||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(totalInQuote)}</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(apr)}</div>
|
||||
<div>
|
||||
<Button type='primary'>
|
||||
<Button type="primary">
|
||||
<span>{LABELS.BORROW_ACTION}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import React from "react";
|
||||
import { useBorrowingPower, useLendingReserve, useUserObligations } from "../../hooks";
|
||||
import {
|
||||
useBorrowingPower,
|
||||
useLendingReserve,
|
||||
useUserObligations,
|
||||
} from "../../hooks";
|
||||
import { useParams } from "react-router-dom";
|
||||
import "./style.less";
|
||||
|
||||
|
@ -17,7 +21,7 @@ export const BorrowReserveView = () => {
|
|||
const lendingReserve = useLendingReserve(id);
|
||||
const { userObligations, totalInQuote: loansValue } = useUserObligations();
|
||||
|
||||
const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id)
|
||||
const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id);
|
||||
|
||||
if (!lendingReserve) {
|
||||
return null;
|
||||
|
@ -62,17 +66,17 @@ export const BorrowReserveView = () => {
|
|||
<BarChartStatistic
|
||||
title="Your Loans"
|
||||
items={userObligations}
|
||||
getPct={(item) => item.obligation.info.borrowedInQuote / loansValue}
|
||||
name={(item) => item.obligation.info.repayName} />
|
||||
getPct={(item) =>
|
||||
item.obligation.info.borrowedInQuote / loansValue
|
||||
}
|
||||
name={(item) => item.obligation.info.repayName}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={GUTTER} style={{ flex: 1 }}>
|
||||
<Col xs={24} xl={15}>
|
||||
<BorrowInput
|
||||
className="card-fill"
|
||||
reserve={lendingReserve}
|
||||
/>
|
||||
<BorrowInput className="card-fill" reserve={lendingReserve} />
|
||||
</Col>
|
||||
<Col xs={24} xl={9}>
|
||||
<SideReserveOverview
|
||||
|
|
|
@ -9,15 +9,23 @@ import { DepositItem } from "./item";
|
|||
export const DashboardDeposits = () => {
|
||||
const { userDeposits, totalInQuote } = useUserDeposits();
|
||||
|
||||
return (<Card title={
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<div className="dashboard-title">
|
||||
<div>{LABELS.DASHBOARD_TITLE_DEPOSITS}</div>
|
||||
<div><span>{LABELS.TOTAL_TITLE}: </span>${formatNumber.format(totalInQuote)}</div>
|
||||
</div>}>
|
||||
<div>
|
||||
<span>{LABELS.TOTAL_TITLE}: </span>$
|
||||
{formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<BarChartStatistic
|
||||
items={userDeposits}
|
||||
getPct={(item) => item.info.amountInQuote / totalInQuote}
|
||||
name={(item) => item.info.name} />
|
||||
name={(item) => item.info.name}
|
||||
/>
|
||||
<div className="dashboard-item dashboard-header">
|
||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||
<div>{LABELS.TABLE_TITLE_DEPOSIT_BALANCE}</div>
|
||||
|
|
|
@ -7,10 +7,8 @@ import { Button } from "antd";
|
|||
import { Link } from "react-router-dom";
|
||||
import { LABELS } from "../../../constants";
|
||||
|
||||
export const DepositItem = (props: {
|
||||
userDeposit: UserDeposit;
|
||||
}) => {
|
||||
const {reserve, info} = props.userDeposit;
|
||||
export const DepositItem = (props: { userDeposit: UserDeposit }) => {
|
||||
const { reserve, info } = props.userDeposit;
|
||||
const mintAddress = reserve.info.liquidityMint;
|
||||
const name = useTokenName(mintAddress);
|
||||
|
||||
|
@ -26,8 +24,12 @@ export const DepositItem = (props: {
|
|||
</span>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(info.amount)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(info.amountInQuote)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(info.amount)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(info.amountInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(depositAPY)}</div>
|
||||
|
|
|
@ -16,30 +16,38 @@ export const DashboardView = () => {
|
|||
<div className="dashboard-container">
|
||||
{!connected && (
|
||||
<div className="dashboard-info">
|
||||
<img src="splash.svg" alt="connect your wallet" className="dashboard-splash"/>
|
||||
<img
|
||||
src="splash.svg"
|
||||
alt="connect your wallet"
|
||||
className="dashboard-splash"
|
||||
/>
|
||||
{LABELS.DASHBOARD_INFO}
|
||||
</div>
|
||||
)}
|
||||
{connected &&
|
||||
userDeposits.length === 0 &&
|
||||
userObligations.length === 0 && (
|
||||
{connected && userDeposits.length === 0 && userObligations.length === 0 && (
|
||||
<div className="dashboard-info">
|
||||
<img src="splash.svg" alt="connect your wallet" className="dashboard-splash"/>
|
||||
<img
|
||||
src="splash.svg"
|
||||
alt="connect your wallet"
|
||||
className="dashboard-splash"
|
||||
/>
|
||||
{LABELS.NO_LOANS_NO_DEPOSITS}
|
||||
</div>
|
||||
)}
|
||||
{connected && <Row gutter={GUTTER} >
|
||||
{userDeposits.length >0 && (
|
||||
{connected && (
|
||||
<Row gutter={GUTTER}>
|
||||
{userDeposits.length > 0 && (
|
||||
<Col md={24} xl={12} span={24}>
|
||||
<DashboardDeposits />
|
||||
</Col>
|
||||
)}
|
||||
{userObligations.length >0 && (
|
||||
{userObligations.length > 0 && (
|
||||
<Col md={24} xl={12} span={24}>
|
||||
<DashboardObligations />
|
||||
</Col>
|
||||
)}
|
||||
</Row>}
|
||||
</Row>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,15 +10,22 @@ export const DashboardObligations = () => {
|
|||
const { userObligations, totalInQuote } = useUserObligations();
|
||||
|
||||
return (
|
||||
<Card title={
|
||||
<Card
|
||||
title={
|
||||
<div className="dashboard-title">
|
||||
<div>{LABELS.DASHBOARD_TITLE_LOANS}</div>
|
||||
<div><span>{LABELS.TOTAL_TITLE}: </span>${formatNumber.format(totalInQuote)}</div>
|
||||
</div>}>
|
||||
<div>
|
||||
<span>{LABELS.TOTAL_TITLE}: </span>$
|
||||
{formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<BarChartStatistic
|
||||
items={userObligations}
|
||||
getPct={(item) => item.obligation.info.borrowedInQuote / totalInQuote}
|
||||
name={(item) => item.obligation.info.repayName} />
|
||||
name={(item) => item.obligation.info.repayName}
|
||||
/>
|
||||
<div className="dashboard-item dashboard-header">
|
||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||
<div>{LABELS.TABLE_TITLE_YOUR_LOAN_BALANCE}</div>
|
||||
|
|
|
@ -67,18 +67,28 @@ export const ObligationItem = (props: {
|
|||
</span>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(borrowAmount)}</em> {borrowName}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(obligation.info.borrowedInQuote)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(borrowAmount)}</em> {borrowName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(obligation.info.borrowedInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(collateral)}</em> {collateralName}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(obligation.info.collateralInQuote)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateral)}</em> {collateralName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(obligation.info.collateralInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(borrowAPY)}</div>
|
||||
<div style={{ color: healthFactorToRiskColor(obligation.info.health)}}>{formatPct.format(obligation.info.ltv / 100)}</div>
|
||||
<div style={{ color: healthFactorToRiskColor(obligation.info.health) }}>
|
||||
{formatPct.format(obligation.info.ltv / 100)}
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<Link to={`/borrow/${borrowReserve.pubkey.toBase58()}`}>
|
||||
<Button type="primary">
|
||||
|
|
|
@ -8,7 +8,7 @@ export const DepositView = () => {
|
|||
const { reserveAccounts } = useLendingReserves();
|
||||
return (
|
||||
<div className="flexColumn">
|
||||
<Card >
|
||||
<Card>
|
||||
<div className="deposit-item deposit-header">
|
||||
<div>Asset</div>
|
||||
<div>Your wallet balance</div>
|
||||
|
|
|
@ -17,10 +17,14 @@ export const ReserveItem = (props: {
|
|||
address: PublicKey;
|
||||
}) => {
|
||||
const name = useTokenName(props.reserve.liquidityMint);
|
||||
const { balance: tokenBalance, balanceInUSD: tokenBalanceInUSD } = useUserBalance(props.reserve.liquidityMint);
|
||||
const { balance: collateralBalance, balanceInUSD: collateralBalanceInUSD } = useUserCollateralBalance(
|
||||
props.reserve
|
||||
);
|
||||
const {
|
||||
balance: tokenBalance,
|
||||
balanceInUSD: tokenBalanceInUSD,
|
||||
} = useUserBalance(props.reserve.liquidityMint);
|
||||
const {
|
||||
balance: collateralBalance,
|
||||
balanceInUSD: collateralBalanceInUSD,
|
||||
} = useUserCollateralBalance(props.reserve);
|
||||
|
||||
const apy = calculateDepositAPY(props.reserve);
|
||||
|
||||
|
@ -33,14 +37,22 @@ export const ReserveItem = (props: {
|
|||
</span>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(tokenBalance)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(tokenBalanceInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(tokenBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(tokenBalanceInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(collateralBalance)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(collateralBalanceInUSD)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateralBalance)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(collateralBalanceInUSD)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(apy)}</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ export const HomeView = () => {
|
|||
borrowed: 0,
|
||||
lentOutPct: 0,
|
||||
items: [],
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const refreshTotal = () => {
|
||||
|
@ -48,26 +48,29 @@ export const HomeView = () => {
|
|||
|
||||
let leaf = {
|
||||
key: item.pubkey.toBase58(),
|
||||
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
|
||||
price,
|
||||
borrowed: fromLamports(
|
||||
marketSize:
|
||||
fromLamports(marketCapLamports, liquidityMint?.info) * price,
|
||||
borrowed:
|
||||
fromLamports(
|
||||
wadToLamports(item.info?.state.borrowedLiquidityWad).toNumber(),
|
||||
liquidityMint.info
|
||||
) *
|
||||
price,
|
||||
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
|
||||
}
|
||||
) * price,
|
||||
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58()),
|
||||
};
|
||||
|
||||
newTotals.items.push(leaf);
|
||||
|
||||
newTotals.marketSize = newTotals.marketSize + leaf.marketSize;
|
||||
newTotals.borrowed = newTotals.borrowed + leaf.borrowed;
|
||||
|
||||
});
|
||||
|
||||
newTotals.lentOutPct = newTotals.borrowed / newTotals.marketSize;
|
||||
newTotals.lentOutPct = Number.isFinite(newTotals.lentOutPct) ? newTotals.lentOutPct : 0;
|
||||
newTotals.items = newTotals.items.sort((a, b) => b.marketSize - a.marketSize)
|
||||
newTotals.lentOutPct = Number.isFinite(newTotals.lentOutPct)
|
||||
? newTotals.lentOutPct
|
||||
: 0;
|
||||
newTotals.items = newTotals.items.sort(
|
||||
(a, b) => b.marketSize - a.marketSize
|
||||
);
|
||||
|
||||
setTotals(newTotals);
|
||||
};
|
||||
|
@ -85,9 +88,7 @@ export const HomeView = () => {
|
|||
|
||||
return (
|
||||
<div className="flexColumn">
|
||||
<Row
|
||||
gutter={GUTTER}
|
||||
className="home-info-row" >
|
||||
<Row gutter={GUTTER} className="home-info-row">
|
||||
<Col xs={24} xl={5}>
|
||||
<Card>
|
||||
<Statistic
|
||||
|
@ -125,7 +126,8 @@ export const HomeView = () => {
|
|||
title="Market composition"
|
||||
name={(item) => item.name}
|
||||
getPct={(item) => item.marketSize / totals.marketSize}
|
||||
items={totals.items} />
|
||||
items={totals.items}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -139,7 +141,13 @@ export const HomeView = () => {
|
|||
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
|
||||
</div>
|
||||
{reserveAccounts.map((account) => (
|
||||
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
|
||||
<LendingReserveItem
|
||||
reserve={account.info}
|
||||
address={account.pubkey}
|
||||
item={totals.items.find(
|
||||
(item) => item.key === account.pubkey.toBase58()
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
@ -59,19 +59,25 @@ export const LendingReserveItem = (props: {
|
|||
</span>
|
||||
<div title={marketSize.toString()}>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(marketSize)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(props.item?.marketSize)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(marketSize)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(props.item?.marketSize)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div title={totalBorrows.toString()}>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(totalBorrows)}</em> {name}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(props.item?.borrowed)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(totalBorrows)}</em> {name}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(props.item?.borrowed)}
|
||||
</div>
|
||||
</div>
|
||||
<div title={depositAPY.toString()}>
|
||||
{formatPct.format(depositAPY)}
|
||||
</div>
|
||||
<div title={depositAPY.toString()}>{formatPct.format(depositAPY)}</div>
|
||||
<div title={borrowAPY.toString()}>{formatPct.format(borrowAPY)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
@ -9,17 +9,29 @@ import { BarChartStatistic } from "../../components/BarChartStatistic";
|
|||
export const LiquidateView = () => {
|
||||
const { obligations } = useEnrichedLendingObligations();
|
||||
|
||||
const atRisk = useMemo(() => obligations.filter(item => item.info.health < 1.0), [obligations]);
|
||||
const atRisk = useMemo(
|
||||
() => obligations.filter((item) => item.info.health < 1.0),
|
||||
[obligations]
|
||||
);
|
||||
|
||||
const valueAtRisk = useMemo(() => atRisk.reduce((acc, item) => acc + item.info.collateralInQuote, 0), [atRisk]);
|
||||
const valueAtRisk = useMemo(
|
||||
() => atRisk.reduce((acc, item) => acc + item.info.collateralInQuote, 0),
|
||||
[atRisk]
|
||||
);
|
||||
const loansAtRiskCount = useMemo(() => atRisk.length, [atRisk]);
|
||||
const pctAtRisk = useMemo(() => atRisk.length / obligations.length, [atRisk, obligations]);
|
||||
const pctAtRisk = useMemo(() => atRisk.length / obligations.length, [
|
||||
atRisk,
|
||||
obligations,
|
||||
]);
|
||||
|
||||
const groupedLoans = useMemo(() => {
|
||||
return atRisk.reduce((acc, item) => {
|
||||
acc.set(item.info.repayName, (acc.get(item.info.collateralName) || 0) + item.info.collateralInQuote);
|
||||
acc.set(
|
||||
item.info.repayName,
|
||||
(acc.get(item.info.collateralName) || 0) + item.info.collateralInQuote
|
||||
);
|
||||
return acc;
|
||||
}, new Map<string, number>())
|
||||
}, new Map<string, number>());
|
||||
}, [atRisk]);
|
||||
|
||||
const keys = useMemo(() => [...groupedLoans.keys()], [groupedLoans]);
|
||||
|
@ -33,14 +45,11 @@ export const LiquidateView = () => {
|
|||
<Row gutter={GUTTER}>
|
||||
<Col span={24}>
|
||||
<Card>
|
||||
<Typography>
|
||||
{LABELS.LIQUIDATION_INFO}
|
||||
</Typography>
|
||||
<Typography>{LABELS.LIQUIDATION_INFO}</Typography>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row
|
||||
gutter={GUTTER}>
|
||||
<Row gutter={GUTTER}>
|
||||
<Col xs={24} xl={5}>
|
||||
<Card>
|
||||
<Statistic
|
||||
|
@ -77,7 +86,8 @@ export const LiquidateView = () => {
|
|||
title="At risk loan composition"
|
||||
name={(item) => item}
|
||||
getPct={(item) => (groupedLoans.get(item) || 0) / valueAtRisk}
|
||||
items={keys} />
|
||||
items={keys}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { cache, ParsedAccount, useMint } from "../../contexts/accounts";
|
||||
import { LendingReserve, calculateBorrowAPY, collateralToLiquidity } from "../../models/lending";
|
||||
import {
|
||||
LendingReserve,
|
||||
calculateBorrowAPY,
|
||||
collateralToLiquidity,
|
||||
} from "../../models/lending";
|
||||
import { EnrichedLendingObligation, useTokenName } from "../../hooks";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Button } from "antd";
|
||||
|
@ -60,14 +64,22 @@ export const LiquidateItem = (props: { item: EnrichedLendingObligation }) => {
|
|||
</span>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(borrowAmount)}</em> {borrowName}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(obligation.borrowedInQuote)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(borrowAmount)}</em> {borrowName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(obligation.borrowedInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div><em>{formatNumber.format(collateral)}</em> {collateralName}</div>
|
||||
<div className="dashboard-amount-quote">${formatNumber.format(obligation.collateralInQuote)}</div>
|
||||
<div>
|
||||
<em>{formatNumber.format(collateral)}</em> {collateralName}
|
||||
</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(obligation.collateralInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(borrowAPY)}</div>
|
||||
|
|
|
@ -15,9 +15,7 @@ export const LiquidateReserveView = () => {
|
|||
|
||||
const obligation = useEnrichedLendingObligation(id);
|
||||
const repayReserve = useLendingReserve(obligation?.info.borrowReserve);
|
||||
const withdrawReserve = useLendingReserve(
|
||||
obligation?.info.collateralReserve
|
||||
);
|
||||
const withdrawReserve = useLendingReserve(obligation?.info.collateralReserve);
|
||||
|
||||
if (!obligation || !repayReserve) {
|
||||
return null;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import React from 'react';
|
||||
import { LABELS } from '../../constants';
|
||||
import './itemStyle.less';
|
||||
import { Card } from 'antd';
|
||||
import { useLendingReserves } from '../../hooks/useLendingReserves';
|
||||
import { MarginTradeItem } from './item';
|
||||
import React from "react";
|
||||
import { LABELS } from "../../constants";
|
||||
import "./itemStyle.less";
|
||||
import { Card } from "antd";
|
||||
import { useLendingReserves } from "../../hooks/useLendingReserves";
|
||||
import { MarginTradeItem } from "./item";
|
||||
|
||||
export const MarginTrading = () => {
|
||||
const { reserveAccounts } = useLendingReserves();
|
||||
return (
|
||||
<div className='flexColumn'>
|
||||
<div className="flexColumn">
|
||||
<Card>
|
||||
<div className='choose-margin-item choose-margin-header'>
|
||||
<div className="choose-margin-item choose-margin-header">
|
||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||
<div>Serum Dex Price</div>
|
||||
<div>{LABELS.TABLE_TITLE_BUYING_POWER}</div>
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
import React from 'react';
|
||||
import { useBorrowingPower, useTokenName } from '../../hooks';
|
||||
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
|
||||
import { TokenIcon } from '../../components/TokenIcon';
|
||||
import { formatNumber, formatPct } from '../../utils/utils';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
import { useMidPriceInUSD } from '../../contexts/market';
|
||||
import React from "react";
|
||||
import { useBorrowingPower, useTokenName } from "../../hooks";
|
||||
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
|
||||
import { TokenIcon } from "../../components/TokenIcon";
|
||||
import { formatNumber, formatPct } from "../../utils/utils";
|
||||
import { Button } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { LABELS } from "../../constants";
|
||||
import { useMidPriceInUSD } from "../../contexts/market";
|
||||
|
||||
export const MarginTradeItem = (props: { reserve: LendingReserve; address: PublicKey }) => {
|
||||
export const MarginTradeItem = (props: {
|
||||
reserve: LendingReserve;
|
||||
address: PublicKey;
|
||||
}) => {
|
||||
const name = useTokenName(props.reserve.liquidityMint);
|
||||
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
|
||||
|
||||
const apr = calculateBorrowAPY(props.reserve);
|
||||
|
||||
// TODO: specifc max leverage
|
||||
const { totalInQuote, borrowingPower } = useBorrowingPower(props.address, false, false);
|
||||
const { totalInQuote, borrowingPower } = useBorrowingPower(
|
||||
props.address,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
return (
|
||||
<Link to={`/margin/${props.address.toBase58()}`}>
|
||||
<div className='choose-margin-item'>
|
||||
<span style={{ display: 'flex' }}>
|
||||
<div className="choose-margin-item">
|
||||
<span style={{ display: "flex" }}>
|
||||
<TokenIcon mintAddress={props.reserve.liquidityMint} />
|
||||
{name}
|
||||
</span>
|
||||
|
@ -31,12 +38,14 @@ export const MarginTradeItem = (props: { reserve: LendingReserve; address: Publi
|
|||
<div>
|
||||
<em>{formatNumber.format(borrowingPower)}</em> {name}
|
||||
</div>
|
||||
<div className='dashboard-amount-quote'>${formatNumber.format(totalInQuote)}</div>
|
||||
<div className="dashboard-amount-quote">
|
||||
${formatNumber.format(totalInQuote)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPct.format(apr)}</div>
|
||||
<div>
|
||||
<Button type='primary'>
|
||||
<Button type="primary">
|
||||
<span>{LABELS.MARGIN_TRADE_ACTION}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
import { Progress, Slider, Card, Statistic } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { Position } from './interfaces';
|
||||
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
||||
import tokens from '../../../config/tokens.json';
|
||||
import GainsChart from './GainsChart';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
import { Progress, Slider, Card, Statistic } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { Position } from "./interfaces";
|
||||
import { ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons";
|
||||
import tokens from "../../../config/tokens.json";
|
||||
import GainsChart from "./GainsChart";
|
||||
import { usePoolAndTradeInfoFrom } from "./utils";
|
||||
|
||||
export default function Breakdown({ item }: { item: Position }) {
|
||||
const { enrichedPools, leverage } = usePoolAndTradeInfoFrom(item);
|
||||
|
||||
const exchangeRate = enrichedPools.length === 0 ? 1 : enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const exchangeRate =
|
||||
enrichedPools.length === 0
|
||||
? 1
|
||||
: enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
|
||||
let myPart = item.collateral.value || 0;
|
||||
const brokeragePart = (item.collateral.value || 0) * leverage - myPart;
|
||||
const brokerageColor = 'brown';
|
||||
const myColor = 'blue';
|
||||
const gains = 'green';
|
||||
const losses = 'red';
|
||||
const token = tokens.find((t) => t.mintAddress === item.asset.type?.info?.liquidityMint?.toBase58());
|
||||
const collateralToken = tokens.find((t) => t.mintAddress === item.collateral.type?.info?.liquidityMint?.toBase58());
|
||||
const brokerageColor = "brown";
|
||||
const myColor = "blue";
|
||||
const gains = "green";
|
||||
const losses = "red";
|
||||
const token = tokens.find(
|
||||
(t) => t.mintAddress === item.asset.type?.info?.liquidityMint?.toBase58()
|
||||
);
|
||||
const collateralToken = tokens.find(
|
||||
(t) =>
|
||||
t.mintAddress === item.collateral.type?.info?.liquidityMint?.toBase58()
|
||||
);
|
||||
|
||||
const [myGain, setMyGain] = useState<number>(10);
|
||||
const profitPart = (myPart + brokeragePart) * (myGain / 100);
|
||||
|
@ -29,7 +37,10 @@ export default function Breakdown({ item }: { item: Position }) {
|
|||
progressBar = (
|
||||
<Progress
|
||||
percent={(myPart / total) * 100 + (brokeragePart / total) * 100}
|
||||
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
|
||||
success={{
|
||||
percent: (brokeragePart / total) * 100,
|
||||
strokeColor: brokerageColor,
|
||||
}}
|
||||
strokeColor={myColor}
|
||||
trailColor={gains}
|
||||
showInfo={false}
|
||||
|
@ -40,24 +51,36 @@ export default function Breakdown({ item }: { item: Position }) {
|
|||
myPart += profitPart; // profit is negative
|
||||
const total = myPart + brokeragePart;
|
||||
if (myPart < 0) {
|
||||
progressBar = <p>Your position has been liquidated at this price swing.</p>;
|
||||
progressBar = (
|
||||
<p>Your position has been liquidated at this price swing.</p>
|
||||
);
|
||||
} else
|
||||
progressBar = (
|
||||
<Progress
|
||||
showInfo={false}
|
||||
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
|
||||
success={{
|
||||
percent: (brokeragePart / total) * 100,
|
||||
strokeColor: brokerageColor,
|
||||
}}
|
||||
trailColor={myColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='new-position-item new-position-item-top-right'>
|
||||
<Card className='new-position-item new-position-item-top-right'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
<div className="new-position-item new-position-item-top-right">
|
||||
<Card className="new-position-item new-position-item-top-right">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Borrowed'
|
||||
title="Borrowed"
|
||||
value={brokeragePart * exchangeRate}
|
||||
precision={2}
|
||||
valueStyle={{ color: brokerageColor }}
|
||||
|
@ -66,7 +89,7 @@ export default function Breakdown({ item }: { item: Position }) {
|
|||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='My Collateral'
|
||||
title="My Collateral"
|
||||
value={myPart}
|
||||
precision={2}
|
||||
valueStyle={{ color: myColor }}
|
||||
|
@ -75,19 +98,21 @@ export default function Breakdown({ item }: { item: Position }) {
|
|||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Profit/Loss'
|
||||
title="Profit/Loss"
|
||||
value={profitPart * exchangeRate}
|
||||
precision={2}
|
||||
valueStyle={{ color: profitPart > 0 ? gains : losses }}
|
||||
suffix={token?.tokenSymbol}
|
||||
prefix={profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
||||
prefix={
|
||||
profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<br />
|
||||
{progressBar}
|
||||
</Card>
|
||||
<Card className='new-position-item new-position-item-bottom-right'>
|
||||
<Card className="new-position-item new-position-item-bottom-right">
|
||||
<GainsChart item={item} priceChange={myGain} />
|
||||
<Slider
|
||||
tooltipVisible={true}
|
||||
|
@ -95,11 +120,11 @@ export default function Breakdown({ item }: { item: Position }) {
|
|||
tipFormatter={(p) => <span>{p}%</span>}
|
||||
max={100}
|
||||
min={-100}
|
||||
tooltipPlacement={'top'}
|
||||
tooltipPlacement={"top"}
|
||||
onChange={(v: number) => {
|
||||
setMyGain(v);
|
||||
}}
|
||||
style={{ marginBottom: '20px' }}
|
||||
style={{ marginBottom: "20px" }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import Chart, { ChartPluginsOptions } from "chart.js";
|
||||
import { Position } from './interfaces';
|
||||
import { Position } from "./interfaces";
|
||||
|
||||
// Special thanks to
|
||||
// https://github.com/bZxNetwork/fulcrum_ui/blob/development/packages/fulcrum-website/assets/js/trading.js
|
||||
|
@ -63,34 +63,36 @@ const baseData = [
|
|||
function getChartData() {
|
||||
//the only way to create an immutable copy of array with objects inside.
|
||||
const baseDashed = getBaseDashed();
|
||||
const baseSolid = JSON.parse(JSON.stringify(baseData.slice(0, Math.floor(baseData.length) / 2 + 1)));
|
||||
const baseSolid = JSON.parse(
|
||||
JSON.stringify(baseData.slice(0, Math.floor(baseData.length) / 2 + 1))
|
||||
);
|
||||
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'rgb(39, 107, 251)',
|
||||
backgroundColor: "transparent",
|
||||
borderColor: "rgb(39, 107, 251)",
|
||||
borderWidth: 4,
|
||||
radius: 0,
|
||||
data: baseSolid,
|
||||
},
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
backgroundColor: "transparent",
|
||||
|
||||
borderWidth: 4,
|
||||
radius: 0,
|
||||
data: baseDashed,
|
||||
borderDash: [15, 3],
|
||||
label: 'LEVERAGE',
|
||||
label: "LEVERAGE",
|
||||
},
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'rgb(86, 169, 255)',
|
||||
backgroundColor: "transparent",
|
||||
borderColor: "rgb(86, 169, 255)",
|
||||
borderWidth: 2,
|
||||
radius: 0,
|
||||
data: baseDashed,
|
||||
borderDash: [8, 4],
|
||||
label: 'HOLD',
|
||||
label: "HOLD",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -99,8 +101,10 @@ function getChartData() {
|
|||
const labelPlugin: ChartPluginsOptions = {};
|
||||
|
||||
const getBaseDashed = () => {
|
||||
return JSON.parse(JSON.stringify(baseData.slice(Math.floor(baseData.length) / 2))) as { x: number, y: number }[];
|
||||
}
|
||||
return JSON.parse(
|
||||
JSON.stringify(baseData.slice(Math.floor(baseData.length) / 2))
|
||||
) as { x: number; y: number }[];
|
||||
};
|
||||
|
||||
function updateChartData({
|
||||
item,
|
||||
|
@ -116,21 +120,24 @@ function updateChartData({
|
|||
}
|
||||
|
||||
labelPlugin.afterDraw = (instance: Chart) => {
|
||||
drawLabels(instance, item.leverage, priceChange)
|
||||
drawLabels(instance, item.leverage, priceChange);
|
||||
};
|
||||
|
||||
const baseDashed = getBaseDashed();
|
||||
const leverage = item.leverage;
|
||||
var leverageData = baseDashed.map((item: { x: number; y: number }, index: number) => {
|
||||
var leverageData = baseDashed.map(
|
||||
(item: { x: number; y: number }, index: number) => {
|
||||
if (index === 0) {
|
||||
return { x: item.x, y: item.y };
|
||||
}
|
||||
const gain = (priceChange * leverage) / 100;
|
||||
return { x: item.x, y: item.y * (1 + gain) };
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
chart.data.datasets[1].data = leverageData;
|
||||
chart.data.datasets[1].borderColor = priceChange >= 0 ? 'rgb(51, 223, 204)' : 'rgb(255,79,79)';
|
||||
chart.data.datasets[1].borderColor =
|
||||
priceChange >= 0 ? "rgb(51, 223, 204)" : "rgb(255,79,79)";
|
||||
|
||||
baseDashed.forEach((item: { y: number; x: number }, index: number) => {
|
||||
if (index !== 0) item.y += (item.y * priceChange) / 100;
|
||||
|
@ -140,11 +147,16 @@ function updateChartData({
|
|||
|
||||
// chart.chartInstance.canvas.parentNode.style.width = '100%';
|
||||
// chart.chartInstance.canvas.parentNode.style.height = 'auto';
|
||||
chart?.update()
|
||||
chart?.update();
|
||||
}
|
||||
|
||||
function drawLabels(chart: Chart, leverage: number, priceChange: number) {
|
||||
if (!chart.config || !chart.config.data || !chart.config.data.datasets || !chart.canvas) {
|
||||
if (
|
||||
!chart.config ||
|
||||
!chart.config.data ||
|
||||
!chart.config.data.datasets ||
|
||||
!chart.canvas
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -154,11 +166,11 @@ function drawLabels(chart: Chart, leverage: number, priceChange: number) {
|
|||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.font = 'normal normal bold 15px /1.5 Muli';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.font = "normal normal bold 15px /1.5 Muli";
|
||||
ctx.textBaseline = "bottom";
|
||||
|
||||
const datasets = chart.config.data.datasets;
|
||||
const element = (chart?.canvas?.parentNode as HTMLElement);
|
||||
const element = chart?.canvas?.parentNode as HTMLElement;
|
||||
datasets.forEach((ds, index) => {
|
||||
const label = ds.label;
|
||||
ctx.fillStyle = ds.borderColor as string;
|
||||
|
@ -171,7 +183,7 @@ function drawLabels(chart: Chart, leverage: number, priceChange: number) {
|
|||
const y = meta.data[pointPostition]._model.y;
|
||||
let yOffset;
|
||||
|
||||
if (label === 'HOLD') {
|
||||
if (label === "HOLD") {
|
||||
yOffset = leverage * priceChange > 0 ? y * 1.2 : y * 0.8;
|
||||
} else {
|
||||
yOffset = leverage * priceChange > 0 ? y * 0.8 : y * 1.2;
|
||||
|
@ -187,7 +199,13 @@ function drawLabels(chart: Chart, leverage: number, priceChange: number) {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
export default function GainsChart({ item, priceChange }: { item: Position; priceChange: number }) {
|
||||
export default function GainsChart({
|
||||
item,
|
||||
priceChange,
|
||||
}: {
|
||||
item: Position;
|
||||
priceChange: number;
|
||||
}) {
|
||||
const chartRef = useRef<Chart>();
|
||||
const canvasRef = useRef<HTMLCanvasElement>();
|
||||
|
||||
|
@ -197,11 +215,9 @@ export default function GainsChart({ item, priceChange }: { item: Position; pric
|
|||
}
|
||||
|
||||
chartRef.current = new Chart(canvasRef.current, {
|
||||
type: 'line',
|
||||
type: "line",
|
||||
data: getChartData(),
|
||||
plugins: [
|
||||
labelPlugin
|
||||
],
|
||||
plugins: [labelPlugin],
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
|
@ -213,12 +229,12 @@ export default function GainsChart({ item, priceChange }: { item: Position; pric
|
|||
},
|
||||
},
|
||||
labels: {
|
||||
render: 'title',
|
||||
fontColor: ['green', 'white', 'red'],
|
||||
render: "title",
|
||||
fontColor: ["green", "white", "red"],
|
||||
precision: 2,
|
||||
},
|
||||
animation: {
|
||||
easing: 'easeOutExpo',
|
||||
easing: "easeOutExpo",
|
||||
duration: 500,
|
||||
},
|
||||
scales: {
|
||||
|
@ -228,8 +244,8 @@ export default function GainsChart({ item, priceChange }: { item: Position; pric
|
|||
gridLines: {
|
||||
display: false,
|
||||
},
|
||||
type: 'linear',
|
||||
position: 'bottom',
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
|
@ -244,7 +260,7 @@ export default function GainsChart({ item, priceChange }: { item: Position; pric
|
|||
legend: {
|
||||
display: false,
|
||||
},
|
||||
} as any
|
||||
} as any,
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -255,14 +271,21 @@ export default function GainsChart({ item, priceChange }: { item: Position; pric
|
|||
}, [priceChange, item]);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch', justifyContent: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<canvas ref={canvasRef as any} />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span>past</span>
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { Button, Card } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { ActionConfirmation } from '../../../components/ActionConfirmation';
|
||||
import { LABELS } from '../../../constants';
|
||||
import { cache, ParsedAccount } from '../../../contexts/accounts';
|
||||
import { LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
|
||||
import { Position } from './interfaces';
|
||||
import { useLeverage } from './leverage';
|
||||
import CollateralInput from '../../../components/CollateralInput';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
import { UserDeposit } from '../../../hooks';
|
||||
import { ArrowDownOutlined } from '@ant-design/icons';
|
||||
import { useWallet } from '../../../contexts/wallet';
|
||||
import { Button, Card } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { ActionConfirmation } from "../../../components/ActionConfirmation";
|
||||
import { LABELS } from "../../../constants";
|
||||
import { cache, ParsedAccount } from "../../../contexts/accounts";
|
||||
import {
|
||||
LendingReserve,
|
||||
LendingReserveParser,
|
||||
} from "../../../models/lending/reserve";
|
||||
import { Position } from "./interfaces";
|
||||
import { useLeverage } from "./leverage";
|
||||
import CollateralInput from "../../../components/CollateralInput";
|
||||
import { usePoolAndTradeInfoFrom } from "./utils";
|
||||
import { UserDeposit } from "../../../hooks";
|
||||
import { ArrowDownOutlined } from "@ant-design/icons";
|
||||
import { useWallet } from "../../../contexts/wallet";
|
||||
|
||||
interface NewPositionFormProps {
|
||||
lendingReserve: ParsedAccount<LendingReserve>;
|
||||
|
@ -18,8 +21,15 @@ interface NewPositionFormProps {
|
|||
setNewPosition: (pos: Position) => void;
|
||||
}
|
||||
|
||||
export const generateActionLabel = (connected: boolean, newPosition: Position) => {
|
||||
return !connected ? LABELS.CONNECT_LABEL : newPosition.error ? newPosition.error : LABELS.TRADING_ADD_POSITION;
|
||||
export const generateActionLabel = (
|
||||
connected: boolean,
|
||||
newPosition: Position
|
||||
) => {
|
||||
return !connected
|
||||
? LABELS.CONNECT_LABEL
|
||||
: newPosition.error
|
||||
? newPosition.error
|
||||
: LABELS.TRADING_ADD_POSITION;
|
||||
};
|
||||
|
||||
function onUserChangesLeverageOrCollateralValue({
|
||||
|
@ -36,9 +46,14 @@ function onUserChangesLeverageOrCollateralValue({
|
|||
setNewPosition(newPosition); // It has always changed, need to guarantee save
|
||||
// if user changes leverage, we need to adjust the amount they desire up.
|
||||
if (collateralDeposit && enrichedPools.length) {
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount = (newPosition.collateral.value || 0) * newPosition.leverage * exchangeRate;
|
||||
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: convertedAmount } });
|
||||
const exchangeRate =
|
||||
enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount =
|
||||
(newPosition.collateral.value || 0) * newPosition.leverage * exchangeRate;
|
||||
setNewPosition({
|
||||
...newPosition,
|
||||
asset: { ...newPosition.asset, value: convertedAmount },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,44 +70,68 @@ function onUserChangesAssetValue({
|
|||
}) {
|
||||
setNewPosition(newPosition); // It has always changed, need to guarantee save
|
||||
if (collateralDeposit && enrichedPools.length) {
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount = (newPosition.asset.value || 0) / (exchangeRate * newPosition.leverage);
|
||||
setNewPosition({ ...newPosition, collateral: { ...newPosition.collateral, value: convertedAmount } });
|
||||
const exchangeRate =
|
||||
enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount =
|
||||
(newPosition.asset.value || 0) / (exchangeRate * newPosition.leverage);
|
||||
setNewPosition({
|
||||
...newPosition,
|
||||
collateral: { ...newPosition.collateral, value: convertedAmount },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function NewPositionForm({ lendingReserve, newPosition, setNewPosition }: NewPositionFormProps) {
|
||||
export default function NewPositionForm({
|
||||
lendingReserve,
|
||||
newPosition,
|
||||
setNewPosition,
|
||||
}: NewPositionFormProps) {
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
};
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const { enrichedPools, collateralDeposit } = usePoolAndTradeInfoFrom(newPosition);
|
||||
const { enrichedPools, collateralDeposit } = usePoolAndTradeInfoFrom(
|
||||
newPosition
|
||||
);
|
||||
useLeverage({ newPosition, setNewPosition });
|
||||
const { wallet, connected } = useWallet();
|
||||
|
||||
return (
|
||||
<Card className='new-position-item new-position-item-top-left' bodyStyle={bodyStyle}>
|
||||
<Card
|
||||
className="new-position-item new-position-item-top-left"
|
||||
bodyStyle={bodyStyle}
|
||||
>
|
||||
{showConfirmation ? (
|
||||
<ActionConfirmation onClose={() => setShowConfirmation(false)} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center' }}>
|
||||
<CollateralInput
|
||||
title='Collateral'
|
||||
title="Collateral"
|
||||
reserve={lendingReserve.info}
|
||||
amount={newPosition.collateral.value}
|
||||
onInputChange={(val: number | null) => {
|
||||
const newPos = { ...newPosition, collateral: { ...newPosition.collateral, value: val } };
|
||||
const newPos = {
|
||||
...newPosition,
|
||||
collateral: { ...newPosition.collateral, value: val },
|
||||
};
|
||||
onUserChangesLeverageOrCollateralValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
|
@ -101,9 +140,18 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
|||
});
|
||||
}}
|
||||
onCollateralReserve={(key) => {
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === key) || '';
|
||||
const id: string =
|
||||
cache
|
||||
.byParser(LendingReserveParser)
|
||||
.find((acc) => acc === key) || "";
|
||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
const newPos = { ...newPosition, collateral: { value: newPosition.collateral.value, type: parser } };
|
||||
const newPos = {
|
||||
...newPosition,
|
||||
collateral: {
|
||||
value: newPosition.collateral.value,
|
||||
type: parser,
|
||||
},
|
||||
};
|
||||
onUserChangesLeverageOrCollateralValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
|
@ -126,14 +174,24 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
|||
</div>
|
||||
<ArrowDownOutlined />
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'stretch' }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "stretch",
|
||||
}}
|
||||
>
|
||||
{newPosition.asset.type && (
|
||||
<CollateralInput
|
||||
title='Choose trade'
|
||||
title="Choose trade"
|
||||
reserve={newPosition.asset.type.info}
|
||||
amount={newPosition.asset.value}
|
||||
onInputChange={(val: number | null) => {
|
||||
const newPos = { ...newPosition, asset: { ...newPosition.asset, value: val } };
|
||||
const newPos = {
|
||||
...newPosition,
|
||||
asset: { ...newPosition.asset, value: val },
|
||||
};
|
||||
onUserChangesAssetValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
|
@ -146,11 +204,11 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
|||
/>
|
||||
)}
|
||||
<Button
|
||||
className='trade-button'
|
||||
type='primary'
|
||||
size='large'
|
||||
className="trade-button"
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={connected ? null : wallet.connect}
|
||||
style={{ width: '100%' }}
|
||||
style={{ width: "100%" }}
|
||||
disabled={connected && !!newPosition.error}
|
||||
>
|
||||
<span>{generateActionLabel(connected, newPosition)}</span>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import Card from 'antd/lib/card';
|
||||
import React from 'react';
|
||||
import { PoolPrice } from '../../../components/PoolPrice';
|
||||
import { SupplyOverview } from '../../../components/SupplyOverview';
|
||||
import { Position } from './interfaces';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
import Card from "antd/lib/card";
|
||||
import React from "react";
|
||||
import { PoolPrice } from "../../../components/PoolPrice";
|
||||
import { SupplyOverview } from "../../../components/SupplyOverview";
|
||||
import { Position } from "./interfaces";
|
||||
import { usePoolAndTradeInfoFrom } from "./utils";
|
||||
|
||||
export default function PoolHealth({ newPosition }: { newPosition: Position }) {
|
||||
const { pool } = usePoolAndTradeInfoFrom(newPosition);
|
||||
return (
|
||||
<Card className='new-position-item new-position-item-bottom-left'>
|
||||
<Card className="new-position-item new-position-item-bottom-left">
|
||||
{!pool && <span>Choose a CCY to see exchange rate information.</span>}
|
||||
{pool && (
|
||||
<>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useLendingReserve } from '../../../hooks';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import './style.less';
|
||||
import React, { useState } from "react";
|
||||
import { useLendingReserve } from "../../../hooks";
|
||||
import { useParams } from "react-router-dom";
|
||||
import "./style.less";
|
||||
|
||||
import NewPositionForm from './NewPositionForm';
|
||||
import { Position } from './interfaces';
|
||||
import Breakdown from './Breakdown';
|
||||
import PoolHealth from './PoolHealth';
|
||||
import NewPositionForm from "./NewPositionForm";
|
||||
import { Position } from "./interfaces";
|
||||
import Breakdown from "./Breakdown";
|
||||
import PoolHealth from "./PoolHealth";
|
||||
|
||||
export const NewPosition = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
@ -23,14 +23,21 @@ export const NewPosition = () => {
|
|||
}
|
||||
|
||||
if (!newPosition.asset.type) {
|
||||
setNewPosition({ ...newPosition, asset: { value: newPosition.asset.value, type: lendingReserve } });
|
||||
setNewPosition({
|
||||
...newPosition,
|
||||
asset: { value: newPosition.asset.value, type: lendingReserve },
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='new-position'>
|
||||
<div className='new-position-container'>
|
||||
<div className='new-position-item-left'>
|
||||
<NewPositionForm lendingReserve={lendingReserve} newPosition={newPosition} setNewPosition={setNewPosition} />
|
||||
<div className="new-position">
|
||||
<div className="new-position-container">
|
||||
<div className="new-position-item-left">
|
||||
<NewPositionForm
|
||||
lendingReserve={lendingReserve}
|
||||
newPosition={newPosition}
|
||||
setNewPosition={setNewPosition}
|
||||
/>
|
||||
<PoolHealth newPosition={newPosition} />
|
||||
</div>
|
||||
<Breakdown item={newPosition} />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ParsedAccount } from '../../../contexts/accounts';
|
||||
import { LendingReserve } from '../../../models/lending/reserve';
|
||||
import { ParsedAccount } from "../../../contexts/accounts";
|
||||
import { LendingReserve } from "../../../models/lending/reserve";
|
||||
|
||||
export interface Token {
|
||||
mintAddress: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
import { LABELS } from '../../../constants';
|
||||
import { Position } from './interfaces';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
import { useEffect } from "react";
|
||||
import { LABELS } from "../../../constants";
|
||||
import { Position } from "./interfaces";
|
||||
import { usePoolAndTradeInfoFrom } from "./utils";
|
||||
|
||||
export function useLeverage({
|
||||
newPosition,
|
||||
|
@ -33,18 +33,28 @@ export function useLeverage({
|
|||
return;
|
||||
}
|
||||
|
||||
if (!desiredType || !newPosition.asset.value || !enrichedPools || enrichedPools.length === 0) {
|
||||
if (
|
||||
!desiredType ||
|
||||
!newPosition.asset.value ||
|
||||
!enrichedPools ||
|
||||
enrichedPools.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is more of A than B
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const exchangeRate =
|
||||
enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const leverageDesired = newPosition.leverage;
|
||||
const amountAvailableInOysterForMargin = collateralDeposit.info.amount * exchangeRate;
|
||||
const amountAvailableInOysterForMargin =
|
||||
collateralDeposit.info.amount * exchangeRate;
|
||||
const amountToDepositOnMargin = desiredValue / leverageDesired;
|
||||
|
||||
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
|
||||
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
|
||||
setNewPosition({
|
||||
...newPosition,
|
||||
error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,6 +81,14 @@ export function useLeverage({
|
|||
setNewPosition({ ...newPosition, error: LABELS.LEVERAGE_LIMIT_MESSAGE });
|
||||
return;
|
||||
}
|
||||
setNewPosition({ ...newPosition, error: '' });
|
||||
}, [collType, desiredType, desiredValue, leverage, enrichedPools, collValue, collateralDeposit]);
|
||||
setNewPosition({ ...newPosition, error: "" });
|
||||
}, [
|
||||
collType,
|
||||
desiredType,
|
||||
desiredValue,
|
||||
leverage,
|
||||
enrichedPools,
|
||||
collValue,
|
||||
collateralDeposit,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ParsedAccount } from '../../../contexts/accounts';
|
||||
import { useEnrichedPools } from '../../../contexts/market';
|
||||
import { UserDeposit, useUserDeposits } from '../../../hooks';
|
||||
import { LendingReserve, PoolInfo } from '../../../models';
|
||||
import { usePoolForBasket } from '../../../utils/pools';
|
||||
import { Position } from './interfaces';
|
||||
import { ParsedAccount } from "../../../contexts/accounts";
|
||||
import { useEnrichedPools } from "../../../contexts/market";
|
||||
import { UserDeposit, useUserDeposits } from "../../../hooks";
|
||||
import { LendingReserve, PoolInfo } from "../../../models";
|
||||
import { usePoolForBasket } from "../../../utils/pools";
|
||||
import { Position } from "./interfaces";
|
||||
|
||||
export function usePoolAndTradeInfoFrom(
|
||||
newPosition: Position
|
||||
|
@ -29,7 +29,9 @@ export function usePoolAndTradeInfoFrom(
|
|||
|
||||
const userDeposits = useUserDeposits();
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() === collType?.info?.liquidityMint?.toBase58()
|
||||
(u) =>
|
||||
u.reserve.info.liquidityMint.toBase58() ===
|
||||
collType?.info?.liquidityMint?.toBase58()
|
||||
);
|
||||
|
||||
const enrichedPools = useEnrichedPools(pool ? [pool] : []);
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import React from "react";
|
||||
import { useBorrowingPower, useEnrichedLendingObligation, useLendingReserve, useUserObligations } from "../../hooks";
|
||||
import {
|
||||
useBorrowingPower,
|
||||
useEnrichedLendingObligation,
|
||||
useLendingReserve,
|
||||
useUserObligations,
|
||||
} from "../../hooks";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { RepayInput } from "../../components/RepayInput";
|
||||
|
@ -36,7 +41,6 @@ export const RepayReserveView = () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="repay-reserve">
|
||||
<Row gutter={GUTTER}>
|
||||
|
@ -76,8 +80,11 @@ export const RepayReserveView = () => {
|
|||
<BarChartStatistic
|
||||
title="Your Loans"
|
||||
items={userObligations}
|
||||
getPct={(item) => item.obligation.info.borrowedInQuote / loansValue}
|
||||
name={(item) => item.obligation.info.repayName} />
|
||||
getPct={(item) =>
|
||||
item.obligation.info.borrowedInQuote / loansValue
|
||||
}
|
||||
name={(item) => item.obligation.info.repayName}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -98,5 +105,6 @@ export const RepayReserveView = () => {
|
|||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,9 +21,7 @@ export const ReserveView = () => {
|
|||
<div className="flexColumn">
|
||||
<Row gutter={GUTTER}>
|
||||
<Col sm={24} md={12} lg={14} xl={15} xxl={18}>
|
||||
<ReserveStatus
|
||||
reserve={reserve}
|
||||
address={lendingReserve.pubkey} />
|
||||
<ReserveStatus reserve={reserve} address={lendingReserve.pubkey} />
|
||||
</Col>
|
||||
<Col sm={24} md={12} lg={10} xl={9} xxl={6}>
|
||||
<UserLendingCard
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const whyDidYouRender = require("@welldone-software/why-did-you-render");
|
||||
whyDidYouRender(React, {
|
||||
trackAllPureComponents: true,
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue