feat: Some refactoring - now it all works and is hooked up. Still lots of nice formatting but at least swap is now providing leverage validaiton.
This commit is contained in:
parent
0b0e5773c2
commit
fc23f18420
|
@ -14,10 +14,11 @@ export class NumericInput extends React.Component<any, any> {
|
||||||
onBlur = () => {
|
onBlur = () => {
|
||||||
const { value, onBlur, onChange } = this.props;
|
const { value, onBlur, onChange } = this.props;
|
||||||
let valueTemp = value;
|
let valueTemp = value;
|
||||||
if (value.charAt(value.length - 1) === '.' || value === '-') {
|
if (value === undefined || value === null) return;
|
||||||
|
if (value.charAt && (value.charAt(value.length - 1) === '.' || value === '-')) {
|
||||||
valueTemp = value.slice(0, -1);
|
valueTemp = value.slice(0, -1);
|
||||||
}
|
}
|
||||||
if (value.startsWith('.') || value.startsWith('-.')) {
|
if (value.startsWith && (value.startsWith('.') || value.startsWith('-.'))) {
|
||||||
valueTemp = valueTemp.replace('.', '0.');
|
valueTemp = valueTemp.replace('.', '0.');
|
||||||
}
|
}
|
||||||
onChange?.(valueTemp.replace(/0*(\d+)/, '$1'));
|
onChange?.(valueTemp.replace(/0*(\d+)/, '$1'));
|
||||||
|
|
|
@ -66,4 +66,5 @@ export const LABELS = {
|
||||||
NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.',
|
NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.',
|
||||||
LEVERAGE_LIMIT_MESSAGE:
|
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.',
|
'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.',
|
||||||
|
NO_DEPOSIT_MESSAGE: 'You need to deposit coin of this type into oyster before trading with it on margin.',
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,6 +178,7 @@ export const collateralExchangeRate = (reserve?: LendingReserve) => {
|
||||||
|
|
||||||
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
|
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
|
||||||
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
|
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
|
||||||
|
console.log('Exchange rate:', collateralExchangeRate(reserve));
|
||||||
return Math.floor(amount / collateralExchangeRate(reserve));
|
return Math.floor(amount / collateralExchangeRate(reserve));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
||||||
import tokens from '../../../config/tokens.json';
|
import tokens from '../../../config/tokens.json';
|
||||||
|
|
||||||
export function Breakdown({ item }: { item: Position }) {
|
export function Breakdown({ item }: { item: Position }) {
|
||||||
let myPart = (item.asset?.value || 0) / item.leverage;
|
let myPart = parseFloat(item.asset?.value || '0') / item.leverage;
|
||||||
const brokeragePart = (item.asset?.value || 0) - myPart;
|
const brokeragePart = parseFloat(item.asset?.value || '0') - myPart;
|
||||||
const brokerageColor = 'brown';
|
const brokerageColor = 'brown';
|
||||||
const myColor = 'blue';
|
const myColor = 'blue';
|
||||||
const gains = 'green';
|
const gains = 'green';
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { Button, Card, Radio } from 'antd';
|
import { Button, Card, Radio } from 'antd';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ActionConfirmation } from '../../../components/ActionConfirmation';
|
import { ActionConfirmation } from '../../../components/ActionConfirmation';
|
||||||
import { NumericInput } from '../../../components/Input/numeric';
|
import { NumericInput } from '../../../components/Input/numeric';
|
||||||
import { TokenIcon } from '../../../components/TokenIcon';
|
import { TokenIcon } from '../../../components/TokenIcon';
|
||||||
import { LABELS } from '../../../constants';
|
import { LABELS } from '../../../constants';
|
||||||
import { cache, ParsedAccount } from '../../../contexts/accounts';
|
import { cache, ParsedAccount } from '../../../contexts/accounts';
|
||||||
import { collateralToLiquidity, LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
|
import { LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
|
||||||
import { Position } from './interfaces';
|
import { Position } from './interfaces';
|
||||||
import tokens from '../../../config/tokens.json';
|
import tokens from '../../../config/tokens.json';
|
||||||
import { CollateralSelector } from '../../../components/CollateralSelector';
|
import { CollateralSelector } from '../../../components/CollateralSelector';
|
||||||
import { Breakdown } from './Breakdown';
|
import { Breakdown } from './Breakdown';
|
||||||
import { usePoolForBasket } from '../../../utils/pools';
|
import { useLeverage } from './leverage';
|
||||||
import { useEnrichedPools } from '../../../contexts/market';
|
|
||||||
|
|
||||||
interface NewPositionFormProps {
|
interface NewPositionFormProps {
|
||||||
lendingReserve: ParsedAccount<LendingReserve>;
|
lendingReserve: ParsedAccount<LendingReserve>;
|
||||||
|
@ -29,64 +28,7 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
||||||
};
|
};
|
||||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||||
|
|
||||||
const collType = newPosition.collateral;
|
useLeverage({ newPosition, setNewPosition });
|
||||||
const desiredType = newPosition.asset.type;
|
|
||||||
|
|
||||||
const pool = usePoolForBasket([
|
|
||||||
collType?.info?.liquidityMint?.toBase58(),
|
|
||||||
desiredType?.info?.liquidityMint?.toBase58(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const enriched = useEnrichedPools(pool ? [pool] : []);
|
|
||||||
|
|
||||||
// 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 || !desiredType || !newPosition.asset.value || !enriched || enriched.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const amountDesiredToPurchase = newPosition.asset.value;
|
|
||||||
const leverageDesired = newPosition.leverage;
|
|
||||||
console.log('collateral reserve', collType);
|
|
||||||
const amountAvailableInOysterForMargin = collateralToLiquidity(collType.info.availableLiquidity, desiredType.info);
|
|
||||||
const amountToDepositOnMargin = amountDesiredToPurchase / leverageDesired;
|
|
||||||
console.log(
|
|
||||||
'Amount desired',
|
|
||||||
amountDesiredToPurchase,
|
|
||||||
'leverage',
|
|
||||||
leverageDesired,
|
|
||||||
'amountAvailable',
|
|
||||||
amountAvailableInOysterForMargin,
|
|
||||||
' amount to deposit on margin',
|
|
||||||
amountToDepositOnMargin
|
|
||||||
);
|
|
||||||
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
|
|
||||||
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const liqA = enriched[0].liquidityA;
|
|
||||||
const liqB = enriched[0].liquidityB;
|
|
||||||
const supplyRatio = liqA / liqB;
|
|
||||||
|
|
||||||
console.log('Liq A', liqA, 'liq b', liqB, 'supply ratio', supplyRatio);
|
|
||||||
|
|
||||||
// change in liquidity is amount desired (in units of B) converted to collateral units(A)
|
|
||||||
const chgLiqA = collateralToLiquidity(amountDesiredToPurchase, collType.info);
|
|
||||||
const newLiqA = liqA - chgLiqA;
|
|
||||||
const newLiqB = liqB + amountDesiredToPurchase;
|
|
||||||
const newSupplyRatio = newLiqA / newLiqB; // 75 / 100
|
|
||||||
console.log('chg in liq a', chgLiqA, 'new liq a', newLiqA, 'new supply ratio', newSupplyRatio);
|
|
||||||
const priceImpact = Math.abs(100 - 100 * (newSupplyRatio / supplyRatio)); // abs(100 - 100*(0.75 / 1)) = 25%
|
|
||||||
const marginToLeverage = 100 / leverageDesired;
|
|
||||||
console.log('priceImpact', priceImpact, 'marginToLeverage', marginToLeverage);
|
|
||||||
if (marginToLeverage > priceImpact) {
|
|
||||||
// if their marginToLeverage ratio < priceImpact, we say hey ho no go
|
|
||||||
setNewPosition({ ...newPosition, error: LABELS.LEVERAGE_LIMIT_MESSAGE });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, [collType, desiredType, newPosition.asset.value, newPosition.leverage, enriched]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
|
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
|
||||||
|
@ -121,8 +63,11 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
outline: 'transparent',
|
outline: 'transparent',
|
||||||
}}
|
}}
|
||||||
onChange={(v: number) => {
|
onChange={(v: string) => {
|
||||||
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: v } });
|
setNewPosition({
|
||||||
|
...newPosition,
|
||||||
|
asset: { ...newPosition.asset, value: v },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
placeholder='0.00'
|
placeholder='0.00'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const NewPosition = () => {
|
||||||
const [newPosition, setNewPosition] = useState<Position>({
|
const [newPosition, setNewPosition] = useState<Position>({
|
||||||
id: null,
|
id: null,
|
||||||
leverage: 1,
|
leverage: 1,
|
||||||
asset: { value: 0 },
|
asset: { value: '0' },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!lendingReserve) {
|
if (!lendingReserve) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface Position {
|
||||||
collateral?: ParsedAccount<LendingReserve>;
|
collateral?: ParsedAccount<LendingReserve>;
|
||||||
asset: {
|
asset: {
|
||||||
type?: ParsedAccount<LendingReserve>;
|
type?: ParsedAccount<LendingReserve>;
|
||||||
value: number;
|
value: string; // because NumericInput returns strings and I dont want to deal with fixing it right now
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { LABELS } from '../../../constants';
|
||||||
|
import { useEnrichedPools } from '../../../contexts/market';
|
||||||
|
import { useUserDeposits } from '../../../hooks';
|
||||||
|
import { collateralToLiquidity, liquidityToCollateral } from '../../../models';
|
||||||
|
import { usePoolForBasket } from '../../../utils/pools';
|
||||||
|
import { Position } from './interfaces';
|
||||||
|
|
||||||
|
export function useLeverage({
|
||||||
|
newPosition,
|
||||||
|
setNewPosition,
|
||||||
|
}: {
|
||||||
|
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] : []);
|
||||||
|
|
||||||
|
// 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 (!collateralDeposit) {
|
||||||
|
setNewPosition({ ...newPosition, error: LABELS.NO_DEPOSIT_MESSAGE });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collType || !desiredType || !newPosition.asset.value || !enriched || enriched.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountDesiredToPurchase = parseFloat(newPosition.asset.value);
|
||||||
|
const leverageDesired = newPosition.leverage;
|
||||||
|
|
||||||
|
const amountAvailableInOysterForMargin = collateralToLiquidity(collateralDeposit.info.amount, desiredType.info);
|
||||||
|
const amountToDepositOnMargin = amountDesiredToPurchase / leverageDesired;
|
||||||
|
|
||||||
|
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
|
||||||
|
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const liqA = enriched[0].liquidityA;
|
||||||
|
const liqB = enriched[0].liquidityB;
|
||||||
|
const supplyRatio = liqA / liqB;
|
||||||
|
|
||||||
|
// change in liquidity is amount desired (in units of B) converted to collateral units(A)
|
||||||
|
const chgLiqA = liquidityToCollateral(amountDesiredToPurchase, desiredType.info);
|
||||||
|
const newLiqA = liqA - chgLiqA;
|
||||||
|
const newLiqB = liqB + amountDesiredToPurchase;
|
||||||
|
const newSupplyRatio = newLiqA / newLiqB;
|
||||||
|
|
||||||
|
const priceImpact = Math.abs(100 - 100 * (newSupplyRatio / supplyRatio));
|
||||||
|
const marginToLeverage = 100 / leverageDesired; // Would be 20% for 5x
|
||||||
|
if (marginToLeverage < priceImpact && leverageDesired != 1) {
|
||||||
|
// Obviously we allow 1x as edge case
|
||||||
|
// if their marginToLeverage ratio < priceImpact, we say hey ho no go
|
||||||
|
setNewPosition({ ...newPosition, error: LABELS.LEVERAGE_LIMIT_MESSAGE });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNewPosition({ ...newPosition, error: '' });
|
||||||
|
}, [collType, desiredType, newPosition.asset.value, newPosition.leverage, enriched]);
|
||||||
|
}
|
Loading…
Reference in New Issue