mirror of https://github.com/certusone/oyster.git
feat: THink we're getting very close to being done with this form.
This commit is contained in:
parent
a0530957d5
commit
b1dfe1676c
|
@ -25,6 +25,7 @@
|
|||
"eventemitter3": "^4.0.7",
|
||||
"identicon.js": "^2.3.3",
|
||||
"jazzicon": "^1.5.0",
|
||||
"lodash": "^4.17.20",
|
||||
"react": "^16.13.1",
|
||||
"react-chartjs-2": "^2.11.1",
|
||||
"react-dom": "^16.13.1",
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { cache, ParsedAccount } from '../../contexts/accounts';
|
||||
import { useConnectionConfig } from '../../contexts/connection';
|
||||
import { useLendingReserves, useUserDeposits } from '../../hooks';
|
||||
import { LendingReserve, LendingMarket, LendingReserveParser } from '../../models';
|
||||
import { getTokenName } from '../../utils/utils';
|
||||
import { Card, Select } from 'antd';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
import { NumericInput } from '../Input/numeric';
|
||||
import './style.less';
|
||||
import { TokenDisplay } from '../TokenDisplay';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
// User can choose a collateral they want to use, and then this will display the balance they have in Oyster's lending
|
||||
// reserve for that collateral type.
|
||||
export default function CollateralInput(props: {
|
||||
title: string;
|
||||
amount?: number | null;
|
||||
reserve: LendingReserve;
|
||||
disabled?: boolean;
|
||||
onCollateralReserve?: (id: string) => void;
|
||||
onLeverage?: (leverage: number) => void;
|
||||
onInputChange: (value: number | null) => void;
|
||||
hideBalance?: boolean;
|
||||
showLeverageSelector?: boolean;
|
||||
leverage?: number;
|
||||
}) {
|
||||
const { reserveAccounts } = useLendingReserves();
|
||||
const { tokenMap } = useConnectionConfig();
|
||||
const [collateralReserve, setCollateralReserve] = useState<string>();
|
||||
const [balance, setBalance] = useState<number>(0);
|
||||
const [lastAmount, setLastAmount] = useState<string>('');
|
||||
const userDeposits = useUserDeposits();
|
||||
|
||||
useEffect(() => {
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === collateralReserve) || '';
|
||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
if (parser) {
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() === parser.info.liquidityMint.toBase58()
|
||||
);
|
||||
if (collateralDeposit) setBalance(collateralDeposit.info.amount);
|
||||
else setBalance(0);
|
||||
}
|
||||
}, [collateralReserve, userDeposits]);
|
||||
|
||||
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||
if (!market) return null;
|
||||
|
||||
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
|
||||
|
||||
const renderReserveAccounts = reserveAccounts
|
||||
.filter((reserve) => reserve.info !== props.reserve)
|
||||
.filter((reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint))
|
||||
.map((reserve) => {
|
||||
const mint = reserve.info.liquidityMint.toBase58();
|
||||
const address = reserve.pubkey.toBase58();
|
||||
const name = getTokenName(tokenMap, mint);
|
||||
return (
|
||||
<Option key={address} value={address} name={name} title={address}>
|
||||
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<TokenIcon mintAddress={mint} />
|
||||
{name}
|
||||
</div>
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className='ccy-input' style={{ borderRadius: 20 }} bodyStyle={{ padding: 0 }}>
|
||||
<div className='ccy-input-header'>
|
||||
<div className='ccy-input-header-left'>{props.title}</div>
|
||||
|
||||
{!props.hideBalance && (
|
||||
<div className='ccy-input-header-right' onClick={(e) => props.onInputChange && props.onInputChange(balance)}>
|
||||
Balance: {balance.toFixed(6)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='ccy-input-header' style={{ padding: '0px 10px 5px 7px' }}>
|
||||
<NumericInput
|
||||
value={parseFloat(lastAmount || '0.00') == props.amount ? lastAmount : props.amount?.toFixed(6)?.toString()}
|
||||
onChange={(val: string) => {
|
||||
if (props.onInputChange && parseFloat(val) != props.amount) {
|
||||
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||
else props.onInputChange(parseFloat(val));
|
||||
}
|
||||
setLastAmount(val);
|
||||
}}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
boxShadow: 'none',
|
||||
borderColor: 'transparent',
|
||||
outline: 'transparent',
|
||||
}}
|
||||
placeholder='0.00'
|
||||
/>
|
||||
<div className='ccy-input-header-right' style={{ display: 'flex' }}>
|
||||
{props.showLeverageSelector && (
|
||||
<Select
|
||||
size='large'
|
||||
showSearch
|
||||
style={{ minWidth: 150 }}
|
||||
placeholder='CCY'
|
||||
value={props.leverage}
|
||||
onChange={(item: number) => {
|
||||
if (props.onLeverage) props.onLeverage(item);
|
||||
}}
|
||||
notFoundContent={null}
|
||||
onSearch={(item: string) => {
|
||||
if (props.onLeverage && item.match(/^\d+$/)) {
|
||||
props.onLeverage(parseFloat(item));
|
||||
}
|
||||
}}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
>
|
||||
{[1, 2, 3, 4, 5].map((val) => (
|
||||
<Option key={val} value={val} name={val + 'x'} title={val + 'x'}>
|
||||
<div key={val} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{val + 'x'}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
{!props.disabled ? (
|
||||
<Select
|
||||
size='large'
|
||||
showSearch
|
||||
style={{ minWidth: 150 }}
|
||||
placeholder='CCY'
|
||||
value={collateralReserve}
|
||||
onChange={(item) => {
|
||||
if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||
setCollateralReserve(item);
|
||||
}}
|
||||
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||
>
|
||||
{renderReserveAccounts}
|
||||
</Select>
|
||||
) : (
|
||||
<TokenDisplay
|
||||
key={props.reserve.liquidityMint.toBase58()}
|
||||
name={getTokenName(tokenMap, props.reserve.liquidityMint.toBase58())}
|
||||
mintAddress={props.reserve.liquidityMint.toBase58()}
|
||||
showBalance={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
.ccy-input {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-select-selector,
|
||||
.ant-select-selector:focus,
|
||||
.ant-select-selector:active {
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.ant-select-selection-item {
|
||||
display: flex;
|
||||
|
||||
.token-balance {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.token-balance {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.ccy-input-header {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-column-gap: 10px;
|
||||
|
||||
-webkit-box-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
padding: 10px 20px 0px 20px;
|
||||
}
|
||||
|
||||
.ccy-input-header-left {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0px;
|
||||
min-width: 0px;
|
||||
display: flex;
|
||||
padding: 0px;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.ccy-input-header-right {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ant-select-dropdown {
|
||||
width: 150px !important;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { Card, Row, Col } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMint } from '../../contexts/accounts';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
import { useUserAccounts } from '../../hooks';
|
||||
import { PoolInfo } from '../../models';
|
||||
import { formatPriceNumber } from '../../utils/utils';
|
||||
|
||||
export const PoolPrice = (props: { pool: PoolInfo }) => {
|
||||
const pool = props.pool;
|
||||
const pools = useMemo(() => [props.pool].filter((p) => p) as PoolInfo[], [props.pool]);
|
||||
const enriched = useEnrichedPools(pools)[0];
|
||||
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const lpMint = useMint(pool.pubkeys.mint);
|
||||
|
||||
const ratio =
|
||||
userAccounts
|
||||
.filter((f) => pool.pubkeys.mint.equals(f.info.mint))
|
||||
.reduce((acc, item) => item.info.amount.toNumber() + acc, 0) / (lpMint?.supply.toNumber() || 0);
|
||||
|
||||
if (!enriched) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
className='ccy-input'
|
||||
style={{ borderRadius: 20, width: '100%' }}
|
||||
bodyStyle={{ padding: '7px' }}
|
||||
size='small'
|
||||
title='Prices and pool share'
|
||||
>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityA) / parseFloat(enriched.liquidityB))}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{formatPriceNumber.format(parseFloat(enriched.liquidityB) / parseFloat(enriched.liquidityA))}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{ratio * 100 < 0.001 && ratio > 0 ? '<' : ''}
|
||||
{formatPriceNumber.format(ratio * 100)}%
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={8}>
|
||||
{enriched.names[0]} per {enriched.names[1]}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{enriched.names[1]} per {enriched.names[0]}
|
||||
</Col>
|
||||
<Col span={8}>Share of pool</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { PoolInfo } from '../../models';
|
||||
import echarts from 'echarts';
|
||||
import { formatNumber, formatUSD } from '../../utils/utils';
|
||||
import { useEnrichedPools } from '../../contexts/market';
|
||||
|
||||
export const SupplyOverview = (props: { pool?: PoolInfo }) => {
|
||||
const { pool } = props;
|
||||
const pools = useMemo(() => (pool ? [pool] : []), [pool]);
|
||||
const enriched = useEnrichedPools(pools);
|
||||
const chartDiv = useRef<HTMLDivElement>(null);
|
||||
|
||||
// dispose chart
|
||||
useEffect(() => {
|
||||
const div = chartDiv.current;
|
||||
return () => {
|
||||
let instance = div && echarts.getInstanceByDom(div);
|
||||
instance && instance.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartDiv.current || enriched.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let instance = echarts.getInstanceByDom(chartDiv.current);
|
||||
if (!instance) {
|
||||
instance = echarts.init(chartDiv.current as any);
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: enriched[0].names[0],
|
||||
value: enriched[0].liquidityAinUsd,
|
||||
tokens: enriched[0].liquidityA,
|
||||
},
|
||||
{
|
||||
name: enriched[0].names[1],
|
||||
value: enriched[0].liquidityBinUsd,
|
||||
tokens: enriched[0].liquidityB,
|
||||
},
|
||||
];
|
||||
|
||||
instance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function (params: any) {
|
||||
var val = formatUSD.format(params.value);
|
||||
var tokenAmount = formatNumber.format(params.data.tokens);
|
||||
return `${params.name}: \n${val}\n(${tokenAmount})`;
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Liquidity',
|
||||
type: 'pie',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
animation: false,
|
||||
label: {
|
||||
fontSize: 14,
|
||||
show: true,
|
||||
formatter: function (params: any) {
|
||||
var val = formatUSD.format(params.value);
|
||||
var tokenAmount = formatNumber.format(params.data.tokens);
|
||||
return `{c|${params.name}}\n{r|${tokenAmount}}\n{r|${val}}`;
|
||||
},
|
||||
rich: {
|
||||
c: {
|
||||
color: 'black',
|
||||
lineHeight: 22,
|
||||
align: 'center',
|
||||
},
|
||||
r: {
|
||||
color: 'black',
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: '#000',
|
||||
},
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, [enriched]);
|
||||
|
||||
if (enriched.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div ref={chartDiv} style={{ height: 150, width: '100%' }} />;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useMint, useAccountByMint } from '../../contexts/accounts';
|
||||
import { TokenIcon } from '../TokenIcon';
|
||||
|
||||
export const TokenDisplay = (props: {
|
||||
name: string;
|
||||
mintAddress: string;
|
||||
icon?: JSX.Element;
|
||||
showBalance?: boolean;
|
||||
}) => {
|
||||
const { showBalance, mintAddress, name, icon } = props;
|
||||
const tokenMint = useMint(mintAddress);
|
||||
const tokenAccount = useAccountByMint(mintAddress);
|
||||
|
||||
let balance: number = 0;
|
||||
let hasBalance: boolean = false;
|
||||
if (showBalance) {
|
||||
if (tokenAccount && tokenMint) {
|
||||
balance = tokenAccount.info.amount.toNumber() / Math.pow(10, tokenMint.decimals);
|
||||
hasBalance = balance > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
title={mintAddress}
|
||||
key={mintAddress}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{icon || <TokenIcon mintAddress={mintAddress} />}
|
||||
{name}
|
||||
</div>
|
||||
{showBalance ? (
|
||||
<span title={balance.toString()} key={mintAddress} className='token-balance'>
|
||||
{hasBalance ? (balance < 0.001 ? '<0.001' : balance.toFixed(3)) : '-'}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -65,7 +65,8 @@ export const LABELS = {
|
|||
MARGIN_TRADE_QUESTION: 'Please choose how much of this asset you wish to purchase.',
|
||||
TABLE_TITLE_BUYING_POWER: 'Total Buying Power',
|
||||
NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.',
|
||||
LEVERAGE_LIMIT_MESSAGE:
|
||||
'With liquidity pools in their current state, you are not allowed to use leverage at this multiple. You will need more margin to make this trade.',
|
||||
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',
|
||||
};
|
||||
|
|
|
@ -272,6 +272,7 @@ export const useEnrichedPools = (pools: PoolInfo[]) => {
|
|||
dispose && dispose();
|
||||
subscriptions.forEach((dispose) => dispose && dispose());
|
||||
};
|
||||
// Do not add pools here, causes a really bad infinite rendering loop. Use poolKeys instead.
|
||||
}, [tokenMap, dailyVolume, poolKeys, subscribeToMarket, marketEmitter, marketsByMint]);
|
||||
|
||||
return enriched;
|
||||
|
|
|
@ -178,12 +178,10 @@ export const collateralExchangeRate = (reserve?: LendingReserve) => {
|
|||
|
||||
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
|
||||
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
|
||||
console.log('Exchange rate:', collateralExchangeRate(reserve));
|
||||
return Math.floor(amount / collateralExchangeRate(reserve));
|
||||
};
|
||||
|
||||
export const liquidityToCollateral = (liquidityAmount: BN | number, reserve?: LendingReserve) => {
|
||||
const amount = typeof liquidityAmount === 'number' ? liquidityAmount : liquidityAmount.toNumber();
|
||||
console.log('Exchange rate:', collateralExchangeRate(reserve));
|
||||
return Math.floor(amount * collateralExchangeRate(reserve));
|
||||
};
|
||||
|
|
|
@ -15,6 +15,12 @@ export interface KnownToken {
|
|||
|
||||
export type KnownTokenMap = Map<string, KnownToken>;
|
||||
|
||||
export const formatPriceNumber = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 8,
|
||||
});
|
||||
|
||||
export function useLocalStorageState(key: string, defaultState?: string) {
|
||||
const [state, setState] = useState(() => {
|
||||
// NOTE: Not sure if this is ok
|
||||
|
|
|
@ -4,17 +4,23 @@ import { Position } from './interfaces';
|
|||
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
||||
import tokens from '../../../config/tokens.json';
|
||||
import GainsChart from './GainsChart';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
|
||||
export function Breakdown({ item }: { item: Position }) {
|
||||
let myPart = parseFloat(item.asset?.value || '0') / item.leverage;
|
||||
const brokeragePart = parseFloat(item.asset?.value || '0') - myPart;
|
||||
export default function Breakdown({ item }: { item: Position }) {
|
||||
const { enrichedPools, leverage } = usePoolAndTradeInfoFrom(item);
|
||||
|
||||
const exchangeRate = enrichedPools.length == 0 ? 1 : enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
|
||||
let myPart = item.collateral.value || 0;
|
||||
const brokeragePart = (item.collateral.value || 0) * leverage - myPart;
|
||||
const brokerageColor = 'brown';
|
||||
const myColor = 'blue';
|
||||
const gains = 'green';
|
||||
const losses = 'red';
|
||||
const token = tokens.find((t) => t.mintAddress === item.asset.type?.info?.liquidityMint?.toBase58());
|
||||
const collateralToken = tokens.find((t) => t.mintAddress === item.collateral.type?.info?.liquidityMint?.toBase58());
|
||||
|
||||
const [myGain, setMyGain] = useState<number>(0);
|
||||
const [myGain, setMyGain] = useState<number>(10);
|
||||
const profitPart = (myPart + brokeragePart) * (myGain / 100);
|
||||
let progressBar = null;
|
||||
if (profitPart > 0) {
|
||||
|
@ -46,52 +52,64 @@ export function Breakdown({ item }: { item: Position }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Slider
|
||||
tooltipVisible={true}
|
||||
defaultValue={0}
|
||||
dots={true}
|
||||
max={100}
|
||||
min={-100}
|
||||
step={5}
|
||||
tooltipPlacement={'top'}
|
||||
onChange={(v: number) => {
|
||||
setMyGain(v);
|
||||
}}
|
||||
style={{ marginBottom: '20px' }}
|
||||
/>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Leverage'
|
||||
value={brokeragePart}
|
||||
precision={2}
|
||||
valueStyle={{ color: brokerageColor }}
|
||||
suffix={token?.tokenSymbol}
|
||||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='My Collateral Value'
|
||||
value={myPart}
|
||||
precision={2}
|
||||
valueStyle={{ color: myColor }}
|
||||
suffix={token?.tokenSymbol}
|
||||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Profit/Loss'
|
||||
value={profitPart}
|
||||
precision={2}
|
||||
valueStyle={{ color: profitPart > 0 ? gains : losses }}
|
||||
suffix={token?.tokenSymbol}
|
||||
prefix={profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<GainsChart item={item} priceChange={myGain} />
|
||||
{progressBar}
|
||||
<div className='new-position-item new-position-item-top-right'>
|
||||
<Card className='new-position-item new-position-item-top-right'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Leverage'
|
||||
value={brokeragePart * exchangeRate}
|
||||
precision={2}
|
||||
valueStyle={{ color: brokerageColor }}
|
||||
suffix={token?.tokenSymbol}
|
||||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='My Collateral Value'
|
||||
value={myPart}
|
||||
precision={2}
|
||||
valueStyle={{ color: myColor }}
|
||||
suffix={collateralToken?.tokenSymbol}
|
||||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='Profit/Loss'
|
||||
value={profitPart * exchangeRate}
|
||||
precision={2}
|
||||
valueStyle={{ color: profitPart > 0 ? gains : losses }}
|
||||
suffix={token?.tokenSymbol}
|
||||
prefix={profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<br />
|
||||
{progressBar}
|
||||
</Card>
|
||||
<Card className='new-position-item new-position-item-bottom-right'>
|
||||
<GainsChart item={item} priceChange={myGain} />
|
||||
<Slider
|
||||
tooltipVisible={true}
|
||||
defaultValue={10}
|
||||
tipFormatter={(p) => <span>{p}%</span>}
|
||||
max={100}
|
||||
min={-100}
|
||||
tooltipPlacement={'top'}
|
||||
onChange={(v: number) => {
|
||||
setMyGain(v);
|
||||
}}
|
||||
style={{ marginBottom: '20px' }}
|
||||
/>
|
||||
<span style={{ float: 'right', fontSize: '9px' }}>
|
||||
<a
|
||||
href='https://github.com/bZxNetwork/fulcrum_ui/blob/development/packages/fulcrum-website/assets/js/trading.js'
|
||||
target='blank'
|
||||
>
|
||||
credit
|
||||
</a>
|
||||
</span>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { Position } from './interfaces';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
// Special thanks to
|
||||
// https://github.com/bZxNetwork/fulcrum_ui/blob/development/packages/fulcrum-website/assets/js/trading.js
|
||||
|
@ -126,7 +127,6 @@ function updateChartData({
|
|||
}
|
||||
|
||||
function drawLabels(t: any, ctx: any, leverage: number, priceChange: number) {
|
||||
console.log('drawing');
|
||||
ctx.save();
|
||||
ctx.font = 'normal normal bold 15px /1.5 Muli';
|
||||
ctx.textBaseline = 'bottom';
|
||||
|
@ -159,13 +159,14 @@ function drawLabels(t: any, ctx: any, leverage: number, priceChange: number) {
|
|||
});
|
||||
ctx.restore();
|
||||
}
|
||||
const debouncedUpdateChartData = debounce(updateChartData, 200);
|
||||
|
||||
export default function GainsChart({ item, priceChange }: { item: Position; priceChange: number }) {
|
||||
const chartRef = useRef<any>();
|
||||
const [booted, setBooted] = useState<boolean>(false);
|
||||
const [canvas, setCanvas] = useState<any>();
|
||||
useEffect(() => {
|
||||
if (chartRef.current.chartInstance) updateChartData({ item, priceChange, chartRef });
|
||||
if (chartRef.current.chartInstance) debouncedUpdateChartData({ item, priceChange, chartRef });
|
||||
}, [priceChange, item.leverage]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { Button, Card, Radio } from 'antd';
|
||||
import { Button, Card } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { ActionConfirmation } from '../../../components/ActionConfirmation';
|
||||
import { NumericInput } from '../../../components/Input/numeric';
|
||||
import { TokenIcon } from '../../../components/TokenIcon';
|
||||
import { LABELS } from '../../../constants';
|
||||
import { cache, ParsedAccount } from '../../../contexts/accounts';
|
||||
import { LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
|
||||
import { Position } from './interfaces';
|
||||
import tokens from '../../../config/tokens.json';
|
||||
import { CollateralSelector } from '../../../components/CollateralSelector';
|
||||
import { Breakdown } from './Breakdown';
|
||||
import { useLeverage } from './leverage';
|
||||
import CollateralInput from '../../../components/CollateralInput';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
import { UserDeposit } from '../../../hooks';
|
||||
import { ArrowDownOutlined } from '@ant-design/icons';
|
||||
import { useWallet } from '../../../contexts/wallet';
|
||||
|
||||
interface NewPositionFormProps {
|
||||
lendingReserve: ParsedAccount<LendingReserve>;
|
||||
|
@ -18,6 +18,49 @@ interface NewPositionFormProps {
|
|||
setNewPosition: (pos: Position) => void;
|
||||
}
|
||||
|
||||
export const generateActionLabel = (connected: boolean, newPosition: Position) => {
|
||||
return !connected ? LABELS.CONNECT_LABEL : newPosition.error ? newPosition.error : LABELS.TRADING_ADD_POSITION;
|
||||
};
|
||||
|
||||
function onUserChangesLeverageOrCollateralValue({
|
||||
newPosition,
|
||||
setNewPosition,
|
||||
collateralDeposit,
|
||||
enrichedPools,
|
||||
}: {
|
||||
newPosition: Position;
|
||||
setNewPosition: (pos: Position) => void;
|
||||
enrichedPools: any[];
|
||||
collateralDeposit: UserDeposit | undefined;
|
||||
}) {
|
||||
setNewPosition(newPosition); // It has always changed, need to guarantee save
|
||||
// if user changes leverage, we need to adjust the amount they desire up.
|
||||
if (collateralDeposit && enrichedPools.length) {
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount = (newPosition.collateral.value || 0) * newPosition.leverage * exchangeRate;
|
||||
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: convertedAmount } });
|
||||
}
|
||||
}
|
||||
|
||||
function onUserChangesAssetValue({
|
||||
newPosition,
|
||||
setNewPosition,
|
||||
collateralDeposit,
|
||||
enrichedPools,
|
||||
}: {
|
||||
newPosition: Position;
|
||||
setNewPosition: (pos: Position) => void;
|
||||
enrichedPools: any[];
|
||||
collateralDeposit: UserDeposit | undefined;
|
||||
}) {
|
||||
setNewPosition(newPosition); // It has always changed, need to guarantee save
|
||||
if (collateralDeposit && enrichedPools.length) {
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const convertedAmount = (newPosition.asset.value || 0) / (exchangeRate * newPosition.leverage);
|
||||
setNewPosition({ ...newPosition, collateral: { ...newPosition.collateral, value: convertedAmount } });
|
||||
}
|
||||
}
|
||||
|
||||
export default function NewPositionForm({ lendingReserve, newPosition, setNewPosition }: NewPositionFormProps) {
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
|
@ -27,11 +70,12 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
|||
height: '100%',
|
||||
};
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const { enrichedPools, collateralDeposit } = usePoolAndTradeInfoFrom(newPosition);
|
||||
useLeverage({ newPosition, setNewPosition });
|
||||
const { wallet, connected } = useWallet();
|
||||
|
||||
return (
|
||||
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
|
||||
<Card className='new-position-item new-position-item-top-left' bodyStyle={bodyStyle}>
|
||||
{showConfirmation ? (
|
||||
<ActionConfirmation onClose={() => setShowConfirmation(false)} />
|
||||
) : (
|
||||
|
@ -42,78 +86,76 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
|||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<p>{newPosition.error}</p>
|
||||
|
||||
<p>{LABELS.MARGIN_TRADE_CHOOSE_COLLATERAL_AND_LEVERAGE}</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center' }}>
|
||||
<CollateralSelector
|
||||
<CollateralInput
|
||||
title='Collateral'
|
||||
reserve={lendingReserve.info}
|
||||
amount={newPosition.collateral.value}
|
||||
onInputChange={(val: number | null) => {
|
||||
const newPos = { ...newPosition, collateral: { ...newPosition.collateral, value: val } };
|
||||
onUserChangesLeverageOrCollateralValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
});
|
||||
}}
|
||||
onCollateralReserve={(key) => {
|
||||
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === key) || '';
|
||||
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
|
||||
setNewPosition({ ...newPosition, collateral: parser });
|
||||
const newPos = { ...newPosition, collateral: { value: newPosition.collateral.value, type: parser } };
|
||||
onUserChangesLeverageOrCollateralValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
});
|
||||
}}
|
||||
showLeverageSelector={true}
|
||||
onLeverage={(leverage: number) => {
|
||||
const newPos = { ...newPosition, leverage };
|
||||
onUserChangesLeverageOrCollateralValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
});
|
||||
}}
|
||||
leverage={newPosition.leverage}
|
||||
/>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Radio.Group
|
||||
defaultValue={newPosition.leverage}
|
||||
size='large'
|
||||
onChange={(e) => {
|
||||
setNewPosition({ ...newPosition, leverage: e.target.value });
|
||||
}}
|
||||
>
|
||||
<Radio.Button value={1}>1x</Radio.Button>
|
||||
<Radio.Button value={2}>2x</Radio.Button>
|
||||
<Radio.Button value={3}>3x</Radio.Button>
|
||||
<Radio.Button value={4}>4x</Radio.Button>
|
||||
<Radio.Button value={5}>5x</Radio.Button>
|
||||
</Radio.Group>
|
||||
<NumericInput
|
||||
value={newPosition.leverage}
|
||||
style={{
|
||||
maxWidth: '75px',
|
||||
}}
|
||||
onChange={(leverage: number) => {
|
||||
setNewPosition({ ...newPosition, leverage });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<p>{LABELS.MARGIN_TRADE_QUESTION}</p>
|
||||
<ArrowDownOutlined />
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'stretch' }}>
|
||||
<div className='token-input'>
|
||||
<TokenIcon mintAddress={newPosition.asset.type?.info?.liquidityMint?.toBase58()} />
|
||||
<NumericInput
|
||||
value={newPosition.asset.value}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
boxShadow: 'none',
|
||||
borderColor: 'transparent',
|
||||
outline: 'transparent',
|
||||
}}
|
||||
onChange={(v: string) => {
|
||||
setNewPosition({
|
||||
...newPosition,
|
||||
asset: { ...newPosition.asset, value: v },
|
||||
{newPosition.asset.type && (
|
||||
<CollateralInput
|
||||
title='Choose trade'
|
||||
reserve={newPosition.asset.type.info}
|
||||
amount={newPosition.asset.value}
|
||||
onInputChange={(val: number | null) => {
|
||||
const newPos = { ...newPosition, asset: { ...newPosition.asset, value: val } };
|
||||
onUserChangesAssetValue({
|
||||
newPosition: newPos,
|
||||
setNewPosition,
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
});
|
||||
}}
|
||||
placeholder='0.00'
|
||||
disabled
|
||||
hideBalance={true}
|
||||
/>
|
||||
<div>
|
||||
{
|
||||
tokens.find((t) => t.mintAddress === newPosition.asset.type?.info?.liquidityMint?.toBase58())
|
||||
?.tokenSymbol
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type='primary'>
|
||||
<span>{LABELS.TRADING_ADD_POSITION}</span>
|
||||
)}
|
||||
<Button
|
||||
className='trade-button'
|
||||
type='primary'
|
||||
size='large'
|
||||
onClick={connected ? null : wallet.connect}
|
||||
style={{ width: '100%' }}
|
||||
disabled={connected && !!newPosition.error}
|
||||
>
|
||||
<span>{generateActionLabel(connected, newPosition)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Breakdown item={newPosition} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import Card from 'antd/lib/card';
|
||||
import React from 'react';
|
||||
import { PoolPrice } from '../../../components/PoolPrice';
|
||||
import { SupplyOverview } from '../../../components/SupplyOverview';
|
||||
import { Position } from './interfaces';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
|
||||
export default function PoolHealth({ newPosition }: { newPosition: Position }) {
|
||||
const { pool } = usePoolAndTradeInfoFrom(newPosition);
|
||||
return (
|
||||
<Card className='new-position-item new-position-item-bottom-left'>
|
||||
{!pool && <span>Choose a CCY to see exchange rate information.</span>}
|
||||
{pool && (
|
||||
<>
|
||||
<PoolPrice pool={pool} />
|
||||
<SupplyOverview pool={pool} />
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useLendingReserve, useTokenName } from '../../../hooks';
|
||||
import { useLendingReserve } from '../../../hooks';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import './style.less';
|
||||
import tokens from '../../../config/tokens.json';
|
||||
|
||||
import { SideReserveOverview, SideReserveOverviewMode } from '../../../components/SideReserveOverview';
|
||||
import NewPositionForm from './NewPositionForm';
|
||||
import { Position } from './interfaces';
|
||||
import { useEffect } from 'react';
|
||||
import Breakdown from './Breakdown';
|
||||
import PoolHealth from './PoolHealth';
|
||||
|
||||
export const NewPosition = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
@ -15,7 +14,8 @@ export const NewPosition = () => {
|
|||
const [newPosition, setNewPosition] = useState<Position>({
|
||||
id: null,
|
||||
leverage: 1,
|
||||
asset: { value: '0' },
|
||||
collateral: {},
|
||||
asset: {},
|
||||
});
|
||||
|
||||
if (!lendingReserve) {
|
||||
|
@ -29,12 +29,11 @@ export const NewPosition = () => {
|
|||
return (
|
||||
<div className='new-position'>
|
||||
<div className='new-position-container'>
|
||||
<NewPositionForm lendingReserve={lendingReserve} newPosition={newPosition} setNewPosition={setNewPosition} />
|
||||
<SideReserveOverview
|
||||
className='new-position-item new-position-item-right'
|
||||
reserve={lendingReserve}
|
||||
mode={SideReserveOverviewMode.Borrow}
|
||||
/>
|
||||
<div className='new-position-item-left'>
|
||||
<NewPositionForm lendingReserve={lendingReserve} newPosition={newPosition} setNewPosition={setNewPosition} />
|
||||
<PoolHealth newPosition={newPosition} />
|
||||
</div>
|
||||
<Breakdown item={newPosition} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,10 +10,13 @@ export interface Token {
|
|||
export interface Position {
|
||||
id?: number | null;
|
||||
leverage: number;
|
||||
collateral?: ParsedAccount<LendingReserve>;
|
||||
collateral: {
|
||||
type?: ParsedAccount<LendingReserve>;
|
||||
value?: number | null;
|
||||
};
|
||||
asset: {
|
||||
type?: ParsedAccount<LendingReserve>;
|
||||
value: string; // because NumericInput returns strings and I dont want to deal with fixing it right now
|
||||
value?: number | null;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
import { LABELS } from '../../../constants';
|
||||
import { useEnrichedPools } from '../../../contexts/market';
|
||||
import { useUserDeposits } from '../../../hooks';
|
||||
import { usePoolForBasket } from '../../../utils/pools';
|
||||
import { Position } from './interfaces';
|
||||
import { usePoolAndTradeInfoFrom } from './utils';
|
||||
|
||||
export function useLeverage({
|
||||
newPosition,
|
||||
|
@ -12,52 +10,57 @@ export function useLeverage({
|
|||
newPosition: Position;
|
||||
setNewPosition: (pos: Position) => void;
|
||||
}) {
|
||||
const collType = newPosition.collateral;
|
||||
const desiredType = newPosition.asset.type;
|
||||
|
||||
const pool = usePoolForBasket([
|
||||
collType?.info?.liquidityMint?.toBase58(),
|
||||
desiredType?.info?.liquidityMint?.toBase58(),
|
||||
]);
|
||||
|
||||
const userDeposits = useUserDeposits();
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() == collType?.info?.liquidityMint?.toBase58()
|
||||
);
|
||||
const enriched = useEnrichedPools(pool ? [pool] : []);
|
||||
const {
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
collType,
|
||||
desiredType,
|
||||
collValue,
|
||||
desiredValue,
|
||||
leverage,
|
||||
} = usePoolAndTradeInfoFrom(newPosition);
|
||||
|
||||
// Leverage validation - if you choose this leverage, is it allowable, with your buying power and with
|
||||
// the pool we have to cover you?
|
||||
useEffect(() => {
|
||||
if (!collType) {
|
||||
setNewPosition({ ...newPosition, error: LABELS.NO_COLL_TYPE_MESSAGE });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!collateralDeposit) {
|
||||
setNewPosition({ ...newPosition, error: LABELS.NO_DEPOSIT_MESSAGE });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!collType || !desiredType || !newPosition.asset.value || !enriched || enriched.length == 0) {
|
||||
if (!desiredType || !newPosition.asset.value || !enrichedPools || enrichedPools.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is more of A than B
|
||||
const exchangeRate = enriched[0].liquidityB / enriched[0].liquidityA;
|
||||
const amountDesiredToPurchase = parseFloat(newPosition.asset.value);
|
||||
const exchangeRate = enrichedPools[0].liquidityB / enrichedPools[0].liquidityA;
|
||||
const leverageDesired = newPosition.leverage;
|
||||
const amountAvailableInOysterForMargin = collateralDeposit.info.amount * exchangeRate;
|
||||
const amountToDepositOnMargin = amountDesiredToPurchase / leverageDesired;
|
||||
const amountToDepositOnMargin = desiredValue / leverageDesired;
|
||||
|
||||
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
|
||||
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
|
||||
return;
|
||||
}
|
||||
|
||||
const liqA = enriched[0].liquidityA;
|
||||
const liqB = enriched[0].liquidityB;
|
||||
if (amountToDepositOnMargin > collValue) {
|
||||
setNewPosition({ ...newPosition, error: LABELS.SET_MORE_MARGIN_MESSAGE });
|
||||
return;
|
||||
}
|
||||
|
||||
const liqA = enrichedPools[0].liquidityA;
|
||||
const liqB = enrichedPools[0].liquidityB;
|
||||
const supplyRatio = liqA / liqB;
|
||||
|
||||
// change in liquidity is amount desired (in units of B) converted to collateral units(A)
|
||||
const chgLiqA = amountDesiredToPurchase / exchangeRate;
|
||||
const chgLiqA = desiredValue / exchangeRate;
|
||||
const newLiqA = liqA - chgLiqA;
|
||||
const newLiqB = liqB + amountDesiredToPurchase;
|
||||
const newLiqB = liqB + desiredValue;
|
||||
const newSupplyRatio = newLiqA / newLiqB;
|
||||
|
||||
const priceImpact = Math.abs(100 - 100 * (newSupplyRatio / supplyRatio));
|
||||
|
@ -69,5 +72,5 @@ export function useLeverage({
|
|||
return;
|
||||
}
|
||||
setNewPosition({ ...newPosition, error: '' });
|
||||
}, [collType, desiredType, newPosition.asset.value, newPosition.leverage, enriched]);
|
||||
}, [collType, desiredType, desiredValue, leverage, enrichedPools, collValue, collateralDeposit?.info.amount]);
|
||||
}
|
||||
|
|
|
@ -16,11 +16,25 @@
|
|||
}
|
||||
|
||||
.new-position-item-left {
|
||||
flex: 60%;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.new-position-item-top-left {
|
||||
flex: 1;
|
||||
}
|
||||
.new-position-item-bottom-left {
|
||||
flex: 1;
|
||||
}
|
||||
.new-position-item-top-right {
|
||||
flex: 1;
|
||||
}
|
||||
.new-position-item-bottom-right {
|
||||
flex: 1;
|
||||
}
|
||||
.new-position-item-right {
|
||||
flex: 30%;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Responsive layout - makes a one column layout instead of a two-column layout */
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { useEffect } from 'react';
|
||||
import { ParsedAccount } from '../../../contexts/accounts';
|
||||
import { useEnrichedPools } from '../../../contexts/market';
|
||||
import { UserDeposit, useUserDeposits } from '../../../hooks';
|
||||
import { LendingReserve, PoolInfo } from '../../../models';
|
||||
import { usePoolForBasket } from '../../../utils/pools';
|
||||
import { Position } from './interfaces';
|
||||
|
||||
export function usePoolAndTradeInfoFrom(
|
||||
newPosition: Position
|
||||
): {
|
||||
enrichedPools: any[];
|
||||
collateralDeposit: UserDeposit | undefined;
|
||||
collType: ParsedAccount<LendingReserve> | undefined;
|
||||
desiredType: ParsedAccount<LendingReserve> | undefined;
|
||||
collValue: number;
|
||||
desiredValue: number;
|
||||
leverage: number;
|
||||
pool: PoolInfo | undefined;
|
||||
} {
|
||||
const collType = newPosition.collateral.type;
|
||||
const desiredType = newPosition.asset.type;
|
||||
const collValue = newPosition.collateral.value || 0;
|
||||
const desiredValue = newPosition.asset.value || 0;
|
||||
|
||||
const pool = usePoolForBasket([
|
||||
collType?.info?.liquidityMint?.toBase58(),
|
||||
desiredType?.info?.liquidityMint?.toBase58(),
|
||||
]);
|
||||
|
||||
const userDeposits = useUserDeposits();
|
||||
const collateralDeposit = userDeposits.userDeposits.find(
|
||||
(u) => u.reserve.info.liquidityMint.toBase58() == collType?.info?.liquidityMint?.toBase58()
|
||||
);
|
||||
|
||||
const enrichedPools = useEnrichedPools(pool ? [pool] : []);
|
||||
|
||||
return {
|
||||
enrichedPools,
|
||||
collateralDeposit,
|
||||
collType,
|
||||
desiredType,
|
||||
collValue,
|
||||
desiredValue,
|
||||
leverage: newPosition.leverage,
|
||||
pool,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue