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:
Mr. Dummy Tester 2020-12-25 14:39:06 -06:00
parent 0b0e5773c2
commit fc23f18420
8 changed files with 91 additions and 70 deletions

View File

@ -14,10 +14,11 @@ export class NumericInput extends React.Component<any, any> {
onBlur = () => {
const { value, onBlur, onChange } = this.props;
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);
}
if (value.startsWith('.') || value.startsWith('-.')) {
if (value.startsWith && (value.startsWith('.') || value.startsWith('-.'))) {
valueTemp = valueTemp.replace('.', '0.');
}
onChange?.(valueTemp.replace(/0*(\d+)/, '$1'));

View File

@ -66,4 +66,5 @@ export const LABELS = {
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.',
NO_DEPOSIT_MESSAGE: 'You need to deposit coin of this type into oyster before trading with it on margin.',
};

View File

@ -178,6 +178,7 @@ 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));
};

View File

@ -5,8 +5,8 @@ import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import tokens from '../../../config/tokens.json';
export function Breakdown({ item }: { item: Position }) {
let myPart = (item.asset?.value || 0) / item.leverage;
const brokeragePart = (item.asset?.value || 0) - myPart;
let myPart = parseFloat(item.asset?.value || '0') / item.leverage;
const brokeragePart = parseFloat(item.asset?.value || '0') - myPart;
const brokerageColor = 'brown';
const myColor = 'blue';
const gains = 'green';

View File

@ -1,17 +1,16 @@
import { Button, Card, Radio } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
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 { collateralToLiquidity, LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
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 { usePoolForBasket } from '../../../utils/pools';
import { useEnrichedPools } from '../../../contexts/market';
import { useLeverage } from './leverage';
interface NewPositionFormProps {
lendingReserve: ParsedAccount<LendingReserve>;
@ -29,64 +28,7 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
};
const [showConfirmation, setShowConfirmation] = useState(false);
const collType = newPosition.collateral;
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]);
useLeverage({ newPosition, setNewPosition });
return (
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
@ -121,8 +63,11 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
borderColor: 'transparent',
outline: 'transparent',
}}
onChange={(v: number) => {
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: v } });
onChange={(v: string) => {
setNewPosition({
...newPosition,
asset: { ...newPosition.asset, value: v },
});
}}
placeholder='0.00'
/>

View File

@ -15,7 +15,7 @@ export const NewPosition = () => {
const [newPosition, setNewPosition] = useState<Position>({
id: null,
leverage: 1,
asset: { value: 0 },
asset: { value: '0' },
});
if (!lendingReserve) {

View File

@ -13,7 +13,7 @@ export interface Position {
collateral?: ParsedAccount<LendingReserve>;
asset: {
type?: ParsedAccount<LendingReserve>;
value: number;
value: string; // because NumericInput returns strings and I dont want to deal with fixing it right now
};
error?: string;
}

View File

@ -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]);
}