mirror of https://github.com/certusone/oyster.git
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 = () => {
|
||||
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'));
|
||||
|
|
|
@ -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.',
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'
|
||||
/>
|
||||
|
|
|
@ -15,7 +15,7 @@ export const NewPosition = () => {
|
|||
const [newPosition, setNewPosition] = useState<Position>({
|
||||
id: null,
|
||||
leverage: 1,
|
||||
asset: { value: 0 },
|
||||
asset: { value: '0' },
|
||||
});
|
||||
|
||||
if (!lendingReserve) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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