feat: back to baseline but in a new flow. about to brign in some swap code to get leverage limits from amm.

This commit is contained in:
Mr. Dummy Tester 2020-12-24 10:10:34 -06:00
parent 61cf44e95a
commit 199253e52f
14 changed files with 511 additions and 354 deletions

View File

@ -1,11 +1,11 @@
import React from "react";
import { useLendingReserves } from "../../hooks";
import { LendingMarket, LendingReserve } from "../../models";
import { TokenIcon } from "../TokenIcon";
import { getTokenName } from "../../utils/utils";
import { Select } from "antd";
import { useConnectionConfig } from "../../contexts/connection";
import { cache, ParsedAccount } from "../../contexts/accounts";
import React from 'react';
import { useLendingReserves } from '../../hooks';
import { LendingMarket, LendingReserve } from '../../models';
import { TokenIcon } from '../TokenIcon';
import { getTokenName } from '../../utils/utils';
import { Select } from 'antd';
import { useConnectionConfig } from '../../contexts/connection';
import { cache, ParsedAccount } from '../../contexts/accounts';
const { Option } = Select;
@ -18,19 +18,17 @@ export const CollateralSelector = (props: {
const { reserveAccounts } = useLendingReserves();
const { tokenMap } = useConnectionConfig();
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<
LendingMarket
>;
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(
market?.info?.quoteMint
);
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
if (!market) return null;
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
return (
<Select
size="large"
size='large'
showSearch
style={{ minWidth: 300, margin: "5px 0px" }}
placeholder="Collateral"
style={{ minWidth: 300, margin: '5px 0px' }}
placeholder='Collateral'
value={props.collateralReserve}
disabled={props.disabled}
defaultValue={props.collateralReserve}
@ -39,27 +37,18 @@ export const CollateralSelector = (props: {
props.onCollateralReserve(item);
}
}}
filterOption={(input, option) =>
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
filterOption={(input, option) => option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
>
{reserveAccounts
.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) => {
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" }}
>
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
<TokenIcon mintAddress={mint} />
{name}
</div>

View File

@ -60,4 +60,7 @@ export const LABELS = {
TRADING_TABLE_TITLE_APY: 'APY',
TRADING_TABLE_TITLE_ACTIONS: 'Action',
TRADING_ADD_POSITION: 'Add Position',
MARGIN_TRADE_ACTION: 'Margin Trade',
MARGIN_TRADE_QUESTION: 'How much of this asset would you like?',
TABLE_TITLE_BUYING_POWER: 'Total Buying Power',
};

View File

@ -1,11 +1,11 @@
import { HashRouter, Route, Switch } from "react-router-dom";
import React from "react";
import { WalletProvider } from "./contexts/wallet";
import { ConnectionProvider } from "./contexts/connection";
import { AccountsProvider } from "./contexts/accounts";
import { MarketProvider } from "./contexts/market";
import { LendingProvider } from "./contexts/lending";
import { AppLayout } from "./components/Layout";
import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { WalletProvider } from './contexts/wallet';
import { ConnectionProvider } from './contexts/connection';
import { AccountsProvider } from './contexts/accounts';
import { MarketProvider } from './contexts/market';
import { LendingProvider } from './contexts/lending';
import { AppLayout } from './components/Layout';
import {
BorrowReserveView,
@ -21,12 +21,13 @@ import {
LiquidateView,
LiquidateReserveView,
MarginTrading,
} from "./views";
} from './views';
import { NewPosition } from './views/marginTrading/newPosition';
export function Routes() {
return (
<>
<HashRouter basename={"/"}>
<HashRouter basename={'/'}>
<ConnectionProvider>
<WalletProvider>
<AccountsProvider>
@ -34,51 +35,22 @@ export function Routes() {
<LendingProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route
exact
path="/dashboard"
children={<DashboardView />}
/>
<Route path="/reserve/:id" children={<ReserveView />} />
<Route
exact
path="/deposit"
component={() => <DepositView />}
/>
<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="/marginTrading"
children={<MarginTrading />}
/>
<Route exact path="/faucet" children={<FaucetView />} />
<Route exact path='/' component={() => <HomeView />} />
<Route exact path='/dashboard' children={<DashboardView />} />
<Route path='/reserve/:id' children={<ReserveView />} />
<Route exact path='/deposit' component={() => <DepositView />} />
<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='/marginTrading' children={<MarginTrading />} />
<Route path='/marginTrading/:id' children={<NewPosition />} />
<Route exact path='/faucet' children={<FaucetView />} />
</Switch>
</AppLayout>
</LendingProvider>

View File

@ -1,42 +1,41 @@
import React from "react";
import { useTokenName, useBorrowingPower } from "../../hooks";
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber, formatPct } from "../../utils/utils";
import { Button } from "antd";
import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js";
import { LABELS } from "../../constants";
import { useMidPriceInUSD } from "../../contexts/market";
import React from 'react';
import { useTokenName, useBorrowingPower } from '../../hooks';
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
import { TokenIcon } from '../../components/TokenIcon';
import { formatNumber, formatPct } from '../../utils/utils';
import { Button } from 'antd';
import { Link } from 'react-router-dom';
import { PublicKey } from '@solana/web3.js';
import { LABELS } from '../../constants';
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 price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
const { borrowingPower, totalInQuote } = useBorrowingPower(props.address)
const { borrowingPower, totalInQuote } = useBorrowingPower(props.address);
const apr = calculateBorrowAPY(props.reserve);
return (
<Link to={`/borrow/${props.address.toBase58()}`}>
<div className="borrow-item">
<span style={{ display: "flex" }}>
<div className='borrow-item'>
<span style={{ display: 'flex' }}>
<TokenIcon mintAddress={props.reserve.liquidityMint} />
{name}
</span>
<div>${formatNumber.format(price)}</div>
<div>
<div>
<div><em>{formatNumber.format(borrowingPower)}</em> {name}</div>
<div className="dashboard-amount-quote">${formatNumber.format(totalInQuote)}</div>
<div>
<em>{formatNumber.format(borrowingPower)}</em> {name}
</div>
<div className='dashboard-amount-quote'>${formatNumber.format(totalInQuote)}</div>
</div>
</div>
<div>{formatPct.format(apr)}</div>
<div>
<Button type="primary">
<Button type='primary'>
<span>{LABELS.BORROW_ACTION}</span>
</Button>
</div>

View File

@ -1,119 +0,0 @@
import { Button, Select, Slider } from 'antd';
import React from 'react';
import { IPosition } from '.';
import { NumericInput } from '../../components/Input/numeric';
import { TokenIcon } from '../../components/TokenIcon';
import tokens from '../../config/tokens.json';
import { LABELS } from '../../constants/labels';
const { Option } = Select;
interface IEditableAssetProps {
label: string;
assetKey: string;
setItem: (item: any) => void;
item: any;
}
function EditableAsset({ label, assetKey, setItem, item }: IEditableAssetProps) {
if (!item[assetKey]?.type) {
return (
<Select
size='large'
showSearch
style={{ margin: '5px 0px' }}
placeholder={label}
onChange={(v) =>
setItem({ ...item, [assetKey]: { ...(item[assetKey] || {}), type: tokens.find((t) => t.mintAddress === v) } })
}
>
{tokens.map((token) => (
<Option key={token.mintAddress} value={token.mintAddress} name={token.tokenName} title={token.tokenName}>
<div key={token.mintAddress} style={{ display: 'flex', alignItems: 'center' }}>
<TokenIcon mintAddress={token.mintAddress} />
{token.tokenName}
</div>
</Option>
))}
</Select>
);
} else {
return (
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start' }}>
<NumericInput
value={item[assetKey].value}
style={{
fontSize: 20,
boxShadow: 'none',
borderColor: 'transparent',
outline: 'transparent',
}}
onChange={(v: number) => {
setItem({ ...item, [assetKey]: { ...(item[assetKey] || {}), value: v } });
}}
placeholder='0.00'
/>
<TokenIcon mintAddress={item[assetKey]?.type?.mintAddress} />
</div>
);
}
}
export default function MarginTradePosition({ item, setItem }: { item: IPosition; setItem?: (item: any) => void }) {
return (
<div className='trading-item'>
<div>
{setItem && (
<Select
size='large'
showSearch
style={{ margin: '5px 0px' }}
placeholder={LABELS.TRADING_TABLE_TITLE_MY_COLLATERAL}
onChange={(v) => setItem({ ...item, collateral: tokens.find((t) => t.mintAddress === v) })}
>
{tokens.map((token) => (
<Option key={token.mintAddress} value={token.mintAddress} name={token.tokenName} title={token.tokenName}>
<div key={token.mintAddress} style={{ display: 'flex', alignItems: 'center' }}>
<TokenIcon mintAddress={token.mintAddress} />
{token.tokenName}
</div>
</Option>
))}
</Select>
)}
</div>
<div>
{setItem && (
<EditableAsset
item={item}
setItem={setItem}
label={LABELS.TRADING_TABLE_TITLE_DESIRED_ASSET}
assetKey={'asset'}
/>
)}
</div>
<div>
{setItem && (
<Slider
tooltipVisible={true}
defaultValue={1}
dots={true}
max={5}
min={1}
step={1}
tooltipPlacement={'top'}
onChange={(v: number) => {
setItem({ ...item, leverage: v });
}}
/>
)}
</div>
<div>123</div>
<div>123</div>
<div>123</div>
<div>
<Button type='primary'>
<span>{LABELS.TRADING_ADD_POSITION}</span>
</Button>
</div>
</div>
);
}

View File

@ -1,138 +1,26 @@
import React, { useState } from 'react';
import React from 'react';
import { LABELS } from '../../constants';
import './style.less';
import { Card, Progress, Slider, Statistic } from 'antd';
import MarginTradePosition from './MarginTradePosition';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import './itemStyle.less';
import { Card } from 'antd';
import { useLendingReserves } from '../../hooks/useLendingReserves';
import { MarginTradeItem } from './item';
export interface IToken {
mintAddress: string;
tokenName: string;
tokenSymbol: string;
}
export interface IPosition {
id?: number | null;
leverage: number;
collateral?: IToken;
asset?: {
type: IToken;
value: number;
};
}
export function Breakdown({ item }: { item: IPosition }) {
let myPart = (item.asset?.value || 0) / item.leverage;
const brokeragePart = (item.asset?.value || 0) - myPart;
const brokerageColor = 'brown';
const myColor = 'blue';
const gains = 'green';
const losses = 'red';
const [myGain, setMyGain] = useState<number>(0);
const profitPart = (myPart + brokeragePart) * (myGain / 100);
let progressBar = null;
if (profitPart > 0) {
// normalize...
const total = profitPart + myPart + brokeragePart;
progressBar = (
<Progress
percent={(myPart / total) * 100 + (brokeragePart / total) * 100}
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
strokeColor={myColor}
trailColor={gains}
showInfo={false}
/>
);
} else {
// now, we're eating away your profit...
myPart += profitPart; // profit is negative
const total = myPart + brokeragePart;
if (myPart < 0) {
progressBar = <p>Your position has been liquidated at this price swing.</p>;
} else
progressBar = (
<Progress
showInfo={false}
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
trailColor={myColor}
/>
);
}
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={item.asset?.type.tokenName}
/>
</Card>
<Card>
<Statistic
title='My Collateral Value'
value={myPart}
precision={2}
valueStyle={{ color: myColor }}
suffix={item.asset?.type.tokenName}
/>
</Card>
<Card>
<Statistic
title='Profit/Loss'
value={profitPart}
precision={2}
valueStyle={{ color: profitPart > 0 ? gains : losses }}
suffix={item.asset?.type.tokenSymbol}
prefix={profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
/>
</Card>
</div>
{progressBar}
</div>
);
}
export const MarginTrading = () => {
const [newPosition, setNewPosition] = useState<IPosition>({ id: null, leverage: 1 });
const positions: any[] = [];
const { reserveAccounts } = useLendingReserves();
return (
<div className='trading-container'>
<div className='flexColumn'>
<Card>
<div className='trading-item trading-header'>
<div>{LABELS.TRADING_TABLE_TITLE_MY_COLLATERAL}</div>
<div>{LABELS.TRADING_TABLE_TITLE_DESIRED_ASSET}</div>
<div>{LABELS.TRADING_TABLE_TITLE_MULTIPLIER}</div>
<div>{LABELS.TRADING_TABLE_TITLE_ASSET_PRICE}</div>
<div>{LABELS.TRADING_TABLE_TITLE_LIQUIDATION_PRICE}</div>
<div>{LABELS.TRADING_TABLE_TITLE_APY}</div>
<div>{LABELS.TRADING_TABLE_TITLE_ACTIONS}</div>
</div>
<MarginTradePosition key={newPosition.id} item={newPosition} setItem={setNewPosition} />
<Breakdown item={newPosition} />
{positions.map((item) => (
<MarginTradePosition key={item.id} item={item} />
))}
</Card>
</div>
<div className='flexColumn'>
<Card>
<div className='choose-margin-item choose-margin-header'>
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>Serum Dex Price</div>
<div>{LABELS.TABLE_TITLE_BUYING_POWER}</div>
<div>{LABELS.TABLE_TITLE_APY}</div>
<div></div>
</div>
{reserveAccounts.map((account) => (
<MarginTradeItem reserve={account.info} address={account.pubkey} />
))}
</Card>
</div>
);
};

View File

@ -0,0 +1,43 @@
import React from 'react';
import { useTokenName } from '../../hooks';
import { calculateBorrowAPY, LendingReserve } from '../../models/lending';
import { TokenIcon } from '../../components/TokenIcon';
import { formatNumber, formatPct } from '../../utils/utils';
import { Button } from 'antd';
import { Link } from 'react-router-dom';
import { PublicKey } from '@solana/web3.js';
import { LABELS } from '../../constants';
import { useMidPriceInUSD } from '../../contexts/market';
export const MarginTradeItem = (props: { reserve: LendingReserve; address: PublicKey }) => {
const name = useTokenName(props.reserve.liquidityMint);
const price = useMidPriceInUSD(props.reserve.liquidityMint.toBase58()).price;
const apr = calculateBorrowAPY(props.reserve);
return (
<Link to={`/marginTrading/${props.address.toBase58()}`}>
<div className='choose-margin-item'>
<span style={{ display: 'flex' }}>
<TokenIcon mintAddress={props.reserve.liquidityMint} />
{name}
</span>
<div>${formatNumber.format(price)}</div>
<div>
<div>
<div>
<em>{formatNumber.format(200)}</em> {name}
</div>
<div className='dashboard-amount-quote'>${formatNumber.format(300)}</div>
</div>
</div>
<div>{formatPct.format(apr)}</div>
<div>
<Button type='primary'>
<span>{LABELS.MARGIN_TRADE_ACTION}</span>
</Button>
</div>
</div>
</Link>
);
};

View File

@ -0,0 +1,32 @@
@import '~antd/es/style/themes/default.less';
.choose-margin-item {
display: flex;
justify-content: space-between;
align-items: center;
color: @text-color;
& > :nth-child(n) {
flex: 20%;
text-align: right;
margin: 10px 0px;
}
& > :first-child {
flex: 80px;
}
border-bottom: 1px solid #eee;
}
.choose-margin-header {
& > div {
flex: 20%;
text-align: right;
}
& > :first-child {
text-align: left;
flex: 80px;
}
}

View File

@ -0,0 +1,93 @@
import { Progress, Slider, Card, Statistic } from 'antd';
import React, { useState } from 'react';
import { Position } from './interfaces';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
export function Breakdown({ item }: { item: Position }) {
let myPart = (item.asset?.value || 0) / item.leverage;
const brokeragePart = (item.asset?.value || 0) - myPart;
const brokerageColor = 'brown';
const myColor = 'blue';
const gains = 'green';
const losses = 'red';
const [myGain, setMyGain] = useState<number>(0);
const profitPart = (myPart + brokeragePart) * (myGain / 100);
let progressBar = null;
if (profitPart > 0) {
// normalize...
const total = profitPart + myPart + brokeragePart;
progressBar = (
<Progress
percent={(myPart / total) * 100 + (brokeragePart / total) * 100}
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
strokeColor={myColor}
trailColor={gains}
showInfo={false}
/>
);
} else {
// now, we're eating away your profit...
myPart += profitPart; // profit is negative
const total = myPart + brokeragePart;
if (myPart < 0) {
progressBar = <p>Your position has been liquidated at this price swing.</p>;
} else
progressBar = (
<Progress
showInfo={false}
success={{ percent: (brokeragePart / total) * 100, strokeColor: brokerageColor }}
trailColor={myColor}
/>
);
}
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={item.asset?.type?.tokenName}
/>
</Card>
<Card>
<Statistic
title='My Collateral Value'
value={myPart}
precision={2}
valueStyle={{ color: myColor }}
suffix={item.asset?.type?.tokenName}
/>
</Card>
<Card>
<Statistic
title='Profit/Loss'
value={profitPart}
precision={2}
valueStyle={{ color: profitPart > 0 ? gains : losses }}
suffix={item.asset?.type?.tokenSymbol}
prefix={profitPart > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
/>
</Card>
</div>
{progressBar}
</div>
);
}

View File

@ -0,0 +1,56 @@
import { Select } from 'antd';
import React from 'react';
import { NumericInput } from '../../../components/Input/numeric';
import { TokenIcon } from '../../../components/TokenIcon';
import tokens from '../../../config/tokens.json';
const { Option } = Select;
interface EditableAssetProps {
label: string;
assetKey: string;
setItem: (item: any) => void;
item: any;
}
export default function EditableAsset({ label, assetKey, setItem, item }: EditableAssetProps) {
if (!item[assetKey]?.type) {
return (
<Select
size='large'
showSearch
style={{ margin: '5px 0px' }}
placeholder={label}
onChange={(v) =>
setItem({ ...item, [assetKey]: { ...(item[assetKey] || {}), type: tokens.find((t) => t.mintAddress === v) } })
}
>
{tokens.map((token) => (
<Option key={token.mintAddress} value={token.mintAddress} name={token.tokenName} title={token.tokenName}>
<div key={token.mintAddress} style={{ display: 'flex', alignItems: 'center' }}>
<TokenIcon mintAddress={token.mintAddress} />
{token.tokenName}
</div>
</Option>
))}
</Select>
);
} else {
return (
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start' }}>
<NumericInput
value={item[assetKey].value}
style={{
fontSize: 20,
boxShadow: 'none',
borderColor: 'transparent',
outline: 'transparent',
}}
onChange={(v: number) => {
setItem({ ...item, [assetKey]: { ...(item[assetKey] || {}), value: v } });
}}
placeholder='0.00'
/>
<TokenIcon mintAddress={item[assetKey]?.type?.mintAddress} />
</div>
);
}
}

View File

@ -0,0 +1,108 @@
import { Button, Card, Radio } 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';
interface NewPositionFormProps {
lendingReserve: ParsedAccount<LendingReserve>;
newPosition: Position;
setNewPosition: (pos: Position) => void;
}
export default function NewPositionForm({ lendingReserve, newPosition, setNewPosition }: NewPositionFormProps) {
const bodyStyle: React.CSSProperties = {
display: 'flex',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
};
const [showConfirmation, setShowConfirmation] = useState(false);
return (
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
{showConfirmation ? (
<ActionConfirmation onClose={() => setShowConfirmation(false)} />
) : (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
}}
>
<div className='borrow-input-title'>{LABELS.SELECT_COLLATERAL}</div>
<CollateralSelector
reserve={lendingReserve.info}
onCollateralReserve={(key) => {
const id: string = cache.byParser(LendingReserveParser).find((acc) => acc === key) || '';
const parser = cache.get(id) as ParsedAccount<LendingReserve>;
const tokenMint = parser.info.collateralMint.toBase58();
setNewPosition({ ...newPosition, collateral: tokens.find((t) => t.mintAddress === tokenMint) });
}}
/>
<div className='borrow-input-title'>{LABELS.MARGIN_TRADE_QUESTION}</div>
<div className='token-input'>
<TokenIcon mintAddress={newPosition.asset.type?.mintAddress} />
<NumericInput
value={newPosition.asset.value}
style={{
fontSize: 20,
boxShadow: 'none',
borderColor: 'transparent',
outline: 'transparent',
}}
onChange={(v: number) => {
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: v } });
}}
placeholder='0.00'
/>
<div>{newPosition.asset.type?.tokenSymbol}</div>
</div>
<div>
<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={{
fontSize: 20,
boxShadow: 'none',
borderColor: 'transparent',
outline: 'transparent',
}}
onChange={(leverage: number) => {
setNewPosition({ ...newPosition, leverage });
}}
/>
</div>
<div>
<Button type='primary'>
<span>{LABELS.TRADING_ADD_POSITION}</span>
</Button>
</div>
<Breakdown item={newPosition} />
</div>
)}
</Card>
);
}

View File

@ -0,0 +1,46 @@
import React, { useState } from 'react';
import { useLendingReserve, useTokenName } 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';
export const NewPosition = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const [newPosition, setNewPosition] = useState<Position>({
id: null,
leverage: 1,
asset: { value: 0 },
});
const assetTokenType = tokens.find((t) => t.mintAddress === lendingReserve?.info?.liquidityMint?.toBase58());
if (!lendingReserve) {
return null;
}
if (!assetTokenType) {
return null;
} else {
if (newPosition.asset.type != assetTokenType) {
setNewPosition({ ...newPosition, asset: { value: newPosition.asset.value, type: assetTokenType } });
}
}
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>
</div>
);
};

View File

@ -0,0 +1,15 @@
export interface Token {
mintAddress: string;
tokenName: string;
tokenSymbol: string;
}
export interface Position {
id?: number | null;
leverage: number;
collateral?: Token;
asset: {
type?: Token;
value: number;
};
}

View File

@ -0,0 +1,32 @@
.new-position {
display: flex;
flex-direction: column;
flex: 1;
}
.new-position-item {
margin: 4px;
}
.new-position-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex: 1;
}
.new-position-item-left {
flex: 60%;
}
.new-position-item-right {
flex: 30%;
}
/* Responsive layout - makes a one column layout instead of a two-column layout */
@media (max-width: 600px) {
.new-position-item-right,
.new-position-item-left {
flex: 100%;
}
}