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